import type { LargeLanguageModel } from "@/lib/schemas"; import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, } from "@/components/ui/dropdown-menu"; import { useEffect, useState } from "react"; import { useLocalModels } from "@/hooks/useLocalModels"; import { useLocalLMSModels } from "@/hooks/useLMStudioModels"; import { useLanguageModelsByProviders } from "@/hooks/useLanguageModelsByProviders"; import { ChevronDown } from "lucide-react"; import { LocalModel } from "@/ipc/ipc_types"; import { useLanguageModelProviders } from "@/hooks/useLanguageModelProviders"; interface ModelPickerProps { selectedModel: LargeLanguageModel; onModelSelect: (model: LargeLanguageModel) => void; } export function ModelPicker({ selectedModel, onModelSelect, }: ModelPickerProps) { const [open, setOpen] = useState(false); // Cloud models from providers const { data: modelsByProviders, isLoading: modelsByProvidersLoading } = useLanguageModelsByProviders(); const { data: providers, isLoading: providersLoading } = useLanguageModelProviders(); const loading = modelsByProvidersLoading || providersLoading; // Ollama Models Hook const { models: ollamaModels, loading: ollamaLoading, error: ollamaError, loadModels: loadOllamaModels, } = useLocalModels(); // LM Studio Models Hook const { models: lmStudioModels, loading: lmStudioLoading, error: lmStudioError, loadModels: loadLMStudioModels, } = useLocalLMSModels(); // Load models when the dropdown opens useEffect(() => { if (open) { loadOllamaModels(); loadLMStudioModels(); } }, [open, loadOllamaModels, loadLMStudioModels]); // Get display name for the selected model const getModelDisplayName = () => { if (selectedModel.provider === "ollama") { return ( ollamaModels.find( (model: LocalModel) => model.modelName === selectedModel.name, )?.displayName || selectedModel.name ); } if (selectedModel.provider === "lmstudio") { return ( lmStudioModels.find( (model: LocalModel) => model.modelName === selectedModel.name, )?.displayName || selectedModel.name // Fallback to path if not found ); } // For cloud models, look up in the modelsByProviders data if (modelsByProviders && modelsByProviders[selectedModel.provider]) { const foundModel = modelsByProviders[selectedModel.provider].find( (model) => model.apiName === selectedModel.name, ); if (foundModel) { return foundModel.displayName; } } // Fallback if not found return selectedModel.name; }; const modelDisplayName = getModelDisplayName(); // Get auto provider models (if any) const autoModels = !loading && modelsByProviders && modelsByProviders["auto"] ? modelsByProviders["auto"] : []; // Determine availability of local models const hasOllamaModels = !ollamaLoading && !ollamaError && ollamaModels.length > 0; const hasLMStudioModels = !lmStudioLoading && !lmStudioError && lmStudioModels.length > 0; return ( Cloud Models {/* Cloud models - loading state */} {loading ? (
Loading models...
) : !modelsByProviders || Object.keys(modelsByProviders).length === 0 ? (
No cloud models available
) : ( /* Cloud models loaded */ <> {/* Auto models at top level if any */} {autoModels.length > 0 && ( <> {autoModels.map((model) => ( { onModelSelect({ name: model.apiName, provider: "auto", }); setOpen(false); }} >
{model.displayName} auto {model.tag && ( {model.tag} )}
{model.description}
))} {Object.keys(modelsByProviders).length > 1 && ( )} )} {/* Group other providers into submenus */} {Object.entries(modelsByProviders).map(([providerId, models]) => { // Skip auto provider as it's already handled if (providerId === "auto") return null; const provider = providers?.find((p) => p.id === providerId); if (models.length === 0) return null; return (
{provider?.name} {models.length} models
{provider?.name} Models {models.map((model) => ( { onModelSelect({ name: model.apiName, provider: providerId, }); setOpen(false); }} >
{model.displayName} {model.tag && ( {model.tag} )}
{model.description}
))}
); })} )} {/* Local Models Parent SubMenu */}
Local models LM Studio, Ollama
{/* Ollama Models SubMenu */}
Ollama {ollamaLoading ? ( Loading... ) : ollamaError ? ( Error loading ) : !hasOllamaModels ? ( None available ) : ( {ollamaModels.length} models )}
Ollama Models {ollamaLoading && ollamaModels.length === 0 ? ( // Show loading only if no models are loaded yet
Loading models...
) : ollamaError ? (
Error loading models Is Ollama running?
) : !hasOllamaModels ? (
No local models found Ensure Ollama is running and models are pulled.
) : ( ollamaModels.map((model: LocalModel) => ( { onModelSelect({ name: model.modelName, provider: "ollama", }); setOpen(false); }} >
{model.displayName} {model.modelName}
)) )}
{/* LM Studio Models SubMenu */}
LM Studio {lmStudioLoading ? ( Loading... ) : lmStudioError ? ( Error loading ) : !hasLMStudioModels ? ( None available ) : ( {lmStudioModels.length} models )}
LM Studio Models {lmStudioLoading && lmStudioModels.length === 0 ? ( // Show loading only if no models are loaded yet
Loading models...
) : lmStudioError ? (
Error loading models {lmStudioError.message} {/* Display specific error */}
) : !hasLMStudioModels ? (
No loaded models found Ensure LM Studio is running and models are loaded.
) : ( lmStudioModels.map((model: LocalModel) => ( { onModelSelect({ name: model.modelName, provider: "lmstudio", }); setOpen(false); }} >
{/* Display the user-friendly name */} {model.displayName} {/* Show the path as secondary info */} {model.modelName}
)) )}
); }