Multimodal Studio
AI & AgentsPrompt-to-artifact canvas with aspect ratios and style presets.
Interactive Live Preview
components/ui/image-studio.tsxTypeScript · React 19
1"use client";23import * as React from "react";4import { Button } from "@/components/atoms/Button";56interface ImageStudioProps {7 variant?: string;8}910export default function ImageStudio({ variant = "Canvas" }: ImageStudioProps) {11 const [aspect, setAspect] = React.useState<"1:1" | "16:9" | "9:16">("16:9");12 const [style, setStyle] = React.useState("Cinematic");13 const [isGenerating, setIsGenerating] = React.useState(false);14 const [progress, setProgress] = React.useState(100);15 const [sliderPos, setSliderPos] = React.useState(50);16 const [prompt, setPrompt] = React.useState(17 "Futuristic green glass solar greenhouse on Mars with neon emerald botanical glows, hyper-detailed 8k octane render."18 );1920 const handleGenerate = () => {21 setIsGenerating(true);22 setProgress(0);23 const interval = setInterval(() => {24 setProgress((p) => {25 if (p >= 100) {26 clearInterval(interval);27 setIsGenerating(false);28 return 100;29 }30 return p + 20;31 });32 }, 200);33 };3435 return (36 <div className="w-full max-w-110 overflow-hidden rounded-card bg-surface shadow-card border border-line">37 {/* Header */}38 <div className="primitive-card-bar flex items-center justify-between border-b border-line bg-surface/90 px-3.5 py-2.5">39 <div className="flex items-center gap-2">40 <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">41 <rect x="3" y="3" width="18" height="18" rx="2" ry="2" />42 <circle cx="8.5" cy="8.5" r="1.5" />43 <polyline points="21 15 16 10 5 21" />44 </svg>45 <span className="font-display font-semibold text-[13.5px] text-ink">46 Multimodal Studio & Canvas47 </span>48 </div>49 <span className="font-mono text-[10.5px] text-ink-3">FLUX.1 [dev]</span>50 </div>5152 {/* Canvas Viewport with Comparison Slider */}53 <div className="relative overflow-hidden bg-inset flex items-center justify-center min-h-[170px] aspect-video">54 {/* Generated Image Background with Emerald Gradient Layer */}55 <div56 className="absolute inset-0 bg-cover bg-center"57 style={{58 backgroundImage: `radial-gradient(ellipse at center, rgba(125, 255, 179, 0.3) 0%, rgba(10, 24, 13, 0.95) 75%), linear-gradient(135deg, #0d2a17 0%, #06150b 100%)`,59 }}60 />6162 {/* Dynamic Stylized Graphic */}63 <div className="relative z-10 flex flex-col items-center gap-2 text-center p-4">64 <div className="size-16 rounded-2xl bg-surface/40 backdrop-blur-md border border-line flex items-center justify-center shadow-card">65 <span className="text-2xl animate-pulse">🌿</span>66 </div>67 <span className="font-display font-semibold text-[14px] text-ink">68 {isGenerating ? `Synthesizing ${progress}%…` : "Generated Render #8402"}69 </span>70 <span className="font-mono text-[10.5px] text-accent">71 {aspect} · {style} · Seed #49281172 </span>73 </div>7475 {/* Progress Overlay */}76 {isGenerating && (77 <div className="absolute inset-0 z-20 flex flex-col items-center justify-center bg-black/60 backdrop-blur-sm">78 <div className="w-48 h-1.5 rounded-full bg-field overflow-hidden">79 <div80 className="h-full bg-accent transition-all duration-200"81 style={{ width: `${progress}%` }}82 />83 </div>84 <span className="mt-2 font-mono text-[11px] text-ink">Sampling Latents {progress}%</span>85 </div>86 )}87 </div>8889 {/* Style & Aspect Controls */}90 <div className="flex flex-wrap items-center justify-between gap-2 border-t border-line bg-inset/30 p-2.5 text-[11px]">91 {/* Aspect selector */}92 <div className="flex items-center gap-1">93 {(["1:1", "16:9", "9:16"] as const).map((ratio) => (94 <button95 key={ratio}96 onClick={() => setAspect(ratio)}97 className={`rounded-chip px-2 py-0.5 font-mono transition-colors ${98 aspect === ratio ? "bg-surface text-ink shadow-btn font-semibold" : "text-ink-3 hover:text-ink"99 }`}100 >101 {ratio}102 </button>103 ))}104 </div>105106 {/* Style selector */}107 <div className="flex items-center gap-1">108 {["Cinematic", "Anime", "3D Render"].map((st) => (109 <button110 key={st}111 onClick={() => setStyle(st)}112 className={`rounded-chip px-2 py-0.5 transition-colors ${113 style === st ? "bg-accent text-accent-ink font-semibold" : "text-ink-3 hover:text-ink"114 }`}115 >116 {st}117 </button>118 ))}119 </div>120 </div>121122 {/* Prompt Composer & Action */}123 <div className="border-t border-line p-2.5">124 <div className="flex items-center gap-2 rounded-control bg-field p-2 focus-within:border-accent">125 <input126 type="text"127 value={prompt}128 onChange={(e) => setPrompt(e.target.value)}129 className="flex-1 bg-transparent text-[12.5px] text-ink outline-none font-body placeholder:text-ink-3"130 placeholder="Describe your scene…"131 />132 <Button133 size="sm"134 variant="accent"135 disabled={isGenerating}136 onClick={handleGenerate}137 className="shrink-0 text-[12px]"138 >139 {isGenerating ? "Generating…" : "Generate ✨"}140 </Button>141 </div>142 </div>143 </div>144 );145}146
Props & Specification
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | Canvas | Comparison | Canvas | Visual state and layout presentation mode. |
| className | string | "" | Optional Tailwind CSS wrapper class overrides. |