From b28c3384b50be2871806cd5b5a9cec43ada79f6a Mon Sep 17 00:00:00 2001 From: a2a-cloud Date: Sat, 18 Jul 2026 08:12:25 +0000 Subject: [PATCH] a2a-source-edit: write frontend/src/App.jsx --- frontend/src/App.jsx | 289 ++++++++++++++++++++++++++++--------------- 1 file changed, 187 insertions(+), 102 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c73da08..e23e15a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,156 +1,241 @@ import { useEffect, useMemo, useState } from "react"; import { callSkill, + fileToBrowserDocument, loadAgentConfig, loadSession, - sampleArgs, + needsInvokeSession, signInUrl, } from "./a2a.js"; +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 formatDeadline(deadline) { + return `${deadline.kind}: ${deadline.date}`; +} + export function App() { const [config, setConfig] = useState(null); const [session, setSession] = useState(null); - const [selectedSkillName, setSelectedSkillName] = useState(""); - const [argsText, setArgsText] = useState("{}"); + const [contractId, setContractId] = useState("studio-contract-v1"); + const [title, setTitle] = useState("Studio Renewal Agreement"); + const [text, setText] = useState(SUCCESS_FIXTURE); + const [file, setFile] = useState(null); const [result, setResult] = useState(null); + const [history, setHistory] = useState([]); + const [status, setStatus] = useState("Loading ContractClock..."); const [error, setError] = useState(null); const [running, setRunning] = useState(false); useEffect(() => { + let cancelled = false; loadAgentConfig() - .then((data) => { - setConfig(data); - const firstSkill = data.skills?.[0]; - if (firstSkill) { - setSelectedSkillName(firstSkill.name); - setArgsText(JSON.stringify(sampleArgs(firstSkill), null, 2)); + .then(async (cfg) => { + if (cancelled) return; + setConfig(cfg); + try { + const sessionData = await loadSession(cfg); + if (!cancelled) setSession(sessionData); + } catch { + if (!cancelled) setSession({ authenticated: false }); } - return loadSession(data).catch(() => null); + if (!cancelled) setStatus("Ready"); }) - .then((sessionData) => setSession(sessionData)) - .catch((err) => setError(err.message || String(err))); + .catch((err) => { + if (!cancelled) { + setError(err.message || String(err)); + setStatus("Unable to load app metadata"); + } + }); + return () => { + cancelled = true; + }; }, []); - const selectedSkill = useMemo( - () => config?.skills?.find((skill) => skill.name === selectedSkillName), - [config, selectedSkillName], - ); + const signInHref = useMemo(() => (config ? signInUrl(config) : null), [config]); + const needsSignIn = needsInvokeSession(config, session); + const skills = config?.skills || []; + const hasBase64Bridge = skills.some((skill) => skill.name === "analyze_contract_upload_base64"); + const hasFileUploadBridge = skills.some((skill) => skill.name === "analyze_contract_file"); - 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 rememberTimeline(data) { + if (!data?.ok) return; + setHistory((current) => { + const next = current.filter((item) => item.contract_id !== data.contract_id); + return [{ contract_id: data.contract_id, title: data.title, deadlines: data.deadlines || [] }, ...next].slice(0, 8); + }); } - async function runSkill(event) { - event.preventDefault(); - if (!config || !selectedSkill) return; + async function invoke(skill, args) { + if (!config) return null; setRunning(true); setError(null); - setResult(null); + setStatus(`Running ${skill}...`); try { - const args = JSON.parse(argsText || "{}"); - const data = await callSkill(config, selectedSkill.name, args); + const data = await callSkill(config, skill, args); setResult(data); + if (data?.ok) { + rememberTimeline(data); + setStatus("Timeline saved"); + } else { + setStatus(data?.code ? `Stopped: ${data.code}` : "Stopped"); + } + return data; } catch (err) { - setError(err.message || String(err)); + if (err.status === 401 && signInHref) { + setError("Your session expired. Sign in again to run ContractClock."); + } else { + setError(err.message || String(err)); + } + setStatus("Request failed"); + return null; } finally { setRunning(false); } } + async function analyzePaste(event) { + event.preventDefault(); + await invoke("analyze_contract", { contract_id: contractId, title, text }); + } + + async function analyzeUpload() { + if (!file) { + setError("Choose a plain-text contract file first."); + return; + } + try { + const document = await fileToBrowserDocument(file); + await invoke("analyze_contract_upload_base64", { contract_id: contractId, title, document }); + } catch (err) { + setError(err.message || String(err)); + setStatus("Upload rejected"); + } + } + + async function reloadTimeline(id = contractId) { + await invoke("get_contract_deadlines", { contract_id: id }); + } + if (!config) { return ( -
-

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

+
+

{error || status}

); } return (
- +
+ Managed Postgres + Standard MCP: /mcp + Receipts persisted + {hasBase64Bridge ? "Browser upload bridge" : "Upload bridge missing"} + {hasFileUploadBridge ? "Typed FileUpload bridge" : "FileUpload missing"} +
+ -
-
-
-

Skill runner

-

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

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