{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"aavya-ai-image-studio","type":"registry:component","title":"Aavya AI Image Studio","description":"Compact prompt + upload surface for n8n-powered asset generation.","version":"0.3.0","status":"beta","registryDependencies":["button","input","select"],"files":[{"path":"src/components/layout/compact-ai-image-studio.tsx","type":"registry:component","content":"'use client'\n\nimport {\n  ChangeEvent,\n  FormEvent,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react'\nimport Image from 'next/image'\nimport {\n  ImagePlus,\n  Loader2,\n  X,\n} from 'lucide-react'\nimport { Button } from '@/components/ui/button'\nimport { Input } from '@/components/ui/input'\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from '@/components/ui/select'\n\ninterface WorkflowResponse {\n  imageUrl?: string\n  prompt?: string\n  [key: string]: unknown\n}\n\nexport function CompactAiImageStudio() {\n  const [prompt, setPrompt] = useState('')\n  const [model, setModel] = useState('gpt-image-1')\n  const [resolution, setResolution] = useState('1024x1024')\n  const [aspectRatio, setAspectRatio] = useState('1:1')\n  const [file, setFile] = useState<File | null>(null)\n  const [loading, setLoading] = useState(false)\n  const [error, setError] = useState<string | null>(null)\n  const [result, setResult] = useState<WorkflowResponse | null>(null)\n  const mode = file ? 'modify' : 'create'\n  const fileInputRef = useRef<HTMLInputElement | null>(null)\n\n  const previewUrl = useMemo(() => {\n    if (!file) return null\n    return URL.createObjectURL(file)\n  }, [file])\n\n  useEffect(() => {\n    return () => {\n      if (previewUrl) URL.revokeObjectURL(previewUrl)\n    }\n  }, [previewUrl])\n\n  const isSubmitDisabled = useMemo(() => {\n    if (!prompt.trim()) return true\n    return loading\n  }, [loading, prompt])\n\n  const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {\n    const nextFile = event.target.files?.[0] ?? null\n    setFile(nextFile)\n  }\n\n  const handleRemoveFile = () => {\n    setFile(null)\n    if (fileInputRef.current) {\n      fileInputRef.current.value = ''\n    }\n  }\n\n  const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n    event.preventDefault()\n    setError(null)\n    setResult(null)\n    setLoading(true)\n\n    try {\n      const formData = new FormData()\n      formData.append('prompt', prompt)\n      formData.append('mode', mode)\n      formData.append('model', model)\n      formData.append('resolution', resolution)\n      formData.append('aspectRatio', aspectRatio)\n\n      if (file) {\n        formData.append('image', file)\n      }\n\n      const response = await fetch('/api/assets/generate', {\n        method: 'POST',\n        body: formData,\n      })\n      const data = await response.json()\n\n      if (!response.ok) {\n        setError(data?.error ?? 'Request failed')\n        return\n      }\n\n      setResult((data?.result as WorkflowResponse) ?? null)\n    } catch (submitError) {\n      console.error('Image studio request failed:', submitError)\n      setError('Unexpected error while calling image workflow')\n    } finally {\n      setLoading(false)\n    }\n  }\n\n  return (\n    <section className=\"space-y-4\">\n      <div className=\"rounded-3xl border border-border bg-card/85 p-5 shadow-[0_2px_8px_rgba(16,24,40,0.06)] dark:shadow-[0_2px_10px_rgba(0,0,0,0.28)] md:p-7\">\n        <form onSubmit={handleSubmit} className=\"space-y-5\">\n          <label htmlFor=\"compact-image-prompt\" className=\"sr-only\">\n            Describe the content you want to create\n          </label>\n\n          <div className=\"flex items-center gap-4\">\n            <input\n              ref={fileInputRef}\n              id=\"source-image-compact\"\n              type=\"file\"\n              accept=\"image/*\"\n              onChange={handleFileChange}\n              className=\"sr-only\"\n            />\n            <Button\n              type=\"button\"\n              variant=\"outline\"\n              size=\"icon\"\n              className=\"h-12 w-12 rounded-full border-input bg-card text-foreground shadow-none\"\n              asChild\n              aria-label=\"Upload image to modify\"\n            >\n              <label htmlFor=\"source-image-compact\" className=\"cursor-pointer\">\n                <ImagePlus className=\"h-10 w-10\" />\n              </label>\n            </Button>\n            <Input\n              id=\"compact-image-prompt\"\n              value={prompt}\n              onChange={(event) => setPrompt(event.target.value)}\n              placeholder=\"Describe the content you want to create\"\n              className=\"h-14 border-0 bg-transparent px-4 text-xl text-foreground shadow-none placeholder:text-muted-foreground focus-visible:ring-0\"\n              required\n            />\n          </div>\n\n          <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n            <div className=\"flex flex-row gap-2.5\">\n              <Select value={resolution} onValueChange={setResolution}>\n                <SelectTrigger aria-label=\"Output resolution\">\n                  <SelectValue />\n                </SelectTrigger>\n                <SelectContent>\n                  <SelectItem value=\"1024x1024\">360P</SelectItem>\n                  <SelectItem value=\"1536x1024\">720P Wide</SelectItem>\n                  <SelectItem value=\"1024x1536\">720P Tall</SelectItem>\n                </SelectContent>\n              </Select>\n              <Select value={aspectRatio} onValueChange={setAspectRatio}>\n                <SelectTrigger aria-label=\"Aspect ratio\">\n                  <SelectValue />\n                </SelectTrigger>\n                <SelectContent>\n                  <SelectItem value=\"1:1\">1:1</SelectItem>\n                  <SelectItem value=\"16:9\">16:9</SelectItem>\n                  <SelectItem value=\"9:16\">9:16</SelectItem>\n                </SelectContent>\n              </Select>\n              <Select value={model} onValueChange={setModel}>\n                <SelectTrigger aria-label=\"Image model\">\n                  <SelectValue />\n                </SelectTrigger>\n                <SelectContent>\n                  <SelectItem value=\"gpt-image-1\">GPT Image 1</SelectItem>\n                  <SelectItem value=\"dall-e-3\">DALL-E 3</SelectItem>\n                </SelectContent>\n              </Select>\n            </div>\n\n            <Button\n              type=\"submit\"\n              disabled={isSubmitDisabled}              \n            >\n              {loading ? (\n                <>\n                  <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n                  Creating\n                </>\n              ) : (\n                <>\n                  Create\n                </>\n              )}\n            </Button>\n          </div>\n\n          {previewUrl && (\n            <div className=\"flex w-fit items-center gap-2.5 rounded-xl border border-input bg-card p-2 pr-3 shadow-sm\">\n              <div className=\"relative h-14 w-14 overflow-hidden rounded-lg\">\n                <Image\n                  src={previewUrl}\n                  alt={`Selected source image ${file?.name ?? ''}`}\n                  width={64}\n                  height={64}\n                  className=\"h-full w-full object-cover\"\n                  unoptimized\n                />\n                <button\n                  type=\"button\"\n                  onClick={handleRemoveFile}\n                  className=\"absolute -right-2 -top-2 flex h-6 w-6 items-center justify-center rounded-full bg-card text-foreground shadow-md ring-1 ring-input transition hover:bg-accent\"\n                  aria-label=\"Remove selected image\"\n                >\n                  <X className=\"h-3 w-3\" />\n                </button>\n              </div>\n              <div>\n                <p className=\"max-w-[180px] truncate text-xs font-medium text-foreground\">\n                  {file?.name}\n                </p>\n                <p className=\"text-[11px] text-muted-foreground\">Source image selected</p>\n              </div>\n            </div>\n          )}\n        </form>\n      </div>\n\n      {error && (\n        <div className=\"rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive\">\n          {error}\n        </div>\n      )}\n\n      {result && (\n        <div className=\"space-y-3 rounded-xl border bg-card p-4\">\n          <p className=\"text-sm font-medium\">Workflow response received</p>\n          {typeof result.imageUrl === 'string' && result.imageUrl.length > 0 && (\n            <div className=\"overflow-hidden rounded-xl border\">\n              <Image\n                src={result.imageUrl}\n                alt=\"Generated brand image\"\n                width={1024}\n                height={1024}\n                className=\"h-auto w-full\"\n                unoptimized\n              />\n            </div>\n          )}\n          <pre className=\"overflow-x-auto rounded bg-background p-3 text-xs text-muted-foreground\">\n            {JSON.stringify(result, null, 2)}\n          </pre>\n        </div>\n      )}\n    </section>\n  )\n}\n"}]}