import { useEffect, useRef, useState } from 'react'; import { ChevronDown, Sparkles } from 'lucide-react'; import { findModel, inferProvider, modelCatalog, providerMeta, type ModelDefinition, } from '@shared/domain/models'; import { reasoningEffortOptions, type ReasoningEffort } from '@shared/domain/pattern'; import { ProviderIcon } from './ProviderIcons'; function TierBadge({ tier }: { tier: ModelDefinition['tier'] }) { if (!tier) { return null; } const styles = { premium: 'bg-amber-500/10 text-amber-400', standard: 'bg-zinc-700/50 text-zinc-500', fast: 'bg-emerald-500/10 text-emerald-400', }; return ( {tier} ); } interface ModelSelectProps { value: string; onChange: (model: string) => void; models?: ReadonlyArray; label?: string; disabled?: boolean; } export function ModelSelect({ value, onChange, models = modelCatalog, label = 'Model', disabled = false, }: ModelSelectProps) { const [open, setOpen] = useState(false); const containerRef = useRef(null); useEffect(() => { if (!open) { return; } function handleClick(event: MouseEvent) { if (containerRef.current && !containerRef.current.contains(event.target as Node)) { setOpen(false); } } document.addEventListener('mousedown', handleClick); return () => document.removeEventListener('mousedown', handleClick); }, [open]); const selected = findModel(value, models); const provider = selected?.provider ?? inferProvider(value); const groupedModels = providerMeta .map((providerGroup) => ({ ...providerGroup, models: models.filter((model) => model.provider === providerGroup.id), })) .filter((providerGroup) => providerGroup.models.length > 0); const otherModels = models.filter((model) => !model.provider); return ( {label} setOpen((current) => !current)} type="button" > {provider && } {selected?.name ?? (value || 'Select model')} {open && !disabled && ( {groupedModels.map((providerGroup) => { return ( {providerGroup.label} {providerGroup.models.map((model) => ( { onChange(model.id); setOpen(false); }} type="button" > {model.name} ))} ); })} {otherModels.length > 0 && ( Other {otherModels.map((model) => ( { onChange(model.id); setOpen(false); }} type="button" > {model.name} ))} )} )} ); } interface ReasoningEffortSelectProps { value?: ReasoningEffort; onChange: (value: ReasoningEffort) => void; supportedEfforts?: ReadonlyArray; label?: string; disabled?: boolean; } export function ReasoningEffortSelect({ value, onChange, supportedEfforts, label = 'Reasoning', disabled = false, }: ReasoningEffortSelectProps) { const options = supportedEfforts ? reasoningEffortOptions.filter((option) => supportedEfforts.includes(option.value)) : [...reasoningEffortOptions]; const selectedValue = value && options.some((option) => option.value === value) ? value : options[0]?.value; if (supportedEfforts && supportedEfforts.length === 0) { return ( {label} ); } return ( {label} onChange(event.target.value as ReasoningEffort)} value={selectedValue} > {options.map((option) => ( {option.label} ))} ); }