{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "model-selector",
  "title": "Model Selector",
  "description": "A model picker as a radio list or segmented control, with badges and disabled states.",
  "files": [
    {
      "path": "components/spectrumui/blocks/ai-assistants/model-selector.tsx",
      "content": "'use client';\n\nimport { useState } from 'react';\nimport { cn } from '@/lib/utils';\nimport type { ModelOption } from './types';\n\nexport type ModelSelectorVariant = 'List' | 'Segmented';\n\nexport interface ModelSelectorProps {\n  models: ModelOption[];\n  value?: string;\n  onChange?: (id: string) => void;\n  variant?: ModelSelectorVariant;\n  className?: string;\n}\n\nexport function ModelSelector({\n  models,\n  value: valueProp,\n  onChange,\n  variant = 'List',\n  className,\n}: ModelSelectorProps) {\n  const [valueState, setValueState] = useState(models.find((m) => !m.disabled)?.id);\n  const value = valueProp !== undefined ? valueProp : valueState;\n\n  function select(id: string) {\n    setValueState(id);\n    onChange?.(id);\n  }\n\n  if (variant === 'Segmented') {\n    const enabled = models.filter((model) => !model.disabled);\n    const activeIndex = Math.max(0, enabled.findIndex((model) => model.id === value));\n    return (\n      <div\n        role=\"radiogroup\"\n        aria-label=\"Model\"\n        className={cn(\n          'relative isolate grid w-fit grid-flow-col auto-cols-fr rounded-lg bg-black/[0.04] p-0.5 dark:bg-white/[0.05]',\n          className,\n        )}\n      >\n        <span\n          aria-hidden\n          className=\"absolute inset-y-0.5 left-0.5 -z-10 rounded-[7px] bg-white shadow-xs transition-transform duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] dark:bg-neutral-800\"\n          style={{\n            width: `calc((100% - 4px) / ${enabled.length})`,\n            transform: `translateX(${activeIndex * 100}%)`,\n          }}\n        />\n        {enabled.map((model) => {\n          const active = model.id === value;\n          return (\n            <button\n              key={model.id}\n              type=\"button\"\n              role=\"radio\"\n              aria-checked={active}\n              onClick={() => select(model.id)}\n              className={cn(\n                'whitespace-nowrap rounded-[7px] px-3 py-1.5 font-mono text-[11.5px] transition-[color,transform] duration-150 active:scale-[0.96]',\n                'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-neutral-400',\n                active\n                  ? 'font-medium text-neutral-900 dark:text-neutral-50'\n                  : 'text-neutral-500 hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200',\n              )}\n            >\n              {model.name}\n            </button>\n          );\n        })}\n      </div>\n    );\n  }\n\n  return (\n    <div role=\"radiogroup\" aria-label=\"Model\" className={cn('w-full max-w-[380px] space-y-1.5', className)}>\n      {models.map((model) => {\n        const active = model.id === value;\n        return (\n          <button\n            key={model.id}\n            type=\"button\"\n            role=\"radio\"\n            aria-checked={active}\n            disabled={model.disabled}\n            onClick={() => select(model.id)}\n            className={cn(\n              'flex w-full items-center gap-3 rounded-xl border px-3 py-2.5 text-left',\n              'transition-[border-color,background-color,transform] duration-150',\n              'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-neutral-400',\n              model.disabled\n                ? 'cursor-not-allowed border-black/[0.05] opacity-45 dark:border-white/[0.06]'\n                : active\n                  ? 'border-black/[0.2] bg-black/[0.02] dark:border-white/[0.28] dark:bg-white/[0.04]'\n                  : 'border-black/[0.07] hover:border-black/[0.14] active:scale-[0.99] dark:border-white/[0.08] dark:hover:border-white/[0.16]',\n            )}\n          >\n            <span\n              aria-hidden\n              className={cn(\n                'grid size-4 shrink-0 place-items-center rounded-full border transition-colors duration-150',\n                active\n                  ? 'border-neutral-900 dark:border-neutral-100'\n                  : 'border-neutral-300 dark:border-neutral-600',\n              )}\n            >\n              <span\n                className={cn(\n                  'size-2 rounded-full bg-neutral-900 transition-transform duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] dark:bg-neutral-100',\n                  active ? 'scale-100' : 'scale-0',\n                )}\n              />\n            </span>\n\n            <span className=\"min-w-0 flex-1\">\n              <span className=\"block truncate text-[13px] font-medium text-neutral-900 dark:text-neutral-100\">\n                {model.name}\n              </span>\n              {model.description && (\n                <span className=\"block truncate text-[11.5px] text-neutral-500 dark:text-neutral-500\">\n                  {model.description}\n                </span>\n              )}\n            </span>\n\n            {model.badge && (\n              <span className=\"shrink-0 rounded-md bg-black/[0.05] px-1.5 py-0.5 font-mono text-[9.5px] font-medium uppercase tracking-wide text-neutral-500 dark:bg-white/[0.08] dark:text-neutral-400\">\n                {model.badge}\n              </span>\n            )}\n          </button>\n        );\n      })}\n    </div>\n  );\n}\n\nexport default ModelSelector;\n",
      "type": "registry:block",
      "target": "components/spectrumui/blocks/ai-assistants/model-selector.tsx"
    },
    {
      "path": "components/spectrumui/blocks/ai-assistants/types.ts",
      "content": "export type MessageRole = 'user' | 'assistant' | 'system';\n\nexport type MessageState = 'streaming' | 'complete' | 'error';\n\nexport interface Attachment {\n  id: string;\n  name: string;\n  size: number;\n  type: string;\n  url?: string;\n  previewUrl?: string;\n}\n\nexport interface Citation {\n  id: string;\n  index: number;\n  url: string;\n  title: string;\n  snippet?: string;\n  favicon?: string;\n}\n\nexport type ToolCallStatus = 'pending' | 'running' | 'success' | 'error' | 'cancelled';\n\nexport interface ToolCall {\n  id: string;\n  name: string;\n  args?: Record<string, unknown>;\n  result?: string;\n  status: ToolCallStatus;\n  startedAt?: number;\n  completedAt?: number;\n  children?: ToolCall[];\n  parallel?: boolean;\n}\n\nexport interface ReasoningStep {\n  id: string;\n  content: string;\n}\n\nexport interface Reasoning {\n  steps: ReasoningStep[];\n  status: 'thinking' | 'complete';\n  durationMs?: number;\n}\n\nexport interface Message {\n  id: string;\n  role: MessageRole;\n  content: string;\n  createdAt?: number;\n  state?: MessageState;\n  attachments?: Attachment[];\n  citations?: Citation[];\n  toolCalls?: ToolCall[];\n  reasoning?: Reasoning;\n}\n\nexport interface Conversation {\n  id: string;\n  label: string;\n}\n\nexport interface ModelOption {\n  id: string;\n  name: string;\n  description?: string;\n  badge?: string;\n  disabled?: boolean;\n}\n\nexport interface SuggestedPrompt {\n  id: string;\n  label: string;\n  prompt?: string;\n}\n\nexport type MessageFeedback = 'positive' | 'negative';\n\nexport interface UsageState {\n  promptTokens: number;\n  completionTokens: number;\n  contextWindow: number;\n  estimatedCostUsd?: number;\n}\n",
      "type": "registry:file",
      "target": "components/spectrumui/blocks/ai-assistants/types.ts"
    }
  ],
  "type": "registry:block"
}
