// screens-images.jsx — Images page: generate images with agy (Antigravity) // and get a shareable link. Exposes window.ImageStudio. // // Calls the admin endpoint POST /ui/api/images/generate, which runs agy in a // scratch dir, collects the file(s) it wrote, and serves them back as capability // URLs (/media/generated/.). Generation is slow (~30–120s on a cold // request), so the request uses a long client timeout. (() => { const { useState, useCallback } = React; const { Card, Button, Select, Textarea, Input, Badge, PageHeader, EmptyState, Spinner, useToast } = window; // size value → what we POST. Presets the live server has been verified to honor. const SIZE_OPTIONS = [ { value: "512", label: "½K — 512 × 512" }, { value: "1024", label: "1K — 1024 × 1024" }, { value: "1280x720", label: "HD 720p — 1280 × 720" }, { value: "1920x1080", label: "FHD 1080p — 1920 × 1080" }, { value: "2k", label: "2K — 2560 × 1440" }, { value: "4k", label: "4K — 3840 × 2160" }, { value: "custom", label: "Custom…" }, ]; // Antigravity models that can drive image generation. Gemini models carry the // image tool; they are the reliable choices here. const MODEL_OPTIONS = [ "Gemini 3.5 Flash (Low)", "Gemini 3.5 Flash (Medium)", "Gemini 3.5 Flash (High)", "Gemini 3.1 Pro (Low)", "Gemini 3.1 Pro (High)", ]; function fmtBytes(n) { if (!n || n <= 0) return "—"; if (n < 1024) return n + " B"; if (n < 1024 * 1024) return (n / 1024).toFixed(1) + " KB"; return (n / (1024 * 1024)).toFixed(1) + " MB"; } function ImageStudio({ pushToast }) { const toast = useToast(); const notify = useCallback((msg, tone) => { if (toast) { tone === "error" ? toast.error(msg) : tone === "success" ? toast.success(msg) : toast.info(msg); } else if (pushToast) pushToast(msg, tone || "info"); }, [toast, pushToast]); const [prompt, setPrompt] = useState(""); const [size, setSize] = useState("1024"); const [custom, setCustom] = useState("1024x1024"); const [model, setModel] = useState(MODEL_OPTIONS[0]); const [count, setCount] = useState(1); const [busy, setBusy] = useState(false); const [result, setResult] = useState(null); const [error, setError] = useState(""); const absUrl = (url) => (url && url.startsWith("http")) ? url : (window.location.origin + url); const generate = useCallback(async () => { const p = prompt.trim(); if (!p) { notify("Enter a prompt first.", "error"); return; } const sizeVal = size === "custom" ? custom.trim() : size; setBusy(true); setError(""); setResult(null); try { const res = await api.post( "/ui/api/images/generate", { prompt: p, model, size: sizeVal, n: Number(count) || 1 }, { timeoutMs: 300000 } ); if (!res || !res.ok) { const msg = (res && res.error) || "Generation failed"; setError(msg); notify(msg, "error"); return; } setResult(res); const k = (res.images || []).length; notify(`Generated ${k} image${k === 1 ? "" : "s"}.`, "success"); } catch (e) { const msg = (e && e.message) || String(e); setError(msg); notify("Generation failed: " + msg, "error"); } finally { setBusy(false); } }, [prompt, size, custom, model, count, notify]); const copyLink = useCallback((url) => { const abs = absUrl(url); if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(abs).then( () => notify("Link copied to clipboard.", "success"), () => notify("Copy failed — select the link manually.", "error") ); } else { notify("Clipboard unavailable — select the link manually.", "info"); } }, [notify]); return (
Prompt What to draw