import { useEffect, useState } from 'react'; import { Check, ChevronDown, Cpu } from 'lucide-react'; import { api } from '@/api/client'; import type { ModelInfo } from '@/api/types'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { BottomSheet } from '@/components/BottomSheet'; import { useViewport } from '@/hooks/useViewport'; interface Props { value: string; onChange: (model: string) => void | Promise; } // v1.9: shared list rendered inside both shells. Lazy-fetches /api/models on // first open so the picker doesn't pay for a request when it's never shown. function ModelList({ models, error, value, onPick, }: { models: ModelInfo[] | null; error: string | null; value: string; onPick: (id: string) => void; }) { if (error) { return
{error}
; } if (models === null) { return
Loading…
; } return ( <> {models.map((m) => ( ))} ); } export function ModelPicker({ value, onChange }: Props) { const { isMobile } = useViewport(); const [models, setModels] = useState(null); const [error, setError] = useState(null); const [open, setOpen] = useState(false); useEffect(() => { if (!open || models !== null) return; api .models() .then(setModels) .catch((err) => setError(err instanceof Error ? err.message : 'failed to load models'), ); }, [open, models]); function handlePick(id: string) { setOpen(false); void onChange(id); } // v1.9: mobile = icon-only trigger + bottom-sheet shell. Desktop = labeled // trigger (model name + chevron) + dropdown. Same ModelList under the hood. if (isMobile) { return ( <> setOpen(false)} title="Model">
); } return ( {error && (
{error}
)} {models === null && !error && (
Loading…
)} {models?.map((m) => ( handlePick(m.id)} className="font-mono text-xs" > {m.id} ))}
); }