Spaces:
Running
Running
| import { useEffect, useState } from "react"; | |
| import { Sparkles } from "lucide-react"; | |
| import { useStore } from "../store"; | |
| import { useAiModels } from "../lib/aiKeys"; | |
| // Brand-neutral model picker: shows our own tier names (Swift / Standard / Reasoning …) — never the | |
| // underlying provider or raw model ids. In "bring your own key" mode the list comes from whatever the | |
| // user's key unlocks (via useAiModels, which sends the key headers), so the switcher just works. | |
| export function ModelSwitcher() { | |
| const model = useStore((s) => s.model); | |
| const setModel = useStore((s) => s.setModel); | |
| const { models: all, defaults } = useAiModels(); | |
| const models = all.filter((m) => m.role === "chat"); | |
| const [open, setOpen] = useState(false); | |
| // keep the selected model valid: if the current one isn't in the usable list (e.g. after connecting a | |
| // different provider's key, or in a no-server-key demo), fall back to the resolved default / first model. | |
| useEffect(() => { | |
| if (!models.length) return; | |
| if (!models.some((m) => m.id === model)) setModel(defaults.chat || models[0].id); | |
| }, [models, defaults.chat, model, setModel]); | |
| const cur = models.find((m) => m.id === model); | |
| return ( | |
| <div className="relative"> | |
| <button | |
| onClick={() => setOpen((o) => !o)} | |
| className="flex items-center gap-1.5 rounded px-2 py-1 text-xs font-medium text-fg hover:bg-white/5" | |
| title="Switch AI model" | |
| > | |
| <Sparkles size={12} className="text-primary" /> | |
| {cur?.label ?? "Model"} | |
| <span className="text-fg-subtle">â–¾</span> | |
| </button> | |
| {open && ( | |
| <> | |
| <div className="fixed inset-0 z-10" onClick={() => setOpen(false)} /> | |
| <div className="absolute bottom-full z-20 mb-1 max-h-80 w-60 overflow-y-auto rounded-lg border border-border bg-surface-raised p-1 shadow-lg"> | |
| <div className="px-2 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-fg-muted">AI model</div> | |
| {models.length === 0 && ( | |
| <div className="px-2 py-2 text-[12px] text-fg-muted">No model available — connect an AI key.</div> | |
| )} | |
| {models.map((m) => ( | |
| <button | |
| key={m.id} | |
| onClick={() => { setModel(m.id); setOpen(false); }} | |
| className={"flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-[13px] text-fg hover:bg-primary/10 " + (m.id === model ? "bg-primary/15 font-medium" : "")} | |
| > | |
| <span className="flex-1 truncate">{m.label}</span> | |
| {m.reasoning && <span className="rounded bg-white/10 px-1 text-[10px] text-fg-muted">reasoning</span>} | |
| </button> | |
| ))} | |
| </div> | |
| </> | |
| )} | |
| </div> | |
| ); | |
| } | |