From 899da272c29464a40f8e02a985ed1e86fe9d036a Mon Sep 17 00:00:00 2001 From: a2a-cloud Date: Sat, 18 Jul 2026 10:13:25 +0000 Subject: [PATCH] a2a-source-edit: write frontend/src/App.jsx --- frontend/src/App.jsx | 243 +++++++++++++++++++++++++------------------ 1 file changed, 143 insertions(+), 100 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c1efad7..81ecd1f 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,41 +1,39 @@ import { useEffect, useMemo, useState } from "react"; import { callSkill, + fileToBrowserDocument, + fixtureInvoices, loadAgentConfig, loadSession, - sampleArgs, signInUrl, } from "./a2a.js"; +const defaultCaseId = "studio-invoice-v1"; + export function App() { const [config, setConfig] = useState(null); const [session, setSession] = useState(null); - const [selectedSkillName, setSelectedSkillName] = useState(""); - const [argsText, setArgsText] = useState("{}"); + const [caseId, setCaseId] = useState(defaultCaseId); + const [invoiceText, setInvoiceText] = useState(JSON.stringify(fixtureInvoices(), null, 2)); + const [documents, setDocuments] = useState([]); const [result, setResult] = useState(null); const [error, setError] = useState(null); const [running, setRunning] = useState(false); + const [decisionNote, setDecisionNote] = useState("Duplicate INV-100 needs AP review; INV-200 total does not match subtotal + tax."); useEffect(() => { loadAgentConfig() - .then((data) => { + .then(async (data) => { setConfig(data); - const firstSkill = data.skills?.[0]; - if (firstSkill) { - setSelectedSkillName(firstSkill.name); - setArgsText(JSON.stringify(sampleArgs(firstSkill), null, 2)); + try { + setSession(await loadSession(data)); + } catch { + setSession({ authenticated: false }); } - return loadSession(data).catch(() => null); }) - .then((sessionData) => setSession(sessionData)) .catch((err) => setError(err.message || String(err))); }, []); - const selectedSkill = useMemo( - () => config?.skills?.find((skill) => skill.name === selectedSkillName), - [config, selectedSkillName], - ); - const needsSignIn = Boolean( config?.auth?.invokeRequiresSession && session && !session.authenticated, ); @@ -44,115 +42,160 @@ export function App() { [config, needsSignIn], ); - function selectSkill(skill) { - setSelectedSkillName(skill.name); - setArgsText(JSON.stringify(sampleArgs(skill), null, 2)); - setResult(null); - setError(null); - } - - async function runSkill(event) { - event.preventDefault(); - if (!config || !selectedSkill) return; + async function invoke(skillName, args) { + if (!config) return null; 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, skillName, args); setResult(data); + return data; } catch (err) { - setError(err.message || String(err)); + if (err.status === 401 && signInUrl(config)) { + setError("Your session expired. Use Sign in to run, then retry."); + } else { + setError(err.message || String(err)); + } + return null; } finally { setRunning(false); } } + async function reviewPasted(event) { + event.preventDefault(); + let invoices; + try { + invoices = JSON.parse(invoiceText); + } catch { + setError("Paste valid JSON: an array of invoice objects."); + return; + } + await invoke("review_invoices", { case_id: caseId, invoices }); + } + + async function reviewUploads(event) { + event.preventDefault(); + if (!documents.length) { + setError("Choose a JSON, CSV, or text invoice file first."); + return; + } + await invoke("review_invoice_uploads", { case_id: caseId, documents }); + } + + async function reopenCase() { + await invoke("get_invoice_case", { case_id: caseId }); + } + + async function recordDecision() { + await invoke("record_decision", { + case_id: caseId, + decision: { + decision_id: "ap-review-1", + invoice_number: "INV-100", + decision: "needs_review", + note: decisionNote, + }, + }); + } + + async function onFilesChanged(event) { + setError(null); + const files = Array.from(event.target.files || []); + if (files.length > 5) { + setError("Choose at most five files."); + return; + } + try { + setDocuments(await Promise.all(files.map(fileToBrowserDocument))); + } catch (err) { + setError(err.message || String(err)); + } + } + if (!config) { - return ( -
-

{error || "Loading A2A agent contract..."}

-
- ); + return

{error || "Loading InvoiceGuard..."}

; } return ( -
- + -
-
-
-

Skill runner

-

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

+
+
+

1. Review invoices

+ +