This commit is contained in:
2026-05-14 19:24:50 +00:00
parent af0628867f
commit a7f218e182
63 changed files with 10539 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
import { useEffect, useState } from 'react';
import { Check, ChevronDown } from 'lucide-react';
import { api } from '@/api/client';
import type { ModelInfo } from '@/api/types';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
interface Props {
value: string;
onChange: (model: string) => void | Promise<void>;
}
export function ModelPicker({ value, onChange }: Props) {
const [models, setModels] = useState<ModelInfo[] | null>(null);
const [error, setError] = useState<string | null>(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]);
return (
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild>
<button
type="button"
className="text-xs font-mono text-muted-foreground hover:text-foreground flex items-center gap-1 px-1.5 py-0.5 rounded hover:bg-muted/60"
>
{value}
<ChevronDown className="size-3 opacity-70" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="max-h-72 overflow-y-auto">
{error && (
<div className="px-2 py-1.5 text-xs text-destructive">{error}</div>
)}
{models === null && !error && (
<div className="px-2 py-1.5 text-xs text-muted-foreground">Loading</div>
)}
{models?.map((m) => (
<DropdownMenuItem
key={m.id}
onSelect={() => void onChange(m.id)}
className="font-mono text-xs"
>
<Check
className={`size-3 ${m.id === value ? 'opacity-100' : 'opacity-0'}`}
/>
{m.id}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}