From e30c60670af6db69bf09cd377bb7231e6aa5136a Mon Sep 17 00:00:00 2001 From: a2a-cloud Date: Sat, 18 Jul 2026 06:06:48 +0000 Subject: [PATCH] a2a-source-edit: write frontend/src/App.jsx --- frontend/src/App.jsx | 358 +++++++++++++------------------------------ 1 file changed, 106 insertions(+), 252 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index fe37b61..6049a59 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,161 +1,68 @@ import { useEffect, useMemo, useState } from "react"; -import { callSkill, loadAgentConfig, loadSession, signInUrl } from "./a2a.js"; - -const MAX_UPLOAD_BYTES = 64 * 1024; -const MAX_UPLOAD_FILES = 2; - -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 }, -]; - -function blankQuote() { - return { vendor: "", unit_price: 0, quantity: 1, delivery_days: 0, warranty_months: 0 }; -} +import { + callSkill, + loadAgentConfig, + loadSession, + sampleArgs, + signInUrl, +} from "./a2a.js"; export function App() { const [config, setConfig] = useState(null); const [session, setSession] = useState(null); - const [comparisonId, setComparisonId] = useState("studio-quote-v1"); - const [quotes, setQuotes] = useState(sampleQuotes); - const [weights, setWeights] = useState({ price: 50, delivery: 30, warranty: 20 }); + const [selectedSkillName, setSelectedSkillName] = useState(""); + const [argsText, setArgsText] = useState("{}"); const [result, setResult] = useState(null); - const [historyId, setHistoryId] = useState("studio-quote-v1"); - const [uploadMessage, setUploadMessage] = useState(""); - const [status, setStatus] = useState("Loading QuoteJudge Studio…"); const [error, setError] = useState(null); const [running, setRunning] = useState(false); useEffect(() => { loadAgentConfig() - .then(async (data) => { + .then((data) => { setConfig(data); - setStatus("Ready to compare vendor quotes."); - try { - setSession(await loadSession(data)); - } catch { - setSession({ authenticated: false }); + const firstSkill = data.skills?.[0]; + if (firstSkill) { + setSelectedSkillName(firstSkill.name); + setArgsText(JSON.stringify(sampleArgs(firstSkill), null, 2)); } + return loadSession(data).catch(() => null); }) - .catch((err) => { - setError(err.message || String(err)); - setStatus("The app could not load its A2A runtime metadata."); - }); + .then((sessionData) => setSession(sessionData)) + .catch((err) => setError(err.message || String(err))); }, []); - const needsSignIn = Boolean(config?.auth?.invokeRequiresSession && session && !session.authenticated); - const signInHref = useMemo(() => (config ? signInUrl(config) : null), [config]); - const skills = useMemo(() => new Set((config?.skills || []).map((skill) => skill.name)), [config]); + const selectedSkill = useMemo( + () => config?.skills?.find((skill) => skill.name === selectedSkillName), + [config, selectedSkillName], + ); - function updateQuote(index, field, value) { - setQuotes((items) => items.map((item, idx) => (idx === index ? { ...item, [field]: value } : item))); + 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 removeQuote(index) { - setQuotes((items) => items.filter((_, idx) => idx !== index)); - } - - function validateForm() { - if (!comparisonId.trim()) return "Comparison id is required."; - if (quotes.length < 2) return "Add at least two vendor quotes."; - if (weights.price + weights.delivery + weights.warranty <= 0) return "At least one weight must be greater than zero."; - for (const quote of quotes) { - if (!quote.vendor.trim()) return "Every quote needs a vendor name."; - if (Number(quote.unit_price) <= 0 || Number(quote.quantity) <= 0) return "Unit price and quantity must be positive."; - } - return null; - } - - async function compare(event) { + async function runSkill(event) { event.preventDefault(); - const problem = validateForm(); - if (problem) { - setError(problem); - return; - } + if (!config || !selectedSkill) return; setRunning(true); setError(null); - setStatus("Scoring quotes and saving the result…"); + setResult(null); try { - const data = await callSkill(config, "compare_quotes", { - comparison_id: comparisonId.trim(), - quotes: quotes.map((quote) => ({ - vendor: 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), - })), - weights: { - price: Number(weights.price), - delivery: Number(weights.delivery), - warranty: Number(weights.warranty), - }, - }); + const args = JSON.parse(argsText || "{}"); + const data = await callSkill(config, selectedSkill.name, args); setResult(data); - setHistoryId(comparisonId.trim()); - setStatus(data.ok ? `Recommended vendor: ${data.recommendation.vendor}` : data.message || "Validation failed."); } catch (err) { setError(err.message || String(err)); - setStatus("The comparison could not run."); - } finally { - setRunning(false); - } - } - - async function reopen() { - setRunning(true); - setError(null); - setStatus("Reopening saved comparison…"); - try { - const data = await callSkill(config, "get_comparison", { comparison_id: historyId.trim() }); - setResult(data); - setStatus(data.ok ? `Reopened ${data.comparison_id}` : data.message || "No saved result found."); - } catch (err) { - setError(err.message || String(err)); - setStatus("The saved comparison could not be reopened."); - } finally { - setRunning(false); - } - } - - async function uploadFiles(event) { - const files = Array.from(event.target.files || []); - event.target.value = ""; - setUploadMessage(""); - if (!files.length) return; - if (files.length > MAX_UPLOAD_FILES) { - setError(`Select at most ${MAX_UPLOAD_FILES} files.`); - return; - } - const documents = []; - for (const file of files) { - if (file.size > MAX_UPLOAD_BYTES) { - setError(`${file.name} is ${file.size} bytes; maximum is ${MAX_UPLOAD_BYTES} bytes.`); - return; - } - const data_base64 = await fileToBase64(file); - documents.push({ filename: file.name, media_type: file.type || "text/plain", data_base64 }); - } - setRunning(true); - setError(null); - setStatus("Parsing uploaded quote file…"); - try { - const data = await callSkill(config, "upload_quotes", { - comparison_id: comparisonId.trim(), - documents, - weights: { - price: Number(weights.price), - delivery: Number(weights.delivery), - warranty: Number(weights.warranty), - }, - }); - setResult(data); - setUploadMessage(data.ok ? "Upload parsed and comparison saved." : data.message || "Upload validation failed."); - setStatus(data.ok ? `Recommended vendor: ${data.recommendation.vendor}` : data.message || "Upload validation failed."); - } catch (err) { - setError(err.message || String(err)); - setStatus("Upload comparison failed."); } finally { setRunning(false); } @@ -164,141 +71,88 @@ export function App() { if (!config) { return (
-

{error || status}

+

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

); } return ( -
-
+
+
+ -
- {status} - {error ? {error} : null} -
- -
-
-
-
-

Workflow

-

Quote comparison

-
- +
+
+
+

Skill runner

+

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

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