diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5d2e1a9..e4c27b0 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,66 +1,74 @@ import { useEffect, useMemo, useState } from "react"; -import { - callSkill, - loadAgentConfig, - loadSession, - sampleArgs, - signInUrl, -} from "./a2a.js"; +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 [selectedSkillName, setSelectedSkillName] = useState(""); - const [argsText, setArgsText] = useState("{}"); + 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); - const firstSkill = data.skills?.[0]; - if (firstSkill) { - setSelectedSkillName(firstSkill.name); - setArgsText(JSON.stringify(sampleArgs(firstSkill), null, 2)); - } - return loadSession(data).catch(() => null); + return loadSession(data).catch(() => ({ authenticated: false })); }) .then((sessionData) => setSession(sessionData)) .catch((err) => setError(err.message || String(err))); }, []); - const selectedSkill = useMemo( - () => config?.skills?.find((skill) => skill.name === selectedSkillName), - [config, selectedSkillName], + 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]); - const needsSignIn = Boolean( - config?.auth?.invokeRequiresSession && session && !session.authenticated, - ); - const signInHref = useMemo( - () => (config && needsSignIn ? signInUrl(config) : null), - [config, needsSignIn], - ); - - function selectSkill(skill) { - setSelectedSkillName(skill.name); - setArgsText(JSON.stringify(sampleArgs(skill), null, 2)); - setResult(null); - setError(null); + function update(field, value) { + setForm((current) => ({ ...current, [field]: value })); } - async function runSkill(event) { + 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 || !selectedSkill) return; + if (!config || !generateSkill) return; setRunning(true); setError(null); setResult(null); try { - const args = JSON.parse(argsText || "{}"); - const data = await callSkill(config, selectedSkill.name, args); + 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 { @@ -68,90 +76,137 @@ export function App() { } } + 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 A2A agent contract..."}

-
- ); + return

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

; } return (
- + -
-
-
-

Skill runner

-

{selectedSkill?.name || "No skills found"}

-
- - Docs - -
- - {selectedSkill ? ( -
+
+ +