From 27a7cd800f493b8d73a692e2c4d2ba603554923a Mon Sep 17 00:00:00 2001 From: a2a-cloud Date: Sat, 18 Jul 2026 06:48:43 +0000 Subject: [PATCH] a2a-source-edit: write frontend/src/App.jsx --- frontend/src/App.jsx | 294 ++++++++++++++++++++++++++++--------------- 1 file changed, 193 insertions(+), 101 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 6049a59..8b55983 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,66 +1,114 @@ import { useEffect, useMemo, useState } from "react"; import { callSkill, + fileToBrowserDocument, loadAgentConfig, loadSession, - sampleArgs, signInUrl, + successFixture, } from "./a2a.js"; +const emptyQuote = { vendor: "", unit_price: 0, quantity: 1, delivery_days: 1, warranty_months: 0 }; + 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 [quotes, setQuotes] = useState(successFixture().quotes); + const [weights, setWeights] = useState(successFixture().weights); + const [documents, setDocuments] = useState([]); const [result, setResult] = useState(null); + const [history, setHistory] = useState([]); const [error, setError] = 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)); + 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); + const signInHref = useMemo(() => (config ? signInUrl(config) : null), [config]); + const totalWeight = Number(weights.price) + Number(weights.delivery) + Number(weights.warranty); - 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 setQuote(index, field, value) { + setQuotes((items) => items.map((quote, i) => (i === index ? { ...quote, [field]: value } : quote))); } - async function runSkill(event) { + function normalizeQuote(quote) { + return { + vendor: quote.vendor.trim(), + unit_price: Number(quote.unit_price), + quantity: Number.parseInt(quote.quantity, 10), + delivery_days: Number.parseInt(quote.delivery_days, 10), + warranty_months: Number.parseInt(quote.warranty_months, 10), + }; + } + + function normalizeWeights() { + return { + price: Number(weights.price), + delivery: Number(weights.delivery), + warranty: Number(weights.warranty), + }; + } + + function validate() { + if (needsSignIn) return "Sign in to run QuoteJudge."; + if (!comparisonId.trim()) return "Comparison ID is required."; + if (documents.length === 0 && quotes.length < 2) return "Enter at least two quotes."; + if (totalWeight <= 0) return "At least one weight must be greater than zero."; + for (const quote of quotes) { + if (documents.length === 0 && !quote.vendor.trim()) return "Every quote needs a vendor name."; + } + return null; + } + + async function runComparison(event) { event.preventDefault(); - if (!config || !selectedSkill) return; + if (!config) return; + const problem = validate(); + if (problem) { + setError(problem); + return; + } setRunning(true); setError(null); setResult(null); try { - const args = JSON.parse(argsText || "{}"); - const data = await callSkill(config, selectedSkill.name, args); + const skill = documents.length ? "compare_quotes_browser_upload" : "compare_quotes"; + const args = documents.length + ? { comparison_id: comparisonId.trim(), weights: normalizeWeights(), documents } + : { comparison_id: comparisonId.trim(), quotes: quotes.map(normalizeQuote), weights: normalizeWeights() }; + const data = await callSkill(config, skill, args); + if (!data?.ok) throw new Error(data?.message || data?.code || "Comparison failed."); setResult(data); + setHistory((items) => [data, ...items.filter((item) => item.comparison_id !== data.comparison_id)].slice(0, 5)); + } catch (err) { + if (err.status === 401 && signInHref) setError("Session expired. Sign in again to continue."); + else setError(err.message || String(err)); + } finally { + setRunning(false); + } + } + + async function reloadComparison(id = comparisonId) { + if (!config) return; + setRunning(true); + setError(null); + try { + const data = await callSkill(config, "get_comparison", { comparison_id: id.trim() }); + if (!data?.ok) throw new Error(data?.message || data?.code || "No saved comparison found."); + setResult(data); + setHistory((items) => [data, ...items.filter((item) => item.comparison_id !== data.comparison_id)].slice(0, 5)); } catch (err) { setError(err.message || String(err)); } finally { @@ -68,90 +116,134 @@ export function App() { } } + async function onFilesSelected(event) { + const files = Array.from(event.target.files || []); + setError(null); + try { + const docs = []; + for (const file of files.slice(0, 3)) docs.push(await fileToBrowserDocument(file)); + setDocuments(docs); + } catch (err) { + setError(err.message || String(err)); + setDocuments([]); + } + } + if (!config) { - return ( -
-

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

-
- ); + return

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

; } return (
- + -
-
-
-

Skill runner

-

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

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