import { useEffect, useMemo, useState } from "react"; import { callSkill, loadAgentConfig, loadSession, signInUrl } from "./a2a.js"; const DEFAULT_FORM = { asset_type: "character", theme: "forest knight", palette: "#22c55e, #14532d, #facc15", size: 128, style: "pixel", variant_count: 2, }; export function App() { const [config, setConfig] = useState(null); const [session, setSession] = useState(null); const [form, setForm] = useState(DEFAULT_FORM); const [result, setResult] = useState(null); const [receipts, setReceipts] = useState([]); const [error, setError] = useState(null); const [running, setRunning] = useState(false); const [loadingReceipts, setLoadingReceipts] = useState(false); useEffect(() => { loadAgentConfig() .then((data) => { setConfig(data); return loadSession(data).catch(() => ({ authenticated: false })); }) .then((sessionData) => setSession(sessionData)) .catch((err) => setError(err.message || String(err))); }, []); const generateSkill = useMemo( () => config?.skills?.find((skill) => skill.name === "generate_svg_asset"), [config], ); const listSkill = useMemo( () => config?.skills?.find((skill) => skill.name === "list_recent_receipts"), [config], ); const needsSignIn = Boolean(config?.auth?.invokeRequiresSession && session && !session.authenticated); const signInHref = useMemo(() => (config ? signInUrl(config) : null), [config]); function update(field, value) { setForm((current) => ({ ...current, [field]: value })); } function requestPayload() { return { asset_type: form.asset_type, theme: form.theme.trim(), palette: form.palette.split(",").map((item) => item.trim()).filter(Boolean), size: Number(form.size), style: form.style, variant_count: Number(form.variant_count), }; } async function generateAsset(event) { event.preventDefault(); if (!config || !generateSkill) return; setRunning(true); setError(null); setResult(null); try { const data = await callSkill(config, "generate_svg_asset", requestPayload()); setResult(data); if (data?.status === "setup_required") { setError(data.warning || "Setup is required before this skill can run."); } if (listSkill) loadReceipts(); } catch (err) { setError(err.message || String(err)); } finally { setRunning(false); } } async function loadReceipts() { if (!config || !listSkill) return; setLoadingReceipts(true); try { const data = await callSkill(config, "list_recent_receipts", { limit: 8 }); if (data.status === "ok") setReceipts(data.receipts || []); } catch (err) { if (!String(err.message || err).toLowerCase().includes("sign in")) { setError(err.message || String(err)); } } finally { setLoadingReceipts(false); } } if (!config) { return

{error || "Loading SVG asset studio..."}

; } return (

SVG game asset generator

Generate production-ready SVG sprites for your game.

Choose an asset type, style, size, and palette. The backend returns inline SVG assets and persists a signed-in execution receipt when deployed with managed Postgres.

{session?.authenticated ? "Signed in" : "Sign-in required to run"} {session?.user?.email || session?.email || config.agent?.name} {needsSignIn && signInHref ? Sign in with A2A Cloud : null}
{needsSignIn ?

Sign in to invoke backend tools and persist receipts.

: null} {error ?

{error}

: null}

Generated assets

{result?.status === "ok" ? `${result.svg_assets?.length || 0} SVG asset(s)` : "Preview"}

{result?.receipt_id ? {result.receipt_id} : null}
{result?.warnings?.length ?

{result.warnings.join(" · ")}

: null}
{(result?.svg_assets || []).map((asset) => (
{asset.name}
))} {!result ?

Submit the form to render SVG assets from the live backend.

: null}
); }