From 3a3c222a8c78b1ff122252cb87eed57d3b59e4b0 Mon Sep 17 00:00:00 2001 From: a2a-cloud Date: Sat, 18 Jul 2026 04:46:38 +0000 Subject: [PATCH] a2a-source-edit: write frontend/src/App.jsx --- frontend/src/App.jsx | 356 ++++++++++++++++++++++++++++++------------- 1 file changed, 249 insertions(+), 107 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 6049a59..f127a50 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 sampleQuotes = [ + { vendor: "Acme Bearings", unit_price: 10.75, quantity: 500, delivery_days: 12, warranty_months: 12 }, + { vendor: "Beta Industrial", unit_price: 9.1, quantity: 500, delivery_days: 8, warranty_months: 18 }, +]; + +const defaultWeights = { price: 50, delivery: 30, warranty: 20 }; +const maxUploadBytes = 64 * 1024; +const acceptedTypes = ["application/json", "text/csv", "text/plain"]; export function App() { const [config, setConfig] = useState(null); const [session, setSession] = useState(null); - const [selectedSkillName, setSelectedSkillName] = useState(""); - const [argsText, setArgsText] = useState("{}"); + const [comparisonId, setComparisonId] = useState("studio-quote-v1"); + const [quotesText, setQuotesText] = useState(JSON.stringify(sampleQuotes, null, 2)); + const [weights, setWeights] = useState(defaultWeights); const [result, setResult] = useState(null); + const [history, setHistory] = useState([]); const [error, setError] = useState(null); + const [notice, setNotice] = useState(null); 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(() => ({ authenticated: false })); + setSession(sessionData); + if (!data.auth?.invokeRequiresSession || sessionData?.authenticated) { + refreshHistory(data).catch(() => null); } - 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); + 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 refreshHistory(activeConfig = config) { + if (!activeConfig) return; + const data = await callSkill(activeConfig, "list_comparisons", { limit: 10 }); + if (data?.ok) setHistory(data.comparisons || []); } - async function runSkill(event) { + function parseQuotes() { + try { + const parsed = JSON.parse(quotesText); + if (!Array.isArray(parsed)) throw new Error("Paste a JSON array of quote objects."); + return parsed; + } catch (err) { + throw new Error(`Quote JSON is invalid: ${err.message || String(err)}`); + } + } + + async function compare(event) { event.preventDefault(); - if (!config || !selectedSkill) return; + if (!config) return; setRunning(true); setError(null); - setResult(null); + setNotice(null); try { - const args = JSON.parse(argsText || "{}"); - const data = await callSkill(config, selectedSkill.name, args); + const data = await callSkill(config, "compare_quotes", { + comparison_id: comparisonId.trim(), + quotes: parseQuotes(), + weights, + }); setResult(data); + if (!data.ok) setNotice(data.message || "Validation failed. Check the highlighted response."); + if (data.ok) await refreshHistory(); } catch (err) { setError(err.message || String(err)); } finally { @@ -68,91 +76,225 @@ export function App() { } } + async function reopen(id) { + if (!config) return; + setRunning(true); + setError(null); + setNotice(null); + try { + const data = await callSkill(config, "get_comparison", { comparison_id: id }); + setComparisonId(id); + setResult(data); + if (!data.ok) setNotice(data.message || "Comparison not found."); + } catch (err) { + setError(err.message || String(err)); + } finally { + setRunning(false); + } + } + + async function handleUpload(event) { + const files = Array.from(event.target.files || []); + if (!files.length || !config) return; + setRunning(true); + setError(null); + setNotice(null); + try { + const documents = await Promise.all(files.map(fileToBrowserDocument)); + const data = await callSkill(config, "compare_quotes_from_browser_upload", { + comparison_id: comparisonId.trim(), + documents, + weights, + }); + setResult(data); + if (!data.ok) setNotice(data.message || "Upload validation failed."); + if (data.ok) await refreshHistory(); + } catch (err) { + setError(err.message || String(err)); + } finally { + setRunning(false); + event.target.value = ""; + } + } + if (!config) { - return ( -
-

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

-
- ); + return

{error || "Loading QuoteJudge Studio..."}

; } return (
- + -
-
-
-

Skill runner

-

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

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