{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ai-04",
  "type": "registry:block",
  "title": "AI Chat with File Attachments",
  "description": "A ai chat with file attachments block.",
  "author": "ephraim duncan <https://ephraimduncan.com>",
  "registryDependencies": [
    "badge",
    "button",
    "dropdown-menu",
    "label",
    "switch",
    "textarea"
  ],
  "dependencies": [
    "@tabler/icons-react"
  ],
  "files": [
    {
      "path": "content/components/ai/ai-04.tsx",
      "type": "registry:component",
      "target": "components/ai-04.tsx",
      "content": "'use client';\n\nimport {\n  IconAdjustmentsHorizontal,\n  IconArrowUp,\n  IconBrandFigma,\n  IconCamera,\n  IconCirclePlus,\n  IconClipboard,\n  IconFileUpload,\n  IconHistory,\n  IconLayoutDashboard,\n  IconLink,\n  IconPaperclip,\n  IconPlayerPlay,\n  IconPlus,\n  IconSparkles,\n  IconTemplate,\n  IconX,\n} from '@tabler/icons-react';\nimport Image from 'next/image';\nimport { useRef, useState } from 'react';\nimport { Badge } from '@/components/ui/badge';\nimport { Button } from '@/components/ui/button';\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\nimport { Label } from '@/components/ui/label';\nimport { Switch } from '@/components/ui/switch';\nimport { Textarea } from '@/components/ui/textarea';\nimport { cn } from '@/lib/utils';\n\ninterface AttachedFile {\n  id: string;\n  name: string;\n  file: File;\n  preview?: string;\n}\n\nconst ACTIONS = [\n  { id: 'clone-screenshot', icon: IconCamera, label: 'Clone a Screenshot' },\n  { id: 'import-figma', icon: IconBrandFigma, label: 'Import from Figma' },\n  { id: 'upload-project', icon: IconFileUpload, label: 'Upload a Project' },\n  { id: 'landing-page', icon: IconLayoutDashboard, label: 'Landing Page' },\n];\n\nexport default function Ai04({\n  onSubmit,\n}: {\n  onSubmit?: (prompt: string) => void;\n}) {\n  const [prompt, setPrompt] = useState('');\n  const [isDragOver, setIsDragOver] = useState(false);\n  const [attachedFiles, setAttachedFiles] = useState<AttachedFile[]>([]);\n  const fileInputRef = useRef<HTMLInputElement>(null);\n\n  const [settings, setSettings] = useState({\n    autoComplete: true,\n    streaming: false,\n    showHistory: false,\n  });\n\n  const generateFileId = () => Math.random().toString(36).substring(7);\n  const processFiles = (files: File[]) => {\n    for (const file of files) {\n      const fileId = generateFileId();\n      const attachedFile: AttachedFile = {\n        id: fileId,\n        name: file.name,\n        file,\n      };\n\n      if (file.type.startsWith('image/')) {\n        const reader = new FileReader();\n        reader.onload = () => {\n          setAttachedFiles((prev) =>\n            prev.map((f) =>\n              f.id === fileId ? { ...f, preview: reader.result as string } : f\n            )\n          );\n        };\n        reader.readAsDataURL(file);\n      }\n\n      setAttachedFiles((prev) => [...prev, attachedFile]);\n    }\n  };\n  const submitPrompt = () => {\n    if (prompt.trim() && onSubmit) {\n      onSubmit(prompt.trim());\n      setPrompt('');\n    }\n  };\n  const updateSetting = (key: keyof typeof settings, value: boolean) => {\n    setSettings((prev) => ({ ...prev, [key]: value }));\n  };\n  const handleSubmit = (e: React.FormEvent) => {\n    e.preventDefault();\n    submitPrompt();\n  };\n  const handleDragOver = (e: React.DragEvent) => {\n    e.preventDefault();\n    setIsDragOver(true);\n  };\n  const handleDragLeave = (e: React.DragEvent) => {\n    e.preventDefault();\n    setIsDragOver(false);\n  };\n  const handleDrop = (e: React.DragEvent) => {\n    e.preventDefault();\n    setIsDragOver(false);\n\n    const files = Array.from(e.dataTransfer.files);\n    if (files.length > 0) {\n      processFiles(files);\n    }\n  };\n  const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n    setPrompt(e.target.value);\n  };\n  const handleKeyDown = (e: React.KeyboardEvent) => {\n    if (e.key === 'Enter' && !e.shiftKey) {\n      e.preventDefault();\n      submitPrompt();\n    }\n  };\n\n  const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {\n    const files = Array.from(e.target.files || []);\n    processFiles(files);\n\n    if (fileInputRef.current) {\n      fileInputRef.current.value = '';\n    }\n  };\n\n  const handleRemoveFile = (fileId: string) => {\n    setAttachedFiles((prev) => prev.filter((file) => file.id !== fileId));\n  };\n\n  return (\n    <div className=\"mx-auto flex w-full flex-col gap-4\">\n      <h1 className=\"text-balance text-pretty text-center font-heading font-semibold text-[29px] text-foreground tracking-tighter sm:text-[32px] md:text-[46px]\">\n        Prompt. Refine. Ship.\n      </h1>\n      <h2 className=\"-my-5 text-balance pb-4 text-center text-muted-foreground text-xl\">\n        Build real, working software just by describing it\n      </h2>\n\n      <div className=\"relative z-10 mx-auto flex w-full max-w-2xl flex-col content-center\">\n        <form\n          className=\"overflow-visible rounded-xl border p-2 transition-colors duration-200 focus-within:border-ring\"\n          onDragLeave={handleDragLeave}\n          onDragOver={handleDragOver}\n          onDrop={handleDrop}\n          onSubmit={handleSubmit}\n        >\n          {attachedFiles.length > 0 && (\n            <div className=\"relative mb-2 flex w-fit items-center gap-2 overflow-hidden\">\n              {attachedFiles.map((file) => (\n                <Badge\n                  className=\"group relative h-6 max-w-30 cursor-pointer overflow-hidden px-0 text-[13px] transition-colors hover:bg-accent\"\n                  key={file.id}\n                  variant=\"outline\"\n                >\n                  <span className=\"flex h-full items-center gap-1.5 overflow-hidden pl-1 font-normal\">\n                    <div className=\"relative flex h-4 min-w-4 items-center justify-center\">\n                      {file.preview ? (\n                        <Image\n                          alt={file.name}\n                          className=\"absolute inset-0 h-4 w-4 rounded border object-cover\"\n                          height={16}\n                          src={file.preview}\n                          width={16}\n                        />\n                      ) : (\n                        <IconPaperclip className=\"opacity-60\" size={12} />\n                      )}\n                    </div>\n                    <span className=\"inline overflow-hidden truncate pr-1.5\">\n                      {file.name}\n                    </span>\n                  </span>\n                  <button\n                    className=\"absolute right-1 z-10 rounded-sm p-0.5 text-muted-foreground opacity-0 focus-visible:bg-accent focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-background group-hover:opacity-100\"\n                    onClick={() => handleRemoveFile(file.id)}\n                    type=\"button\"\n                  >\n                    <IconX size={12} />\n                  </button>\n                </Badge>\n              ))}\n            </div>\n          )}\n          <Textarea\n            className=\"max-h-50 min-h-12 resize-none rounded-none border-none bg-transparent! p-0 text-sm shadow-none focus-visible:border-transparent focus-visible:ring-0\"\n            onChange={handleTextareaChange}\n            onKeyDown={handleKeyDown}\n            placeholder=\"Ask anything\"\n            value={prompt}\n          />\n\n          <div className=\"flex items-center gap-1\">\n            <div className=\"flex items-end gap-0.5 sm:gap-1\">\n              <input\n                className=\"sr-only\"\n                multiple\n                onChange={handleFileSelect}\n                ref={fileInputRef}\n                type=\"file\"\n              />\n\n              <DropdownMenu>\n                <DropdownMenuTrigger\n                  render={\n                    <Button\n                      aria-label=\"Add attachments\"\n                      className=\"ml-[-2px] rounded-md\"\n                      size=\"icon-sm\"\n                      type=\"button\"\n                      variant=\"ghost\"\n                    />\n                  }\n                >\n                  <IconPlus size={16} />\n                </DropdownMenuTrigger>\n                <DropdownMenuContent\n                  align=\"start\"\n                  className=\"max-w-xs rounded-2xl p-1.5\"\n                >\n                  <DropdownMenuGroup className=\"space-y-1\">\n                    <DropdownMenuItem\n                      className=\"rounded-md text-xs\"\n                      onClick={() => fileInputRef.current?.click()}\n                    >\n                      <div className=\"flex items-center gap-2\">\n                        <IconPaperclip\n                          className=\"text-muted-foreground\"\n                          size={16}\n                        />\n                        <span>Attach Files</span>\n                      </div>\n                    </DropdownMenuItem>\n                    <DropdownMenuItem className=\"rounded-md text-xs\">\n                      <div className=\"flex items-center gap-2\">\n                        <IconLink className=\"text-muted-foreground\" size={16} />\n                        <span>Import from URL</span>\n                      </div>\n                    </DropdownMenuItem>\n                    <DropdownMenuItem className=\"rounded-md text-xs\">\n                      <div className=\"flex items-center gap-2\">\n                        <IconClipboard\n                          className=\"text-muted-foreground\"\n                          size={16}\n                        />\n                        <span>Paste from Clipboard</span>\n                      </div>\n                    </DropdownMenuItem>\n                    <DropdownMenuItem className=\"rounded-md text-xs\">\n                      <div className=\"flex items-center gap-2\">\n                        <IconTemplate\n                          className=\"text-muted-foreground\"\n                          size={16}\n                        />\n                        <span>Use Template</span>\n                      </div>\n                    </DropdownMenuItem>\n                  </DropdownMenuGroup>\n                </DropdownMenuContent>\n              </DropdownMenu>\n\n              <DropdownMenu>\n                <DropdownMenuTrigger\n                  render={\n                    <Button\n                      aria-label=\"Adjust settings\"\n                      className=\"rounded-md\"\n                      size=\"icon-sm\"\n                      type=\"button\"\n                      variant=\"ghost\"\n                    />\n                  }\n                >\n                  <IconAdjustmentsHorizontal size={16} />\n                </DropdownMenuTrigger>\n                <DropdownMenuContent\n                  align=\"start\"\n                  className=\"w-48 rounded-2xl p-3\"\n                >\n                  <DropdownMenuGroup className=\"space-y-3\">\n                    <div className=\"flex items-center justify-between\">\n                      <div className=\"flex items-center gap-2\">\n                        <IconSparkles\n                          className=\"text-muted-foreground\"\n                          size={16}\n                        />\n                        <Label className=\"text-xs\">Auto-complete</Label>\n                      </div>\n                      <Switch\n                        checked={settings.autoComplete}\n                        className=\"scale-75\"\n                        onCheckedChange={(value) =>\n                          updateSetting('autoComplete', value)\n                        }\n                      />\n                    </div>\n\n                    <div className=\"flex items-center justify-between\">\n                      <div className=\"flex items-center gap-2\">\n                        <IconPlayerPlay\n                          className=\"text-muted-foreground\"\n                          size={16}\n                        />\n                        <Label className=\"text-xs\">Streaming</Label>\n                      </div>\n                      <Switch\n                        checked={settings.streaming}\n                        className=\"scale-75\"\n                        onCheckedChange={(value) =>\n                          updateSetting('streaming', value)\n                        }\n                      />\n                    </div>\n\n                    <div className=\"flex items-center justify-between\">\n                      <div className=\"flex items-center gap-2\">\n                        <IconHistory\n                          className=\"text-muted-foreground\"\n                          size={16}\n                        />\n                        <Label className=\"text-xs\">Show History</Label>\n                      </div>\n                      <Switch\n                        checked={settings.showHistory}\n                        className=\"scale-75\"\n                        onCheckedChange={(value) =>\n                          updateSetting('showHistory', value)\n                        }\n                      />\n                    </div>\n                  </DropdownMenuGroup>\n                </DropdownMenuContent>\n              </DropdownMenu>\n            </div>\n\n            <div className=\"ml-auto flex items-center gap-0.5 sm:gap-1\">\n              <Button\n                aria-label=\"Send message\"\n                className=\"rounded-md\"\n                disabled={!prompt.trim()}\n                size=\"icon-sm\"\n                type=\"submit\"\n                variant=\"default\"\n              >\n                <IconArrowUp size={16} />\n              </Button>\n            </div>\n          </div>\n\n          <div\n            className={cn(\n              'pointer-events-none absolute inset-0 z-20 flex items-center justify-center rounded-[inherit] border border-border border-dashed bg-muted text-foreground text-sm transition-opacity duration-200',\n              isDragOver ? 'opacity-100' : 'opacity-0'\n            )}\n          >\n            <span className=\"flex w-full items-center justify-center gap-1 font-medium\">\n              <IconCirclePlus className=\"min-w-4\" size={16} />\n              Drop files here to add as attachments\n            </span>\n          </div>\n        </form>\n      </div>\n\n      <div className=\"mx-auto flex min-h-0 max-w-250 shrink-0 flex-wrap items-center justify-center gap-3\">\n        {ACTIONS.map((action) => (\n          <Button\n            className=\"gap-2 rounded-full\"\n            key={action.id}\n            size=\"sm\"\n            variant=\"outline\"\n          >\n            <action.icon size={16} />\n            {action.label}\n          </Button>\n        ))}\n      </div>\n    </div>\n  );\n}\n"
    }
  ],
  "categories": [
    "ai"
  ]
}