()
| 17 | * different agent name on the next render. |
| 18 | */ |
| 19 | export function ModelPicker() { |
| 20 | const { agentName, setAgent, models } = useChatState(); |
| 21 | |
| 22 | const grouped = models.reduce<Record<string, ChatModelOption[]>>( |
| 23 | (acc, m) => { |
| 24 | (acc[m.group] ??= []).push(m); |
| 25 | return acc; |
| 26 | }, |
| 27 | {}, |
| 28 | ); |
| 29 | |
| 30 | return ( |
| 31 | <DropdownMenu> |
| 32 | <DropdownMenuTrigger asChild> |
| 33 | <Button |
| 34 | variant="ghost" |
| 35 | size="sm" |
| 36 | className="h-7 px-2 text-xs gap-1 text-muted-foreground" |
| 37 | aria-label="Select model" |
| 38 | title="Select model" |
| 39 | > |
| 40 | <span className="truncate max-w-[140px]"> |
| 41 | {getModelLabel(agentName)} |
| 42 | </span> |
| 43 | <ChevronDownIcon className="size-3" /> |
| 44 | </Button> |
| 45 | </DropdownMenuTrigger> |
| 46 | <DropdownMenuContent align="end" className="w-56"> |
| 47 | {Object.entries(grouped).map(([group, items], idx) => ( |
| 48 | <div key={group}> |
| 49 | {idx > 0 && <DropdownMenuSeparator />} |
| 50 | <DropdownMenuLabel className="text-[11px] uppercase tracking-wide text-muted-foreground"> |
| 51 | {group} |
| 52 | </DropdownMenuLabel> |
| 53 | {items.map((m) => ( |
| 54 | <DropdownMenuItem |
| 55 | key={m.id} |
| 56 | onSelect={() => setAgent(m.id)} |
| 57 | className="flex items-center justify-between gap-2" |
| 58 | > |
| 59 | <span className="truncate">{m.label}</span> |
| 60 | {agentName === m.id && ( |
| 61 | <CheckIcon className="size-3 shrink-0 text-foreground" /> |
| 62 | )} |
| 63 | </DropdownMenuItem> |
| 64 | ))} |
| 65 | </div> |
| 66 | ))} |
| 67 | </DropdownMenuContent> |
| 68 | </DropdownMenu> |
| 69 | ); |
| 70 | } |
nothing calls this directly
no test coverage detected