import { useState, useRef, useEffect } from 'react'; import { ChevronDown, Globe } from 'lucide-react'; import type { LLMProvider } from '../../../../types/app'; import SessionProviderLogo from '../../../llm-logo-provider/SessionProviderLogo'; type ModelOption = { value: string; label: string }; interface ModelSelectorBarProps { provider: LLMProvider; claudeModel: string; setClaudeModel: (model: string) => void; cursorModel: string; setCursorModel: (model: string) => void; codexModel: string; setCodexModel: (model: string) => void; geminiModel: string; setGeminiModel: (model: string) => void; claudeModelOptions: ModelOption[]; codexModelOptions: ModelOption[]; geminiModelOptions: ModelOption[]; cursorModelOptions: ModelOption[]; } function useCurrentModel( provider: LLMProvider, claudeModel: string, cursorModel: string, codexModel: string, geminiModel: string, claudeModelOptions: ModelOption[], codexModelOptions: ModelOption[], geminiModelOptions: ModelOption[], cursorModelOptions: ModelOption[], ): { value: string; label: string; options: ModelOption[] } { const map: Record = { claude: { value: claudeModel, options: claudeModelOptions }, cursor: { value: cursorModel, options: cursorModelOptions }, codex: { value: codexModel, options: codexModelOptions }, gemini: { value: geminiModel, options: geminiModelOptions }, }; const { value, options } = map[provider] ?? map.claude; const label = options.find((o) => o.value === value)?.label ?? value; return { value, label, options }; } export default function ModelSelectorBar({ provider, claudeModel, setClaudeModel, cursorModel, setCursorModel, codexModel, setCodexModel, geminiModel, setGeminiModel, claudeModelOptions, codexModelOptions, geminiModelOptions, cursorModelOptions, }: ModelSelectorBarProps) { const [open, setOpen] = useState(false); const [search, setSearch] = useState(''); const dropdownRef = useRef(null); const searchRef = useRef(null); const { value, label, options } = useCurrentModel( provider, claudeModel, cursorModel, codexModel, geminiModel, claudeModelOptions, codexModelOptions, geminiModelOptions, cursorModelOptions, ); const filtered = search.trim() ? options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase())) : options; // Close on outside click useEffect(() => { if (!open) return; const handler = (e: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { setOpen(false); setSearch(''); } }; document.addEventListener('mousedown', handler); return () => document.removeEventListener('mousedown', handler); }, [open]); useEffect(() => { if (open) setTimeout(() => searchRef.current?.focus(), 50); }, [open]); const handleSelect = (modelValue: string) => { const setters: Record void> = { claude: setClaudeModel, cursor: setCursorModel, codex: setCodexModel, gemini: setGeminiModel, }; setters[provider]?.(modelValue); localStorage.setItem(`${provider}-model`, modelValue); setOpen(false); setSearch(''); }; return (
{/* Left: provider label */}
{provider}
{/* Right: model picker */}
{open && (
setSearch(e.target.value)} placeholder="Search models..." className="w-full rounded-lg bg-muted/50 px-2.5 py-1.5 text-xs text-foreground placeholder:text-muted-foreground/50 outline-none border border-border/40 focus:border-border" />
{filtered.length === 0 ? (

No models found

) : ( filtered.map((opt) => ( )) )}
)}
); }