diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 2f1c992..c73da08 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,211 +1,157 @@ import { useEffect, useMemo, useState } from "react"; -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, - }; -} +import { + callSkill, + loadAgentConfig, + loadSession, + sampleArgs, + signInUrl, +} from "./a2a.js"; export function App() { const [config, setConfig] = useState(null); const [session, setSession] = 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 [selectedSkillName, setSelectedSkillName] = useState(""); + const [argsText, setArgsText] = useState("{}"); + const [result, setResult] = useState(null); + const [error, setError] = useState(null); const [running, setRunning] = useState(false); useEffect(() => { loadAgentConfig() - .then(async (data) => { + .then((data) => { setConfig(data); - const sessionData = await loadSession(data).catch(() => null); - setSession(sessionData); - if (!data.auth?.invokeRequiresSession || sessionData?.authenticated) { - await refreshContracts(data); + const firstSkill = data.skills?.[0]; + if (firstSkill) { + setSelectedSkillName(firstSkill.name); + setArgsText(JSON.stringify(sampleArgs(firstSkill), null, 2)); } + return loadSession(data).catch(() => null); }) - .catch((err) => setStatus({ type: "error", message: err.message || String(err) })); + .then((sessionData) => setSession(sessionData)) + .catch((err) => setError(err.message || String(err))); }, []); - const needsSignIn = Boolean(config?.auth?.invokeRequiresSession && session && !session.authenticated); - const signInHref = useMemo(() => (config && needsSignIn ? signInUrl(config) : null), [config, needsSignIn]); + const selectedSkill = useMemo( + () => config?.skills?.find((skill) => skill.name === selectedSkillName), + [config, selectedSkillName], + ); - async function refreshContracts(activeConfig = config) { - if (!activeConfig) return; - const result = await callSkill(activeConfig, "list_contracts", { limit: 20 }); - if (result?.ok) setContracts(result.contracts || []); + 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 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) { + async function runSkill(event) { event.preventDefault(); - if (!config || running) return; + if (!config || !selectedSkill) return; setRunning(true); - setStatus({ type: "loading", message: "Extracting explicit dated obligations…" }); - setTimeline(null); + setError(null); + setResult(null); try { - 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." }); - } + const args = JSON.parse(argsText || "{}"); + const data = await callSkill(config, selectedSkill.name, args); + setResult(data); } catch (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) }); + setError(err.message || String(err)); } finally { setRunning(false); } } if (!config) { - return

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

; + return ( +
+

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

+
+ ); } return ( -
-
+
+
+ -
-
-
-

Analyze

-

Paste or upload a contract

+
+
+
+

Skill runner

+

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

- - -