Multimodal Studio

AI & Agents

Prompt-to-artifact canvas with aspect ratios and style presets.

Interactive Live Preview
components/ui/image-studio.tsxTypeScript · React 19
1"use client";
2
3import * as React from "react";
4import { Button } from "@/components/atoms/Button";
5
6interface ImageStudioProps {
7 variant?: string;
8}
9
10export 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 );
19
20 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 };
34
35 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 & Canvas
47 </span>
48 </div>
49 <span className="font-mono text-[10.5px] text-ink-3">FLUX.1 [dev]</span>
50 </div>
51
52 {/* 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 <div
56 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 />
61
62 {/* 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 #492811
72 </span>
73 </div>
74
75 {/* 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 <div
80 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>
88
89 {/* 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 <button
95 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>
105
106 {/* Style selector */}
107 <div className="flex items-center gap-1">
108 {["Cinematic", "Anime", "3D Render"].map((st) => (
109 <button
110 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>
121
122 {/* 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 <input
126 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 <Button
133 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

PropTypeDefaultDescription
variantCanvas | ComparisonCanvasVisual state and layout presentation mode.
classNamestring""Optional Tailwind CSS wrapper class overrides.

Related Components in AI & Agents