Model picker
Rows that describe the job rather than the architecture: what it is for, what it costs relative to the cheap one, how much context it holds and what it accepts. Switching mid-conversation leaves a marker, because the turns above came from something else.
npx shadcn@latest add dropdown-menu button
npm i lucide-react
Tokens this needs: --card, --card-foreground, --popover, --popover-foreground, --accent, --accent-foreground, --muted, --muted-foreground, --border, --chart-1
────────────────────────────────────────────────────────────────────────
// ModelPicker.tsx
────────────────────────────────────────────────────────────────────────
import { useState } from "react";
import { Check, ChevronDown } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
/**
* Each row describes the job, not the architecture. A parameter count and a
* point release tell a chooser nothing they can act on; "analysis and code"
* and "8×" do. Cost, window and modality sit in the list because they change
* which tasks are possible, and hiding them makes the menu decor.
*/
export const MODELS = [
{ id: "fast", name: "Fast", job: "everyday questions", cost: "1×", meta: "text only" },
{ id: "careful", name: "Careful", job: "analysis and code", cost: "8×", meta: "text, images" },
{ id: "long", name: "Long context", job: "whole documents", cost: "6×", meta: "1 million tokens" },
] as const;
export type ModelId = (typeof MODELS)[number]["id"];
type Props = {
/** "auto" lets the system route by the question. Most people never open
* this menu, so auto and its routing decide the experience. */
value: ModelId | "auto";
/** What auto picked for the current turn, so the choice is visible and can
* be overridden rather than silent. */
routed?: ModelId;
onChange: (id: ModelId | "auto") => void;
/** Start open. Used when the picker is the thing on screen, as in a demo. */
defaultOpen?: boolean;
};
export function ModelPicker({ value, routed, onChange, defaultOpen = false }: Props) {
const [open, setOpen] = useState(defaultOpen);
const current = MODELS.find((m) => m.id === value);
const picked = MODELS.find((m) => m.id === routed);
return (
// Non-modal: a picker beside a composer should not lock the page behind it.
earlier turns came from another model