ModelSelector
Cursor-style model picker with optional download flow per row, capability badges, provider tags, RAM-based disabling.
Inspired by Cursor model picker, t3.chat multi-provider dropdown, OpenRouter.
What it looks like.
Live — the real component, scoped to this frame.
Drop it into your app.
import { ModelSelector } from '@locara/components'
<ModelSelector
value={modelId}
onChange={setModelId}
onDownload={(id) => models.download(id)}
options={[
{ id: 'qwen2.5-7b', label: 'Qwen 2.5 7B', hint: '4.9 GB · Q4_K_M', provider: 'Local',
badges: ['fast'], status: 'ready' },
{ id: 'llama-3-8b', label: 'Llama 3 8B', hint: '4.9 GB · Q4_K_M', provider: 'Local',
status: 'ready' },
{ id: 'llama-3-70b', label: 'Llama 3 70B', hint: '42.8 GB · Q4_K_M', provider: 'Local',
badges: ['heavy'], status: 'needs-download', sizeLabel: '42.8 GB' },
{ id: 'llama-3-405b', label: 'Llama 3.1 405B', hint: '248 GB · Q4_K_M',
disabled: true, disabledReason: 'Needs 256 GB+ Mac Studio' },
]}
/>
Or copy the source into your repo:
locara add model-selector
Why it works this way.
Per-row download is the differentiator vs a generic dropdown. The consumer owns the actual download lifecycle: on click, you call into your SDK, then update the option's `status` to `'downloading'` and stream `progress` (0..1). Once verification finishes, flip to `'ready'`. Rows marked `disabled` with a `disabledReason` are visible but un-selectable — useful for surfacing 'this model needs more RAM than you have' instead of silently hiding it.
The API surface.
| Name | Type | Description |
|---|---|---|
options required | ModelOption[] | The available models, with status and metadata. |
value required | string | Currently selected model id. |
onChange required | (id: string) => void | Called when the user picks a model. |
onDownload | (id: string) => void | Wired to your model-download SDK; enables the per-row Download button. |
size | 'sm' | 'md' | Trigger height. Default md. |
prefix | string | Label prefix on the trigger. Default "Model". |