{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "settings-dialog",
  "dependencies": [
    "@platejs/ai"
  ],
  "registryDependencies": [
    "https://platejs.org/r/ai-kit.json",
    "button",
    "command",
    "dialog",
    "input",
    "popover"
  ],
  "files": [
    {
      "path": "src/registry/components/editor/settings-dialog.tsx",
      "content": "'use client';\n\n/* DEMO ONLY, DO NOT USE IN PRODUCTION */\n\nimport * as React from 'react';\n\nimport { CopilotPlugin } from '@platejs/ai/react';\nimport {\n  Check,\n  ChevronsUpDown,\n  ExternalLinkIcon,\n  Eye,\n  EyeOff,\n  Settings,\n  Wand2Icon,\n} from 'lucide-react';\nimport { useEditorRef } from 'platejs/react';\n\nimport { Button } from '@/components/ui/button';\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from '@/components/ui/command';\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from '@/components/ui/dialog';\nimport { Input } from '@/components/ui/input';\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from '@/components/ui/popover';\nimport { cn } from '@/lib/utils';\n\nimport { aiChatPlugin } from './plugins/ai-kit';\n\ntype Model = {\n  label: string;\n  value: string;\n};\n\nexport const models: Model[] = [\n  // OpenAI Models\n  { label: 'GPT-3.5 Turbo', value: 'openai/gpt-3.5-turbo' },\n  { label: 'GPT-3.5 Turbo Instruct', value: 'openai/gpt-3.5-turbo-instruct' },\n  { label: 'GPT-4 Turbo', value: 'openai/gpt-4-turbo' },\n  { label: 'GPT-4.1', value: 'openai/gpt-4.1' },\n  { label: 'GPT-4.1 Mini', value: 'openai/gpt-4.1-mini' },\n  { label: 'GPT-4.1 Nano', value: 'openai/gpt-4.1-nano' },\n  { label: 'GPT-4o', value: 'openai/gpt-4o' },\n  { label: 'GPT-4o Mini', value: 'openai/gpt-4o-mini' },\n  { label: 'GPT-5', value: 'openai/gpt-5' },\n  { label: 'GPT-5 Codex', value: 'openai/gpt-5-codex' },\n  { label: 'GPT-5 Mini', value: 'openai/gpt-5-mini' },\n  { label: 'GPT-5 Nano', value: 'openai/gpt-5-nano' },\n  { label: 'GPT-OSS 120B', value: 'openai/gpt-oss-120b' },\n  { label: 'GPT-OSS 20B', value: 'openai/gpt-oss-20b' },\n  { label: 'O1', value: 'openai/o1' },\n  { label: 'O3', value: 'openai/o3' },\n  { label: 'O3 Mini', value: 'openai/o3-mini' },\n  { label: 'O4 Mini', value: 'openai/o4-mini' },\n\n  // Google Models\n  { label: 'Gemini 2.0 Flash', value: 'google/gemini-2.0-flash' },\n  { label: 'Gemini 2.0 Flash Lite', value: 'google/gemini-2.0-flash-lite' },\n  { label: 'Gemini 2.5 Flash', value: 'google/gemini-2.5-flash' },\n  {\n    label: 'Gemini 2.5 Flash Image Preview',\n    value: 'google/gemini-2.5-flash-image-preview',\n  },\n  { label: 'Gemini 2.5 Flash Lite', value: 'google/gemini-2.5-flash-lite' },\n  { label: 'Gemini 2.5 Pro', value: 'google/gemini-2.5-pro' },\n  { label: 'Gemma 2 9B', value: 'google/gemma-2-9b' },\n\n  // Alibaba Models\n  { label: 'Qwen 3 14B', value: 'alibaba/qwen-3-14b' },\n  { label: 'Qwen 3 235B', value: 'alibaba/qwen-3-235b' },\n  { label: 'Qwen 3 30B', value: 'alibaba/qwen-3-30b' },\n  { label: 'Qwen 3 32B', value: 'alibaba/qwen-3-32b' },\n  { label: 'Qwen3 Coder', value: 'alibaba/qwen3-coder' },\n  { label: 'Qwen3 Coder Plus', value: 'alibaba/qwen3-coder-plus' },\n  { label: 'Qwen3 Max', value: 'alibaba/qwen3-max' },\n  { label: 'Qwen3 Max Preview', value: 'alibaba/qwen3-max-preview' },\n  {\n    label: 'Qwen3 Next 80B A3B Instruct',\n    value: 'alibaba/qwen3-next-80b-a3b-instruct',\n  },\n  {\n    label: 'Qwen3 Next 80B A3B Thinking',\n    value: 'alibaba/qwen3-next-80b-a3b-thinking',\n  },\n  { label: 'Qwen3 VL Instruct', value: 'alibaba/qwen3-vl-instruct' },\n  { label: 'Qwen3 VL Thinking', value: 'alibaba/qwen3-vl-thinking' },\n\n  // Amazon Models\n  { label: 'Nova Lite', value: 'amazon/nova-lite' },\n  { label: 'Nova Micro', value: 'amazon/nova-micro' },\n  { label: 'Nova Pro', value: 'amazon/nova-pro' },\n\n  // Anthropic Models\n  { label: 'Claude 3 Haiku', value: 'anthropic/claude-3-haiku' },\n  { label: 'Claude 3 Opus', value: 'anthropic/claude-3-opus' },\n  { label: 'Claude 3.5 Haiku', value: 'anthropic/claude-3.5-haiku' },\n  { label: 'Claude 3.5 Sonnet', value: 'anthropic/claude-3.5-sonnet' },\n  { label: 'Claude 3.7 Sonnet', value: 'anthropic/claude-3.7-sonnet' },\n  { label: 'Claude Opus 4', value: 'anthropic/claude-opus-4' },\n  { label: 'Claude Opus 4.1', value: 'anthropic/claude-opus-4.1' },\n  { label: 'Claude Sonnet 4', value: 'anthropic/claude-sonnet-4' },\n\n  // Cohere Models\n  { label: 'Command A', value: 'cohere/command-a' },\n  { label: 'Command R', value: 'cohere/command-r' },\n  { label: 'Command R Plus', value: 'cohere/command-r-plus' },\n\n  // DeepSeek Models\n  { label: 'DeepSeek R1', value: 'deepseek/deepseek-r1' },\n  {\n    label: 'DeepSeek R1 Distill Llama 70B',\n    value: 'deepseek/deepseek-r1-distill-llama-70b',\n  },\n  { label: 'DeepSeek V3', value: 'deepseek/deepseek-v3' },\n  { label: 'DeepSeek V3.1', value: 'deepseek/deepseek-v3.1' },\n  { label: 'DeepSeek V3.1 Base', value: 'deepseek/deepseek-v3.1-base' },\n  { label: 'DeepSeek V3.1 Terminus', value: 'deepseek/deepseek-v3.1-terminus' },\n  { label: 'DeepSeek V3.2 Exp', value: 'deepseek/deepseek-v3.2-exp' },\n  {\n    label: 'DeepSeek V3.2 Exp Thinking',\n    value: 'deepseek/deepseek-v3.2-exp-thinking',\n  },\n\n  // Inception Models\n  { label: 'Mercury Coder Small', value: 'inception/mercury-coder-small' },\n\n  // Meituan Models\n  { label: 'LongCat Flash Chat', value: 'meituan/longcat-flash-chat' },\n  { label: 'LongCat Flash Thinking', value: 'meituan/longcat-flash-thinking' },\n\n  // Meta Models\n  { label: 'Llama 3 70B', value: 'meta/llama-3-70b' },\n  { label: 'Llama 3 8B', value: 'meta/llama-3-8b' },\n  { label: 'Llama 3.1 70B', value: 'meta/llama-3.1-70b' },\n  { label: 'Llama 3.1 8B', value: 'meta/llama-3.1-8b' },\n  { label: 'Llama 3.2 11B', value: 'meta/llama-3.2-11b' },\n  { label: 'Llama 3.2 1B', value: 'meta/llama-3.2-1b' },\n  { label: 'Llama 3.2 3B', value: 'meta/llama-3.2-3b' },\n  { label: 'Llama 3.2 90B', value: 'meta/llama-3.2-90b' },\n  { label: 'Llama 3.3 70B', value: 'meta/llama-3.3-70b' },\n  { label: 'Llama 4 Maverick', value: 'meta/llama-4-maverick' },\n  { label: 'Llama 4 Scout', value: 'meta/llama-4-scout' },\n\n  // Mistral Models\n  { label: 'Codestral', value: 'mistral/codestral' },\n  { label: 'Devstral Small', value: 'mistral/devstral-small' },\n  { label: 'Magistral Medium', value: 'mistral/magistral-medium' },\n  { label: 'Magistral Small', value: 'mistral/magistral-small' },\n  { label: 'Ministral 3B', value: 'mistral/ministral-3b' },\n  { label: 'Ministral 8B', value: 'mistral/ministral-8b' },\n  { label: 'Mistral Large', value: 'mistral/mistral-large' },\n  { label: 'Mistral Medium', value: 'mistral/mistral-medium' },\n  { label: 'Mistral Small', value: 'mistral/mistral-small' },\n  { label: 'Mixtral 8x22B Instruct', value: 'mistral/mixtral-8x22b-instruct' },\n  { label: 'Pixtral 12B', value: 'mistral/pixtral-12b' },\n  { label: 'Pixtral Large', value: 'mistral/pixtral-large' },\n\n  // MoonshotAI Models\n  { label: 'Kimi K2', value: 'moonshotai/kimi-k2' },\n  { label: 'Kimi K2 0905', value: 'moonshotai/kimi-k2-0905' },\n  { label: 'Kimi K2 Turbo', value: 'moonshotai/kimi-k2-turbo' },\n\n  // Morph Models\n  { label: 'Morph V3 Fast', value: 'morph/morph-v3-fast' },\n  { label: 'Morph V3 Large', value: 'morph/morph-v3-large' },\n\n  // Perplexity Models\n  { label: 'Sonar', value: 'perplexity/sonar' },\n  { label: 'Sonar Pro', value: 'perplexity/sonar-pro' },\n  { label: 'Sonar Reasoning', value: 'perplexity/sonar-reasoning' },\n  { label: 'Sonar Reasoning Pro', value: 'perplexity/sonar-reasoning-pro' },\n\n  // Stealth Models\n  { label: 'Sonoma Dusk Alpha', value: 'stealth/sonoma-dusk-alpha' },\n  { label: 'Sonoma Sky Alpha', value: 'stealth/sonoma-sky-alpha' },\n\n  // Vercel Models\n  { label: 'v0 1.0 MD', value: 'vercel/v0-1.0-md' },\n  { label: 'v0 1.5 MD', value: 'vercel/v0-1.5-md' },\n\n  // xAI Models\n  { label: 'Grok 2', value: 'xai/grok-2' },\n  { label: 'Grok 2 Vision', value: 'xai/grok-2-vision' },\n  { label: 'Grok 3', value: 'xai/grok-3' },\n  { label: 'Grok 3 Fast', value: 'xai/grok-3-fast' },\n  { label: 'Grok 3 Mini', value: 'xai/grok-3-mini' },\n  { label: 'Grok 3 Mini Fast', value: 'xai/grok-3-mini-fast' },\n  { label: 'Grok 4', value: 'xai/grok-4' },\n  { label: 'Grok Code Fast 1', value: 'xai/grok-code-fast-1' },\n  {\n    label: 'Grok 4 Fast Non-Reasoning',\n    value: 'xai/grok-4-fast-non-reasoning',\n  },\n  { label: 'Grok 4 Fast Reasoning', value: 'xai/grok-4-fast-reasoning' },\n\n  // ZAI Models\n  { label: 'GLM 4.5', value: 'zai/glm-4.5' },\n  { label: 'GLM 4.5 Air', value: 'zai/glm-4.5-air' },\n  { label: 'GLM 4.5V', value: 'zai/glm-4.5v' },\n];\n\nexport function SettingsDialog() {\n  const editor = useEditorRef();\n\n  const [tempModel, setTempModel] = React.useState(models[7]);\n  const [tempKeys, setTempKeys] = React.useState<Record<string, string>>({\n    aiGatewayApiKey: '',\n    uploadthing: '',\n  });\n  const [showKey, setShowKey] = React.useState<Record<string, boolean>>({});\n  const [open, setOpen] = React.useState(false);\n  const [openModel, setOpenModel] = React.useState(false);\n\n  const handleSubmit = (e: React.FormEvent) => {\n    e.preventDefault();\n\n    // Update AI chat options\n    const chatOptions = editor.getOptions(aiChatPlugin).chatOptions ?? {};\n\n    editor.setOption(aiChatPlugin, 'chatOptions', {\n      ...chatOptions,\n      body: {\n        ...chatOptions.body,\n        apiKey: tempKeys.aiGatewayApiKey,\n        model: tempModel.value,\n      },\n    });\n\n    setOpen(false);\n\n    // Update AI complete options\n    const completeOptions =\n      editor.getOptions(CopilotPlugin).completeOptions ?? {};\n    editor.setOption(CopilotPlugin, 'completeOptions', {\n      ...completeOptions,\n      body: {\n        ...completeOptions.body,\n        apiKey: tempKeys.aiGatewayApiKey,\n        model: tempModel.value,\n      },\n    });\n  };\n\n  const toggleKeyVisibility = (key: string) => {\n    setShowKey((prev) => ({ ...prev, [key]: !prev[key] }));\n  };\n\n  const renderApiKeyInput = (service: string, label: string) => (\n    <div className=\"group relative\">\n      <div className=\"flex items-center justify-between\">\n        <label\n          className=\"-translate-y-1/2 absolute top-1/2 block cursor-text px-1 text-muted-foreground/70 text-sm transition-all group-focus-within:pointer-events-none group-focus-within:top-0 group-focus-within:cursor-default group-focus-within:font-medium group-focus-within:text-foreground group-focus-within:text-xs has-[+input:not(:placeholder-shown)]:pointer-events-none has-[+input:not(:placeholder-shown)]:top-0 has-[+input:not(:placeholder-shown)]:cursor-default has-[+input:not(:placeholder-shown)]:font-medium has-[+input:not(:placeholder-shown)]:text-foreground has-[+input:not(:placeholder-shown)]:text-xs\"\n          htmlFor={label}\n        >\n          <span className=\"inline-flex bg-background px-2\">{label}</span>\n        </label>\n        <Button\n          asChild\n          size=\"icon\"\n          variant=\"ghost\"\n          className=\"absolute top-0 right-[28px] h-full\"\n        >\n          <a\n            className=\"flex items-center\"\n            href={\n              service === 'aiGatewayApiKey'\n                ? 'https://vercel.com/docs/ai-gateway'\n                : 'https://uploadthing.com/dashboard'\n            }\n            rel=\"noopener noreferrer\"\n            target=\"_blank\"\n          >\n            <ExternalLinkIcon className=\"size-4\" />\n            <span className=\"sr-only\">Get {label}</span>\n          </a>\n        </Button>\n      </div>\n\n      <Input\n        id={label}\n        className=\"pr-10\"\n        value={tempKeys[service]}\n        onChange={(e) =>\n          setTempKeys((prev) => ({ ...prev, [service]: e.target.value }))\n        }\n        placeholder=\"\"\n        data-1p-ignore\n        type={showKey[service] ? 'text' : 'password'}\n      />\n      <Button\n        size=\"icon\"\n        variant=\"ghost\"\n        className=\"absolute top-0 right-0 h-full\"\n        onClick={() => toggleKeyVisibility(service)}\n        type=\"button\"\n      >\n        {showKey[service] ? (\n          <EyeOff className=\"size-4\" />\n        ) : (\n          <Eye className=\"size-4\" />\n        )}\n        <span className=\"sr-only\">\n          {showKey[service] ? 'Hide' : 'Show'} {label}\n        </span>\n      </Button>\n    </div>\n  );\n\n  return (\n    <Dialog open={open} onOpenChange={setOpen}>\n      <DialogTrigger asChild>\n        <Button\n          size=\"icon\"\n          variant=\"default\"\n          className={cn(\n            'group fixed right-4 bottom-4 z-50 size-10 overflow-hidden',\n            'rounded-full shadow-md hover:shadow-lg'\n          )}\n          // data-block-hide\n        >\n          <Settings className=\"size-4\" />\n        </Button>\n      </DialogTrigger>\n      <DialogContent>\n        <DialogHeader>\n          <DialogTitle className=\"text-xl\">Settings</DialogTitle>\n          <DialogDescription>\n            Configure your API keys and preferences.\n          </DialogDescription>\n        </DialogHeader>\n\n        <form className=\"space-y-10\" onSubmit={handleSubmit}>\n          {/* AI Settings Group */}\n          <div className=\"space-y-4\">\n            <div className=\"flex items-center gap-2\">\n              <div className=\"size-8 rounded-full bg-purple-100 p-2 dark:bg-purple-900\">\n                <Wand2Icon className=\"size-4 text-purple-600 dark:text-purple-400\" />\n              </div>\n              <h4 className=\"font-semibold\">AI</h4>\n            </div>\n\n            <div className=\"space-y-4\">\n              {renderApiKeyInput('aiGatewayApiKey', 'AI Gateway API Key')}\n\n              <div className=\"group relative\">\n                <label\n                  className=\"-translate-y-1/2 absolute start-1 top-0 z-10 block bg-background px-2 font-medium text-foreground text-xs group-has-disabled:opacity-50\"\n                  htmlFor=\"select-model\"\n                >\n                  Model\n                </label>\n                <Popover open={openModel} onOpenChange={setOpenModel}>\n                  <PopoverTrigger id=\"select-model\" asChild>\n                    <Button\n                      size=\"lg\"\n                      variant=\"outline\"\n                      className=\"w-full justify-between\"\n                      aria-expanded={openModel}\n                      role=\"combobox\"\n                    >\n                      <code>{tempModel.label}</code>\n                      <ChevronsUpDown className=\"ml-2 size-4 shrink-0 opacity-50\" />\n                    </Button>\n                  </PopoverTrigger>\n                  <PopoverContent className=\"w-full p-0\">\n                    <Command>\n                      <CommandInput placeholder=\"Search model...\" />\n                      <CommandEmpty>No model found.</CommandEmpty>\n                      <CommandList>\n                        <CommandGroup>\n                          {models.map((m) => (\n                            <CommandItem\n                              key={m.value}\n                              value={m.value}\n                              onSelect={() => {\n                                setTempModel(m);\n                                setOpenModel(false);\n                              }}\n                            >\n                              <Check\n                                className={cn(\n                                  'mr-2 size-4',\n                                  tempModel.value === m.value\n                                    ? 'opacity-100'\n                                    : 'opacity-0'\n                                )}\n                              />\n                              <code>{m.label}</code>\n                            </CommandItem>\n                          ))}\n                        </CommandGroup>\n                      </CommandList>\n                    </Command>\n                  </PopoverContent>\n                </Popover>\n              </div>\n            </div>\n          </div>\n\n          {/* Upload Settings Group */}\n          {/* <div className=\"space-y-4\">\n            <div className=\"flex items-center gap-2\">\n              <div className=\"size-8 rounded-full bg-red-100 p-2 dark:bg-red-900\">\n                <Upload className=\"size-4 text-red-600 dark:text-red-400\" />\n              </div>\n              <h4 className=\"font-semibold\">Upload</h4>\n            </div>\n\n            <div className=\"space-y-4\">\n              {renderApiKeyInput('uploadthing', 'Uploadthing API key')}\n            </div>\n          </div> */}\n\n          <Button size=\"lg\" className=\"w-full\" type=\"submit\">\n            Save changes\n          </Button>\n        </form>\n\n        <p className=\"text-muted-foreground text-sm\">\n          Not stored anywhere. Used only for current session requests.\n        </p>\n      </DialogContent>\n    </Dialog>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/editor/settings-dialog.tsx"
    }
  ],
  "type": "registry:component"
}