From 6b8b63e26d5507427e590e25a29834b98b692a55 Mon Sep 17 00:00:00 2001 From: a2a-cloud Date: Sat, 18 Jul 2026 05:11:42 +0000 Subject: [PATCH] a2a-source-edit: write frontend/src/App.jsx --- frontend/src/App.jsx | 319 ++++++++++++++++++++++++++++--------------- 1 file changed, 207 insertions(+), 112 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 6049a59..7b4deb4 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,158 +1,253 @@ import { useEffect, useMemo, useState } from "react"; import { - callSkill, + callSkillWithSession, + fileToBrowserDocument, loadAgentConfig, loadSession, - sampleArgs, signInUrl, } from "./a2a.js"; +const MAX_UPLOAD_BYTES = 256000; +const STARTER_QUOTES = [ + { vendor: "Acme Bearings", unit_price: 10.75, quantity: 500, delivery_days: 12, warranty_months: 12, currency: "BRL" }, + { vendor: "Beta Industrial", unit_price: 9.1, quantity: 500, delivery_days: 8, warranty_months: 18, currency: "BRL" }, +]; + 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(STARTER_QUOTES); + const [weights, setWeights] = useState({ price: 50, delivery: 30, warranty: 20 }); const [result, setResult] = useState(null); - const [error, setError] = useState(null); + const [status, setStatus] = useState("Loading QuoteJudge Studio…"); const [running, setRunning] = useState(false); + const [files, setFiles] = useState([]); 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); + setStatus("Ready to compare vendor quotes."); }) - .then((sessionData) => setSession(sessionData)) - .catch((err) => setError(err.message || String(err))); + .catch((error) => setStatus(error.message || String(error))); }, []); - 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 totalWeight = Number(weights.price || 0) + Number(weights.delivery || 0) + Number(weights.warranty || 0); - 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 updateQuote(index, field, value) { + setQuotes((current) => current.map((quote, i) => (i === index ? { ...quote, [field]: value } : quote))); } - async function runSkill(event) { + function addQuote() { + setQuotes((current) => [...current, { vendor: "", unit_price: 0, quantity: 1, delivery_days: 0, warranty_months: 0, currency: "BRL" }]); + } + + function removeQuote(index) { + setQuotes((current) => current.filter((_, i) => i !== index)); + } + + function normalizeQuotes() { + return quotes.map((quote) => ({ + vendor: String(quote.vendor || "").trim(), + unit_price: Number(quote.unit_price), + quantity: Number(quote.quantity), + delivery_days: Number(quote.delivery_days), + warranty_months: Number(quote.warranty_months), + currency: quote.currency || "BRL", + })); + } + + async function runCompare(event) { event.preventDefault(); - if (!config || !selectedSkill) return; + if (!config) return; setRunning(true); - setError(null); setResult(null); + setStatus("Scoring quotes and saving result…"); try { - const args = JSON.parse(argsText || "{}"); - const data = await callSkill(config, selectedSkill.name, args); + const data = await callSkillWithSession(config, "compare_quotes", { + comparison_id: comparisonId, + quotes: normalizeQuotes(), + weights: { + price: Number(weights.price), + delivery: Number(weights.delivery), + warranty: Number(weights.warranty), + }, + }); setResult(data); - } catch (err) { - setError(err.message || String(err)); + setStatus(data.ok ? `Saved ${data.comparison_id}. Recommendation: ${data.recommendation.vendor}.` : data.message); + } catch (error) { + setStatus(error.message || String(error)); + } finally { + setRunning(false); + } + } + + async function reopen() { + if (!config) return; + setRunning(true); + setResult(null); + setStatus("Reopening saved comparison…"); + try { + const data = await callSkillWithSession(config, "get_comparison", { comparison_id: comparisonId }); + setResult(data); + setStatus(data.ok ? `Reopened ${data.comparison_id}.` : data.message); + } catch (error) { + setStatus(error.message || String(error)); + } finally { + setRunning(false); + } + } + + async function uploadAndCompare() { + if (!config || !files.length) return; + setRunning(true); + setResult(null); + setStatus("Encoding browser upload and comparing…"); + try { + const tooLarge = files.find((file) => file.size > MAX_UPLOAD_BYTES); + if (tooLarge) throw new Error(`${tooLarge.name} exceeds ${MAX_UPLOAD_BYTES} bytes.`); + const documents = await Promise.all(files.map(fileToBrowserDocument)); + const data = await callSkillWithSession(config, "compare_uploaded_quotes_browser", { + comparison_id: comparisonId, + documents, + weights: { + price: Number(weights.price), + delivery: Number(weights.delivery), + warranty: Number(weights.warranty), + }, + }); + setResult(data); + setStatus(data.ok ? `Uploaded and saved ${data.comparison_id}.` : data.message); + } catch (error) { + setStatus(error.message || String(error)); } finally { setRunning(false); } } if (!config) { - return ( -
-

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

-
- ); + return

{status}

; } return ( -
- + -
-
-
-

Skill runner

-

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

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