{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "model-selector",
  "title": "AI Model Selector",
  "description": "A functional AI model selector with provider logos and popular models from Vercel AI Gateway. Features searchable combobox, provider icons, and model grouping.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "command",
    "popover",
    "openai-logo",
    "anthropic-logo",
    "gemini-logo",
    "xai-logo",
    "deepseek-logo",
    "mistral-logo",
    "groq-logo",
    "cohere-logo",
    "meta-logo"
  ],
  "files": [
    {
      "path": "registry/default/blocks/ai/ai-model-selector/components/elements/ai-model-selector.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { Check, ChevronDown } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport { AnthropicLogo } from \"@/components/ui/logos/anthropic\";\nimport { CohereLogo } from \"@/components/ui/logos/cohere\";\nimport { DeepSeekLogo } from \"@/components/ui/logos/deepseek\";\nimport { GeminiLogo } from \"@/components/ui/logos/gemini\";\nimport { GroqLogo } from \"@/components/ui/logos/groq\";\nimport { MetaLogo } from \"@/components/ui/logos/meta\";\nimport { MistralLogo } from \"@/components/ui/logos/mistral\";\nimport { OpenAILogo } from \"@/components/ui/logos/openai\";\nimport { XAILogo } from \"@/components/ui/logos/xai\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\n\ntype Provider =\n  | \"openai\"\n  | \"anthropic\"\n  | \"google\"\n  | \"xai\"\n  | \"deepseek\"\n  | \"mistral\"\n  | \"groq\"\n  | \"cohere\"\n  | \"meta\";\n\ninterface Model {\n  id: string;\n  name: string;\n  provider: Provider;\n  description?: string;\n}\n\nconst PROVIDERS: Record<\n  Provider,\n  { name: string; logo: React.ComponentType<{ className?: string }> }\n> = {\n  openai: { name: \"OPENAI\", logo: OpenAILogo },\n  anthropic: { name: \"ANTHROPIC\", logo: AnthropicLogo },\n  google: { name: \"GOOGLE\", logo: GeminiLogo },\n  xai: { name: \"XAI\", logo: XAILogo },\n  deepseek: { name: \"DEEPSEEK\", logo: DeepSeekLogo },\n  mistral: { name: \"MISTRAL\", logo: MistralLogo },\n  groq: { name: \"GROQ\", logo: GroqLogo },\n  cohere: { name: \"COHERE\", logo: CohereLogo },\n  meta: { name: \"META\", logo: MetaLogo },\n};\n\nconst DEFAULT_MODELS: Model[] = [\n  {\n    id: \"openai/gpt-5.2\",\n    name: \"GPT-5.2\",\n    provider: \"openai\",\n    description: \"Latest flagship model\",\n  },\n  {\n    id: \"openai/gpt-5\",\n    name: \"GPT-5\",\n    provider: \"openai\",\n    description: \"Previous flagship\",\n  },\n  {\n    id: \"openai/gpt-4o\",\n    name: \"GPT-4o\",\n    provider: \"openai\",\n    description: \"Multimodal, fast\",\n  },\n  {\n    id: \"openai/gpt-4o-mini\",\n    name: \"GPT-4o Mini\",\n    provider: \"openai\",\n    description: \"Fast and affordable\",\n  },\n  {\n    id: \"openai/gpt-5-codex\",\n    name: \"GPT-5 Codex\",\n    provider: \"openai\",\n    description: \"Optimized for code\",\n  },\n  {\n    id: \"anthropic/claude-opus-4.5\",\n    name: \"Claude Opus 4.5\",\n    provider: \"anthropic\",\n    description: \"Most capable\",\n  },\n  {\n    id: \"anthropic/claude-sonnet-4.5\",\n    name: \"Claude Sonnet 4.5\",\n    provider: \"anthropic\",\n    description: \"Balanced performance\",\n  },\n  {\n    id: \"anthropic/claude-sonnet-4\",\n    name: \"Claude Sonnet 4\",\n    provider: \"anthropic\",\n    description: \"Fast and capable\",\n  },\n  {\n    id: \"anthropic/claude-haiku-4.5\",\n    name: \"Claude Haiku 4.5\",\n    provider: \"anthropic\",\n    description: \"Fast and efficient\",\n  },\n  {\n    id: \"google/gemini-2.5-pro\",\n    name: \"Gemini 2.5 Pro\",\n    provider: \"google\",\n    description: \"Most capable\",\n  },\n  {\n    id: \"google/gemini-2.5-flash\",\n    name: \"Gemini 2.5 Flash\",\n    provider: \"google\",\n    description: \"Fast and efficient\",\n  },\n  {\n    id: \"google/gemini-3-flash\",\n    name: \"Gemini 3 Flash\",\n    provider: \"google\",\n    description: \"Latest flash model\",\n  },\n  {\n    id: \"xai/grok-4.1-fast-reasoning\",\n    name: \"Grok 4.1 Reasoning\",\n    provider: \"xai\",\n    description: \"Advanced reasoning\",\n  },\n  {\n    id: \"xai/grok-code-fast-1\",\n    name: \"Grok Code Fast\",\n    provider: \"xai\",\n    description: \"Optimized for code\",\n  },\n  {\n    id: \"deepseek/deepseek-v3.2\",\n    name: \"DeepSeek V3.2\",\n    provider: \"deepseek\",\n    description: \"Latest version\",\n  },\n  {\n    id: \"deepseek/deepseek-v3.2-thinking\",\n    name: \"DeepSeek V3.2 Thinking\",\n    provider: \"deepseek\",\n    description: \"With reasoning\",\n  },\n  {\n    id: \"mistral/devstral-2\",\n    name: \"Devstral 2\",\n    provider: \"mistral\",\n    description: \"Code-focused\",\n  },\n  {\n    id: \"mistral/devstral-small-2\",\n    name: \"Devstral Small 2\",\n    provider: \"mistral\",\n    description: \"Lightweight code model\",\n  },\n  {\n    id: \"groq/llama-3.3-70b\",\n    name: \"Llama 3.3 70B\",\n    provider: \"groq\",\n    description: \"Fast inference\",\n  },\n  {\n    id: \"groq/mixtral-8x7b\",\n    name: \"Mixtral 8x7B\",\n    provider: \"groq\",\n    description: \"MoE architecture\",\n  },\n  {\n    id: \"cohere/command-r-plus\",\n    name: \"Command R+\",\n    provider: \"cohere\",\n    description: \"Enterprise RAG\",\n  },\n  {\n    id: \"meta/llama-3.3-70b\",\n    name: \"Llama 3.3 70B\",\n    provider: \"meta\",\n    description: \"Open source flagship\",\n  },\n];\n\ntype SelectorSize = \"default\" | \"compact\";\n\ninterface AiModelSelectorProps {\n  value?: string;\n  onValueChange?: (value: string) => void;\n  models?: Model[];\n  placeholder?: string;\n  className?: string;\n  disabled?: boolean;\n  size?: SelectorSize;\n}\n\nexport function AiModelSelector({\n  value,\n  onValueChange,\n  models = DEFAULT_MODELS,\n  placeholder = \"SELECT MODEL\",\n  className,\n  disabled = false,\n  size = \"default\",\n}: AiModelSelectorProps) {\n  const [open, setOpen] = React.useState(false);\n  const [internalValue, setInternalValue] = React.useState(value || \"\");\n\n  const currentValue = value !== undefined ? value : internalValue;\n  const selectedModel = models.find((model) => model.id === currentValue);\n\n  const handleSelect = (modelId: string) => {\n    const newValue = modelId === currentValue ? \"\" : modelId;\n    if (value === undefined) {\n      setInternalValue(newValue);\n    }\n    onValueChange?.(newValue);\n    setOpen(false);\n  };\n\n  const modelsByProvider = React.useMemo(() => {\n    const grouped = new Map<Provider, Model[]>();\n    for (const model of models) {\n      const existing = grouped.get(model.provider) || [];\n      grouped.set(model.provider, [...existing, model]);\n    }\n    return grouped;\n  }, [models]);\n\n  const SelectedLogo = selectedModel\n    ? PROVIDERS[selectedModel.provider]?.logo\n    : null;\n\n  const isCompact = size === \"compact\";\n\n  return (\n    <Popover open={open} onOpenChange={setOpen}>\n      <PopoverTrigger asChild>\n        <button\n          type=\"button\"\n          role=\"combobox\"\n          aria-expanded={open}\n          disabled={disabled}\n          data-slot=\"ai-model-selector\"\n          className={cn(\n            \"flex items-center justify-between border bg-background font-mono transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50\",\n            isCompact\n              ? \"h-8 w-[200px] px-2 text-xs\"\n              : \"w-[280px] px-3 py-2 text-sm\",\n            className\n          )}\n        >\n          <div className=\"flex items-center gap-2 truncate\">\n            {selectedModel && SelectedLogo ? (\n              <>\n                <SelectedLogo className={cn(\"shrink-0\", isCompact ? \"size-3.5\" : \"size-4\")} />\n                <span className=\"truncate font-medium\">{selectedModel.name}</span>\n                <span className={cn(\n                  \"uppercase tracking-wider text-muted-foreground\",\n                  isCompact ? \"text-[9px]\" : \"text-[10px]\"\n                )}>\n                  {PROVIDERS[selectedModel.provider].name}\n                </span>\n              </>\n            ) : (\n              <span className={cn(\n                \"uppercase tracking-wider text-muted-foreground\",\n                isCompact ? \"text-[9px]\" : \"text-xs\"\n              )}>\n                {placeholder}\n              </span>\n            )}\n          </div>\n          <ChevronDown className={cn(\"ml-2 shrink-0 text-muted-foreground\", isCompact ? \"size-3\" : \"size-3.5\")} />\n        </button>\n      </PopoverTrigger>\n      <PopoverContent\n        className={cn(\"rounded-none border p-0\", isCompact ? \"w-[200px]\" : \"w-[280px]\")}\n        align=\"end\"\n        sideOffset={4}\n      >\n        <Command className=\"rounded-none\">\n          <CommandInput\n            placeholder=\"Search models...\"\n            className=\"rounded-none border-none font-mono text-xs\"\n          />\n          <CommandList className=\"max-h-[300px]\">\n            <CommandEmpty className=\"py-4 text-center font-mono text-xs uppercase tracking-wider text-muted-foreground\">\n              No model found.\n            </CommandEmpty>\n            {Array.from(modelsByProvider.entries()).map(\n              ([provider, providerModels]) => {\n                const providerInfo = PROVIDERS[provider];\n                if (!providerInfo) return null;\n                const ProviderLogo = providerInfo.logo;\n\n                return (\n                  <CommandGroup\n                    key={provider}\n                    heading={\n                      <div className=\"flex items-center gap-2 font-mono text-[10px] uppercase tracking-widest\">\n                        <ProviderLogo className=\"size-3\" />\n                        <span>{providerInfo.name}</span>\n                      </div>\n                    }\n                    className=\"[&_[cmdk-group-heading]]:px-3 [&_[cmdk-group-heading]]:py-2\"\n                  >\n                    {providerModels.map((model) => (\n                      <CommandItem\n                        key={model.id}\n                        value={model.id}\n                        onSelect={() => handleSelect(model.id)}\n                        className=\"flex cursor-pointer items-center justify-between rounded-none px-3 py-2 font-mono\"\n                      >\n                        <div className=\"flex flex-col gap-0.5\">\n                          <span className=\"text-sm\">{model.name}</span>\n                          {model.description && (\n                            <span className=\"text-[10px] text-muted-foreground\">\n                              {model.description}\n                            </span>\n                          )}\n                        </div>\n                        <Check\n                          className={cn(\n                            \"size-3.5 shrink-0\",\n                            currentValue === model.id\n                              ? \"opacity-100\"\n                              : \"opacity-0\"\n                          )}\n                        />\n                      </CommandItem>\n                    ))}\n                  </CommandGroup>\n                );\n              }\n            )}\n          </CommandList>\n        </Command>\n      </PopoverContent>\n    </Popover>\n  );\n}\n\nexport { DEFAULT_MODELS, PROVIDERS };\nexport type { Model, Provider, AiModelSelectorProps, SelectorSize };\n",
      "type": "registry:component"
    }
  ],
  "docs": "Fully functional AI model selector with 22+ popular models from OpenAI, Anthropic, Google, xAI, DeepSeek, Mistral, Groq, Cohere, and Meta. Supports custom models via the models prop. Returns provider/model-id format compatible with Vercel AI SDK.",
  "categories": [
    "ai",
    "form",
    "selector"
  ],
  "type": "registry:ui"
}