"use client"; import { useAtomValue } from "jotai"; import { Bot, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Edit3, ImageIcon, Layers, Plus, ScanEye, Search, Zap, } from "lucide-react"; import type React from "react"; import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; import { globalImageGenConfigsAtom, imageGenConfigsAtom, } from "@/atoms/image-gen-config/image-gen-config-query.atoms"; import { updateLLMPreferencesMutationAtom } from "@/atoms/new-llm-config/new-llm-config-mutation.atoms"; import { globalNewLLMConfigsAtom, llmPreferencesAtom, newLLMConfigsAtom, } from "@/atoms/new-llm-config/new-llm-config-query.atoms"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; import { globalVisionLLMConfigsAtom, visionLLMConfigsAtom, } from "@/atoms/vision-llm-config/vision-llm-config-query.atoms"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Drawer, DrawerContent, DrawerHandle, DrawerHeader, DrawerTitle, DrawerTrigger, } from "@/components/ui/drawer"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Spinner } from "@/components/ui/spinner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; 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"; // ─── Helpers ──────────────────────────────────────────────────────── const PROVIDER_NAMES: Record = { OPENAI: "OpenAI", ANTHROPIC: "Anthropic", GOOGLE: "Google", AZURE: "Azure", AZURE_OPENAI: "Azure OpenAI", AWS_BEDROCK: "AWS Bedrock", BEDROCK: "Bedrock", DEEPSEEK: "DeepSeek", MISTRAL: "Mistral", COHERE: "Cohere", GITHUB_MODELS: "GitHub Models", GROQ: "Groq", OLLAMA: "Ollama", TOGETHER_AI: "Together AI", FIREWORKS_AI: "Fireworks AI", REPLICATE: "Replicate", HUGGINGFACE: "HuggingFace", PERPLEXITY: "Perplexity", XAI: "xAI", OPENROUTER: "OpenRouter", CEREBRAS: "Cerebras", SAMBANOVA: "SambaNova", VERTEX_AI: "Vertex AI", MINIMAX: "MiniMax", MOONSHOT: "Moonshot", ZHIPU: "Zhipu", DEEPINFRA: "DeepInfra", CLOUDFLARE: "Cloudflare", DATABRICKS: "Databricks", NSCALE: "NScale", RECRAFT: "Recraft", XINFERENCE: "XInference", CUSTOM: "Custom", AI21: "AI21", ALIBABA_QWEN: "Qwen", ANYSCALE: "Anyscale", COMETAPI: "CometAPI", }; // Provider keys valid per model type, matching backend enums // (LiteLLMProvider, ImageGenProvider, VisionProvider in db.py) const LLM_PROVIDER_KEYS: string[] = [ "OPENAI", "ANTHROPIC", "GOOGLE", "AZURE_OPENAI", "BEDROCK", "VERTEX_AI", "GROQ", "DEEPSEEK", "XAI", "MISTRAL", "COHERE", "OPENROUTER", "TOGETHER_AI", "FIREWORKS_AI", "REPLICATE", "PERPLEXITY", "OLLAMA", "CEREBRAS", "SAMBANOVA", "DEEPINFRA", "AI21", "ALIBABA_QWEN", "MOONSHOT", "ZHIPU", "MINIMAX", "HUGGINGFACE", "CLOUDFLARE", "DATABRICKS", "ANYSCALE", "COMETAPI", "GITHUB_MODELS", "CUSTOM", ]; const IMAGE_PROVIDER_KEYS: string[] = [ "OPENAI", "AZURE_OPENAI", "GOOGLE", "VERTEX_AI", "BEDROCK", "RECRAFT", "OPENROUTER", "XINFERENCE", "NSCALE", ]; const VISION_PROVIDER_KEYS: string[] = [ "OPENAI", "ANTHROPIC", "GOOGLE", "AZURE_OPENAI", "VERTEX_AI", "BEDROCK", "XAI", "OPENROUTER", "OLLAMA", "GROQ", "TOGETHER_AI", "FIREWORKS_AI", "DEEPSEEK", "MISTRAL", "CUSTOM", ]; const PROVIDER_KEYS_BY_TAB: Record = { llm: LLM_PROVIDER_KEYS, image: IMAGE_PROVIDER_KEYS, vision: VISION_PROVIDER_KEYS, }; function formatProviderName(provider: string): string { const key = provider.toUpperCase(); return ( PROVIDER_NAMES[key] ?? provider.charAt(0).toUpperCase() + provider.slice(1).toLowerCase().replace(/_/g, " ") ); } function normalizeText(input: string): string { return input .normalize("NFD") .replace(/\p{Diacritic}/gu, "") .toLowerCase() .replace(/[^a-z0-9]+/g, " ") .trim(); } interface ConfigBase { id: number; name: string; model_name: string; provider: string; } function filterAndScore( configs: T[], selectedProvider: string, searchQuery: string ): T[] { let result = configs; if (selectedProvider !== "all") { result = result.filter((c) => c.provider.toUpperCase() === selectedProvider); } if (!searchQuery.trim()) return result; const normalized = normalizeText(searchQuery); const tokens = normalized.split(/\s+/).filter(Boolean); const scored = result.map((c) => { const aggregate = normalizeText([c.name, c.model_name, c.provider].join(" ")); let score = 0; if (aggregate.includes(normalized)) score += 5; for (const token of tokens) { if (aggregate.includes(token)) score += 1; } return { config: c, score }; }); return scored .filter((s) => s.score > 0) .sort((a, b) => b.score - a.score) .map((s) => s.config); } interface DisplayItem { config: ConfigBase & Record; isGlobal: boolean; isAutoMode: boolean; } // ─── Component ────────────────────────────────────────────────────── 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; } export function ModelSelector({ onEditLLM, onAddNewLLM, onEditImage, onAddNewImage, onEditVision, onAddNewVision, className, }: ModelSelectorProps) { const [open, setOpen] = useState(false); const [activeTab, setActiveTab] = useState<"llm" | "image" | "vision">("llm"); const [searchQuery, setSearchQuery] = useState(""); const [selectedProvider, setSelectedProvider] = useState("all"); const [focusedIndex, setFocusedIndex] = useState(-1); const [modelScrollPos, setModelScrollPos] = useState<"top" | "middle" | "bottom">("top"); const [sidebarScrollPos, setSidebarScrollPos] = useState<"top" | "middle" | "bottom">("top"); const providerSidebarRef = useRef(null); const modelListRef = useRef(null); const searchInputRef = useRef(null); const isMobile = useIsMobile(); const handleOpenChange = useCallback( (next: boolean) => { if (next) { setSearchQuery(""); setSelectedProvider("all"); if (!isMobile) { requestAnimationFrame(() => searchInputRef.current?.focus()); } } setOpen(next); }, [isMobile] ); const handleTabChange = useCallback( (next: "llm" | "image" | "vision") => { setActiveTab(next); setSelectedProvider("all"); setSearchQuery(""); setFocusedIndex(-1); setModelScrollPos("top"); if (open && !isMobile) { requestAnimationFrame(() => searchInputRef.current?.focus()); } }, [open, isMobile] ); const handleModelListScroll = useCallback((e: React.UIEvent) => { const el = e.currentTarget; const atTop = el.scrollTop <= 2; const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight <= 2; setModelScrollPos(atTop ? "top" : atBottom ? "bottom" : "middle"); }, []); const handleSidebarScroll = useCallback( (e: React.UIEvent) => { const el = e.currentTarget; if (isMobile) { const atStart = el.scrollLeft <= 2; const atEnd = el.scrollWidth - el.scrollLeft - el.clientWidth <= 2; setSidebarScrollPos(atStart ? "top" : atEnd ? "bottom" : "middle"); } else { const atTop = el.scrollTop <= 2; const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight <= 2; setSidebarScrollPos(atTop ? "top" : atBottom ? "bottom" : "middle"); } }, [isMobile] ); // Cmd/Ctrl+M shortcut (desktop only) useEffect(() => { if (isMobile) return; const handler = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === "m") { e.preventDefault(); // setOpen((prev) => !prev); handleOpenChange(!open); } }; document.addEventListener("keydown", handler); return () => document.removeEventListener("keydown", handler); }, [isMobile, open, handleOpenChange]); // ─── Data ─── const { data: llmUserConfigs, isLoading: llmUserLoading } = useAtomValue(newLLMConfigsAtom); const { data: llmGlobalConfigs, isLoading: llmGlobalLoading } = useAtomValue(globalNewLLMConfigsAtom); const { data: preferences, isLoading: prefsLoading } = useAtomValue(llmPreferencesAtom); const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); const { mutateAsync: updatePreferences } = useAtomValue(updateLLMPreferencesMutationAtom); const { data: imageGlobalConfigs, isLoading: imageGlobalLoading } = useAtomValue(globalImageGenConfigsAtom); const { data: imageUserConfigs, isLoading: imageUserLoading } = useAtomValue(imageGenConfigsAtom); const { data: visionGlobalConfigs, isLoading: visionGlobalLoading } = useAtomValue( globalVisionLLMConfigsAtom ); const { data: visionUserConfigs, isLoading: visionUserLoading } = useAtomValue(visionLLMConfigsAtom); const isLoading = llmUserLoading || llmGlobalLoading || prefsLoading || imageGlobalLoading || imageUserLoading || visionGlobalLoading || visionUserLoading; // ─── Current selected configs ─── const currentLLMConfig = useMemo(() => { if (!preferences) return null; const id = preferences.agent_llm_id; if (id === null || id === undefined) return null; if (id <= 0) return llmGlobalConfigs?.find((c) => c.id === id) ?? null; return llmUserConfigs?.find((c) => c.id === id) ?? null; }, [preferences, llmGlobalConfigs, llmUserConfigs]); const isLLMAutoMode = currentLLMConfig && "is_auto_mode" in currentLLMConfig && currentLLMConfig.is_auto_mode; const currentImageConfig = useMemo(() => { if (!preferences) return null; const id = preferences.image_generation_config_id; if (id === null || id === undefined) return null; return ( imageGlobalConfigs?.find((c) => c.id === id) ?? imageUserConfigs?.find((c) => c.id === id) ?? null ); }, [preferences, imageGlobalConfigs, imageUserConfigs]); const isImageAutoMode = currentImageConfig && "is_auto_mode" in currentImageConfig && currentImageConfig.is_auto_mode; const currentVisionConfig = useMemo(() => { if (!preferences) return null; const id = preferences.vision_llm_config_id; if (id === null || id === undefined) return null; return ( visionGlobalConfigs?.find((c) => c.id === id) ?? visionUserConfigs?.find((c) => c.id === id) ?? null ); }, [preferences, visionGlobalConfigs, visionUserConfigs]); const isVisionAutoMode = currentVisionConfig && "is_auto_mode" in currentVisionConfig && currentVisionConfig.is_auto_mode; // ─── Filtered configs (separate global / user for section headers) ─── const filteredLLMGlobal = useMemo( () => filterAndScore(llmGlobalConfigs ?? [], selectedProvider, searchQuery), [llmGlobalConfigs, selectedProvider, searchQuery] ); const filteredLLMUser = useMemo( () => filterAndScore(llmUserConfigs ?? [], selectedProvider, searchQuery), [llmUserConfigs, selectedProvider, searchQuery] ); const filteredImageGlobal = useMemo( () => filterAndScore(imageGlobalConfigs ?? [], selectedProvider, searchQuery), [imageGlobalConfigs, selectedProvider, searchQuery] ); const filteredImageUser = useMemo( () => filterAndScore(imageUserConfigs ?? [], selectedProvider, searchQuery), [imageUserConfigs, selectedProvider, searchQuery] ); const filteredVisionGlobal = useMemo( () => filterAndScore(visionGlobalConfigs ?? [], selectedProvider, searchQuery), [visionGlobalConfigs, selectedProvider, searchQuery] ); const filteredVisionUser = useMemo( () => filterAndScore(visionUserConfigs ?? [], selectedProvider, searchQuery), [visionUserConfigs, selectedProvider, searchQuery] ); // Combined display list for keyboard navigation const currentDisplayItems: DisplayItem[] = useMemo(() => { const toItems = (configs: ConfigBase[], isGlobal: boolean): DisplayItem[] => configs.map((c) => ({ config: c as ConfigBase & Record, isGlobal, isAutoMode: isGlobal && "is_auto_mode" in c && !!(c as Record).is_auto_mode, })); const sortGlobalItems = (items: DisplayItem[]): DisplayItem[] => [...items].sort((a, b) => { if (a.isAutoMode !== b.isAutoMode) return a.isAutoMode ? -1 : 1; const aPremium = !!(a.config as Record).is_premium; const bPremium = !!(b.config as Record).is_premium; if (aPremium !== bPremium) return aPremium ? 1 : -1; return 0; }); switch (activeTab) { case "llm": return [ ...sortGlobalItems(toItems(filteredLLMGlobal, true)), ...toItems(filteredLLMUser, false), ]; case "image": return [ ...sortGlobalItems(toItems(filteredImageGlobal, true)), ...toItems(filteredImageUser, false), ]; case "vision": return [ ...sortGlobalItems(toItems(filteredVisionGlobal, true)), ...toItems(filteredVisionUser, false), ]; } }, [ activeTab, filteredLLMGlobal, filteredLLMUser, filteredImageGlobal, filteredImageUser, filteredVisionGlobal, filteredVisionUser, ]); // ─── Provider sidebar data ─── // Collect which providers actually have configured models for the active tab const configuredProviderSet = useMemo(() => { const configs = activeTab === "llm" ? [...(llmGlobalConfigs ?? []), ...(llmUserConfigs ?? [])] : activeTab === "image" ? [...(imageGlobalConfigs ?? []), ...(imageUserConfigs ?? [])] : [...(visionGlobalConfigs ?? []), ...(visionUserConfigs ?? [])]; const set = new Set(); for (const c of configs) { if (c.provider) set.add(c.provider.toUpperCase()); } return set; }, [ activeTab, llmGlobalConfigs, llmUserConfigs, imageGlobalConfigs, imageUserConfigs, visionGlobalConfigs, visionUserConfigs, ]); // Show only providers valid for the active tab; configured ones first const activeProviders = useMemo(() => { const tabKeys = PROVIDER_KEYS_BY_TAB[activeTab] ?? LLM_PROVIDER_KEYS; const configured = tabKeys.filter((p) => configuredProviderSet.has(p)); const unconfigured = tabKeys.filter((p) => !configuredProviderSet.has(p)); return ["all", ...configured, ...unconfigured]; }, [activeTab, configuredProviderSet]); const providerModelCounts = useMemo(() => { const allConfigs = activeTab === "llm" ? [...(llmGlobalConfigs ?? []), ...(llmUserConfigs ?? [])] : activeTab === "image" ? [...(imageGlobalConfigs ?? []), ...(imageUserConfigs ?? [])] : [...(visionGlobalConfigs ?? []), ...(visionUserConfigs ?? [])]; const counts: Record = { all: allConfigs.length }; for (const c of allConfigs) { const p = c.provider.toUpperCase(); counts[p] = (counts[p] || 0) + 1; } return counts; }, [ activeTab, llmGlobalConfigs, llmUserConfigs, imageGlobalConfigs, imageUserConfigs, visionGlobalConfigs, visionUserConfigs, ]); // ─── Selection handlers ─── const handleSelectLLM = useCallback( async (config: NewLLMConfigPublic | GlobalNewLLMConfig) => { if (currentLLMConfig?.id === config.id) { setOpen(false); return; } if (!searchSpaceId) { toast.error("No search space selected"); return; } try { await updatePreferences({ search_space_id: Number(searchSpaceId), data: { agent_llm_id: config.id }, }); toast.success(`Switched to ${config.name}`); setOpen(false); } catch { toast.error("Failed to switch model"); } }, [currentLLMConfig, searchSpaceId, updatePreferences] ); const handleSelectImage = useCallback( async (configId: number) => { if (currentImageConfig?.id === configId) { setOpen(false); return; } if (!searchSpaceId) { toast.error("No search space selected"); return; } try { await updatePreferences({ search_space_id: Number(searchSpaceId), data: { image_generation_config_id: configId }, }); toast.success("Image model updated"); setOpen(false); } catch { toast.error("Failed to switch image model"); } }, [currentImageConfig, searchSpaceId, updatePreferences] ); const handleSelectVision = useCallback( async (configId: number) => { if (currentVisionConfig?.id === configId) { setOpen(false); return; } if (!searchSpaceId) { toast.error("No search space selected"); return; } try { await updatePreferences({ search_space_id: Number(searchSpaceId), data: { vision_llm_config_id: configId }, }); toast.success("Vision model updated"); setOpen(false); } catch { toast.error("Failed to switch vision model"); } }, [currentVisionConfig, searchSpaceId, updatePreferences] ); const handleSelectItem = useCallback( (item: DisplayItem) => { switch (activeTab) { case "llm": handleSelectLLM(item.config as NewLLMConfigPublic | GlobalNewLLMConfig); break; case "image": handleSelectImage(item.config.id); break; case "vision": handleSelectVision(item.config.id); break; } }, [activeTab, handleSelectLLM, handleSelectImage, handleSelectVision] ); const handleEditItem = useCallback( (e: React.MouseEvent, item: DisplayItem) => { e.stopPropagation(); setOpen(false); switch (activeTab) { case "llm": onEditLLM(item.config as NewLLMConfigPublic | GlobalNewLLMConfig, item.isGlobal); break; case "image": onEditImage?.(item.config as ImageGenerationConfig | GlobalImageGenConfig, item.isGlobal); break; case "vision": onEditVision?.(item.config as VisionLLMConfig | GlobalVisionLLMConfig, item.isGlobal); break; } }, [activeTab, onEditLLM, onEditImage, onEditVision] ); // ─── Keyboard navigation ─── // biome-ignore lint/correctness/useExhaustiveDependencies: searchQuery and selectedProvider are intentional triggers to reset focus useEffect(() => { setFocusedIndex(-1); }, [searchQuery, selectedProvider]); useEffect(() => { if (focusedIndex < 0 || !modelListRef.current) return; const items = modelListRef.current.querySelectorAll("[data-model-index]"); items[focusedIndex]?.scrollIntoView({ block: "nearest", behavior: "smooth", }); }, [focusedIndex]); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { const count = currentDisplayItems.length; // Arrow Left/Right cycle provider filters if (e.key === "ArrowLeft" || e.key === "ArrowRight") { e.preventDefault(); const providers = activeProviders; const idx = providers.indexOf(selectedProvider); let next: number; if (e.key === "ArrowLeft") { next = idx > 0 ? idx - 1 : providers.length - 1; } else { next = idx < providers.length - 1 ? idx + 1 : 0; } setSelectedProvider(providers[next]); if (providerSidebarRef.current) { const buttons = providerSidebarRef.current.querySelectorAll("button"); buttons[next]?.scrollIntoView({ block: "nearest", inline: "nearest", behavior: "smooth", }); } return; } if (count === 0) return; switch (e.key) { case "ArrowDown": e.preventDefault(); setFocusedIndex((prev) => (prev < count - 1 ? prev + 1 : 0)); break; case "ArrowUp": e.preventDefault(); setFocusedIndex((prev) => (prev > 0 ? prev - 1 : count - 1)); break; case "Enter": e.preventDefault(); if (focusedIndex >= 0 && focusedIndex < count) { handleSelectItem(currentDisplayItems[focusedIndex]); } break; case "Home": e.preventDefault(); setFocusedIndex(0); break; case "End": e.preventDefault(); setFocusedIndex(count - 1); break; } }, [currentDisplayItems, focusedIndex, activeProviders, selectedProvider, handleSelectItem] ); // ─── Render: Provider sidebar ─── const renderProviderSidebar = () => { const configuredCount = configuredProviderSet.size; return (
{!isMobile && sidebarScrollPos !== "top" && (
)} {isMobile && sidebarScrollPos !== "top" && (
)}
{activeProviders.map((provider, idx) => { const isAll = provider === "all"; const isActive = selectedProvider === provider; const count = providerModelCounts[provider] || 0; const isConfigured = isAll || configuredProviderSet.has(provider); // Separator between configured and unconfigured providers // idx 0 is "all", configured run from 1..configuredCount, unconfigured start at configuredCount+1 const showSeparator = !isAll && idx === configuredCount + 1 && configuredCount > 0; return ( {showSeparator && (isMobile ? (
) : (
))} {isAll ? "All Models" : formatProviderName(provider)} {isConfigured ? ` (${count})` : " (not configured)"} ); })}
{!isMobile && sidebarScrollPos !== "bottom" && (
)} {isMobile && sidebarScrollPos !== "bottom" && (
)}
); }; // ─── Render: Model card ─── const getSelectedId = () => { switch (activeTab) { case "llm": return currentLLMConfig?.id; case "image": return currentImageConfig?.id; case "vision": return currentVisionConfig?.id; } }; const renderModelCard = (item: DisplayItem, index: number) => { const { config, isAutoMode } = item; const isSelected = getSelectedId() === config.id; const isFocused = focusedIndex === index; const hasCitations = "citations_enabled" in config && !!config.citations_enabled; return (
handleSelectItem(item)} onKeyDown={ isMobile ? undefined : (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handleSelectItem(item); } } } onMouseEnter={() => setFocusedIndex(index)} className={cn( "group flex items-center gap-2.5 px-3 py-2 rounded-xl cursor-pointer", "transition-all duration-150 mx-2", "hover:bg-accent/40", isSelected && "bg-primary/6 dark:bg-primary/8", isFocused && "bg-accent/50" )} > {/* Provider icon */}
{getProviderIcon(config.provider as string, { isAutoMode, className: "size-5", })}
{/* Model info */}
{config.name} {isAutoMode && ( Recommended )} {"is_premium" in config && (config as Record).is_premium ? ( Premium ) : "is_premium" in config && !(config as Record).is_premium && !isAutoMode ? ( Free ) : null}
{isAutoMode ? "Auto Mode" : (config.model_name as string)} {!isAutoMode && hasCitations && ( Citations )}
{/* Actions */}
{!isAutoMode && ( )} {isSelected && }
); }; // ─── Render: Full content ─── const renderContent = () => { const globalItems = currentDisplayItems.filter((i) => i.isGlobal); const userItems = currentDisplayItems.filter((i) => !i.isGlobal); const globalStartIdx = 0; const userStartIdx = globalItems.length; const addHandler = activeTab === "llm" ? onAddNewLLM : activeTab === "image" ? onAddNewImage : onAddNewVision; const addLabel = activeTab === "llm" ? "Add Model" : activeTab === "image" ? "Add Image Model" : "Add Vision Model"; return (
{/* Tab header */}
{( [ { value: "llm" as const, icon: Zap, label: "LLM", }, { value: "image" as const, icon: ImageIcon, label: "Image", }, { value: "vision" as const, icon: ScanEye, label: "Vision", }, ] as const ).map(({ value, icon: Icon, label }) => ( ))}
{/* Two-pane layout */}
{/* Provider sidebar */} {renderProviderSidebar()} {/* Main content */}
{/* Search */}
setSearchQuery(e.target.value)} onKeyDown={isMobile ? undefined : handleKeyDown} role="combobox" aria-expanded={true} aria-controls="model-selector-list" className={cn( "w-full pl-8 pr-3 py-2.5 text-sm bg-transparent", "focus:outline-none", "placeholder:text-muted-foreground" )} />
{/* Provider header when filtered */} {selectedProvider !== "all" && (
{getProviderIcon(selectedProvider, { className: "size-4", })} {formatProviderName(selectedProvider)} {configuredProviderSet.has(selectedProvider) ? `${providerModelCounts[selectedProvider] || 0} models` : "Not configured"}
)} {/* Model list */}
{currentDisplayItems.length === 0 ? (
{selectedProvider !== "all" && !configuredProviderSet.has(selectedProvider) ? ( <>
{getProviderIcon(selectedProvider, { className: "size-10", })}

No {formatProviderName(selectedProvider)} models configured

Add a model with this provider to get started

{addHandler && ( )} ) : searchQuery ? ( <>

No models found

Try a different search term

) : ( <>

No models configured

Configure models in your search space settings

)}
) : ( <> {globalItems.length > 0 && ( <>
Global Models
{globalItems.map((item, i) => renderModelCard(item, globalStartIdx + i))} )} {globalItems.length > 0 && userItems.length > 0 && (
)} {userItems.length > 0 && ( <>
Your Configurations
{userItems.map((item, i) => renderModelCard(item, userStartIdx + i))} )} )}
{/* Add model button */} {addHandler && (
)}
); }; // ─── Trigger button ─── const triggerButton = ( ); // ─── Shell: Drawer on mobile, Popover on desktop ─── if (isMobile) { return ( {triggerButton} Select Model
{renderContent()}
); } return ( {triggerButton} e.preventDefault()} > {renderContent()} ); }