From 6a843d94405c6227e79b18faf08c30f8339c3f03 Mon Sep 17 00:00:00 2001 From: a2a-cloud Date: Sat, 18 Jul 2026 07:12:57 +0000 Subject: [PATCH] a2a-source-edit: write frontend/src/App.jsx --- frontend/src/App.jsx | 284 +++++++++++++++++++++++++------------------ 1 file changed, 169 insertions(+), 115 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c73da08..2f1c992 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,158 +1,212 @@ import { useEffect, useMemo, useState } from "react"; -import { - callSkill, - loadAgentConfig, - loadSession, - sampleArgs, - signInUrl, -} from "./a2a.js"; +import { callSkill, loadAgentConfig, loadSession, signInUrl } from "./a2a.js"; + +const MAX_UPLOAD_BYTES = 256 * 1024; +const SUCCESS_FIXTURE = + "This Agreement renews automatically on 2026-12-31 unless either party gives at least 60 days written notice. Invoices are due 30 days after receipt."; + +function emptyForm() { + return { + contract_id: "studio-contract-v1", + title: "Studio Renewal Agreement", + text: SUCCESS_FIXTURE, + file: null, + }; +} export function App() { const [config, setConfig] = useState(null); const [session, setSession] = useState(null); - const [selectedSkillName, setSelectedSkillName] = useState(""); - const [argsText, setArgsText] = useState("{}"); - const [result, setResult] = useState(null); - const [error, setError] = useState(null); + const [form, setForm] = useState(emptyForm); + const [timeline, setTimeline] = useState(null); + const [contracts, setContracts] = useState([]); + const [status, setStatus] = useState({ type: "idle", message: "Paste or upload a contract to begin." }); const [running, setRunning] = useState(false); 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)); + const sessionData = await loadSession(data).catch(() => null); + setSession(sessionData); + if (!data.auth?.invokeRequiresSession || sessionData?.authenticated) { + await refreshContracts(data); } - return loadSession(data).catch(() => null); }) - .then((sessionData) => setSession(sessionData)) - .catch((err) => setError(err.message || String(err))); + .catch((err) => setStatus({ type: "error", message: 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); + const signInHref = useMemo(() => (config && needsSignIn ? signInUrl(config) : null), [config, needsSignIn]); - 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); + async function refreshContracts(activeConfig = config) { + if (!activeConfig) return; + const result = await callSkill(activeConfig, "list_contracts", { limit: 20 }); + if (result?.ok) setContracts(result.contracts || []); } - async function runSkill(event) { + function updateField(name, value) { + setForm((current) => ({ ...current, [name]: value })); + } + + async function fileToBrowserDocument(file) { + if (!file) return null; + if (file.size > MAX_UPLOAD_BYTES) { + throw new Error("Upload is larger than 256 KiB. Paste a shorter text excerpt or choose a smaller file."); + } + const buffer = await file.arrayBuffer(); + let binary = ""; + for (const byte of new Uint8Array(buffer)) binary += String.fromCharCode(byte); + return { + filename: file.name || "contract.txt", + media_type: file.type || "text/plain", + data_base64: btoa(binary), + }; + } + + async function analyze(event) { event.preventDefault(); - if (!config || !selectedSkill) return; + if (!config || running) return; setRunning(true); - setError(null); - setResult(null); + setStatus({ type: "loading", message: "Extracting explicit dated obligations…" }); + setTimeline(null); try { - const args = JSON.parse(argsText || "{}"); - const data = await callSkill(config, selectedSkill.name, args); - setResult(data); + const base = { contract_id: form.contract_id.trim(), title: form.title.trim() }; + const result = form.file + ? await callSkill(config, "analyze_contract_browser_upload", { + ...base, + document: await fileToBrowserDocument(form.file), + }) + : await callSkill(config, "analyze_contract", { ...base, text: form.text }); + setTimeline(result); + if (result.ok) { + setStatus({ type: "success", message: `Saved ${result.deadlines?.length || 0} deadlines with receipt ${result.receipt_id}.` }); + await refreshContracts(); + } else { + setStatus({ type: "error", message: result.message || result.code || "Contract could not be analyzed." }); + } } catch (err) { - setError(err.message || String(err)); + setStatus({ type: "error", message: err.message || String(err) }); + } finally { + setRunning(false); + } + } + + async function reopen(contractId) { + if (!config) return; + setRunning(true); + setStatus({ type: "loading", message: `Reopening ${contractId}…` }); + try { + const result = await callSkill(config, "get_contract_deadlines", { contract_id: contractId }); + setTimeline(result); + setStatus({ type: result.ok ? "success" : "error", message: result.ok ? "Timeline reopened from managed Postgres." : result.message }); + } catch (err) { + setStatus({ type: "error", message: err.message || String(err) }); } finally { setRunning(false); } } if (!config) { - return ( -
-

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

-
- ); + return

{status.message || "Loading ContractClock…"}

; } return ( -
- + -
-
-
-

Skill runner

-

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

+
+
+
+

Analyze

+

Paste or upload a contract

- - Docs - -
+ + +