"use client"; import { useAtom, useAtomValue } from "jotai"; import { Bot, Check, ChevronDown, ImageOff, Search, Settings2, Zap } from "lucide-react"; import { useMemo, useState } from "react"; import { updateModelRolesMutationAtom } from "@/atoms/model-connections/model-connections-mutation.atoms"; import { globalModelConnectionsAtom, modelConnectionsAtom, modelRolesAtom, } from "@/atoms/model-connections/model-connections-query.atoms"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger, } from "@/components/ui/drawer"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Spinner } from "@/components/ui/spinner"; import type { ConnectionRead, ModelRead } from "@/contracts/types/model-connections.types"; import type { GlobalImageGenConfig, GlobalNewLLMConfig, GlobalVisionLLMConfig, ImageGenerationConfig, NewLLMConfigPublic, VisionLLMConfig, } from "@/contracts/types/new-llm-config.types"; import { useIsMobile } from "@/hooks/use-mobile"; import { getProviderIcon } from "@/lib/provider-icons"; import { cn } from "@/lib/utils"; interface ModelSelectorProps { onEditLLM: (config: NewLLMConfigPublic | GlobalNewLLMConfig, isGlobal: boolean) => void; onAddNewLLM: (provider?: string) => void; onEditImage?: (config: ImageGenerationConfig | GlobalImageGenConfig, isGlobal: boolean) => void; onAddNewImage?: (provider?: string) => void; onEditVision?: (config: VisionLLMConfig | GlobalVisionLLMConfig, isGlobal: boolean) => void; onAddNewVision?: (provider?: string) => void; className?: string; } type ChatModel = ModelRead & { connectionId: number; connectionLabel: string; provider: string; }; function modelName(model: ModelRead) { return model.display_name || model.model_id; } function connectionLabel(connection: ConnectionRead) { if (connection.scope === "GLOBAL") return "Hosted"; return connection.provider; } function flattenChatModels(connections: ConnectionRead[]) { return connections.flatMap((connection) => connection.models .filter((model) => model.enabled && Boolean(model.supports_chat)) .map((model) => ({ ...model, connectionId: connection.id, connectionLabel: connectionLabel(connection), provider: connection.provider, })) ); } function groupedModels(models: ChatModel[]) { return models.reduce>((groups, model) => { const key = model.connectionLabel; if (!groups[key]) groups[key] = []; groups[key].push(model); return groups; }, {}); } export function ModelSelector({ onAddNewLLM, onEditLLM, onEditImage, onAddNewImage, onEditVision, onAddNewVision, className, }: ModelSelectorProps) { void onEditLLM; void onEditImage; void onAddNewImage; void onEditVision; void onAddNewVision; const isMobile = useIsMobile(); const [open, setOpen] = useState(false); const [search, setSearch] = useState(""); const [{ data: globalConnections = [], isLoading: globalLoading }] = useAtom( globalModelConnectionsAtom ); const [{ data: connections = [], isLoading: connectionsLoading }] = useAtom(modelConnectionsAtom); const [{ data: roles }] = useAtom(modelRolesAtom); const updateRoles = useAtomValue(updateModelRolesMutationAtom); const chatModels = useMemo(() => { const normalized = search.trim().toLowerCase(); const models = flattenChatModels([...globalConnections, ...connections]); if (!normalized) return models; return models.filter((model) => [modelName(model), model.model_id, model.connectionLabel] .join(" ") .toLowerCase() .includes(normalized) ); }, [globalConnections, connections, search]); const selected = chatModels.find((model) => model.id === roles?.chat_model_id); const groups = groupedModels(chatModels); const loading = globalLoading || connectionsLoading; function selectModel(modelId: number) { updateRoles.mutate({ chat_model_id: modelId }); setOpen(false); } const content = (
setSearch(event.target.value)} placeholder="Search chat models..." className="pl-9" />
{loading ? (
) : Object.keys(groups).length === 0 ? (
No enabled chat models. Add or enable models in Settings.
) : ( Object.entries(groups).map(([connection, models]) => (
{connection}
{models.map((model) => ( ))}
)) )}
); const trigger = ( ); if (isMobile) { return ( {trigger} Select Chat Model {content} ); } return ( {trigger} {content} ); }