diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index f127a50..6049a59 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,74 +1,66 @@ import { useEffect, useMemo, useState } from "react"; -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"]; +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 [quotesText, setQuotesText] = useState(JSON.stringify(sampleQuotes, null, 2)); - const [weights, setWeights] = useState(defaultWeights); + const [selectedSkillName, setSelectedSkillName] = useState(""); + const [argsText, setArgsText] = useState("{}"); 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(async (data) => { + .then((data) => { setConfig(data); - const sessionData = await loadSession(data).catch(() => ({ authenticated: false })); - setSession(sessionData); - if (!data.auth?.invokeRequiresSession || sessionData?.authenticated) { - refreshHistory(data).catch(() => null); + const firstSkill = data.skills?.[0]; + if (firstSkill) { + setSelectedSkillName(firstSkill.name); + setArgsText(JSON.stringify(sampleArgs(firstSkill), null, 2)); } + return loadSession(data).catch(() => null); }) + .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 refreshHistory(activeConfig = config) { - if (!activeConfig) return; - const data = await callSkill(activeConfig, "list_comparisons", { limit: 10 }); - if (data?.ok) setHistory(data.comparisons || []); + 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 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) { + async function runSkill(event) { event.preventDefault(); - if (!config) return; + if (!config || !selectedSkill) return; setRunning(true); setError(null); - setNotice(null); + setResult(null); try { - const data = await callSkill(config, "compare_quotes", { - comparison_id: comparisonId.trim(), - quotes: parseQuotes(), - weights, - }); + const args = JSON.parse(argsText || "{}"); + const data = await callSkill(config, selectedSkill.name, args); 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 { @@ -76,225 +68,91 @@ 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 QuoteJudge Studio..."}

; + return ( +
+

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

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

Workflow

-

Compare quotes

-
- + ))} + + +
+ {session?.authenticated ? "Signed in" : needsSignIn ? "Signed out" : "Local session"} + {session?.user?.email || session?.org?.slug || "dev@example.local"} + {/* Skills that spend the caller's LLM budget need a signed-in + visitor even when the page itself is public, so offer the way in + rather than failing at the first click. */} + {needsSignIn && signInHref ? ( + Sign in to run + ) : null} +
+ + +
+
+
+

Skill runner

+

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

+ + Docs + +
- - -
- Weights - {Object.keys(defaultWeights).map((key) => ( - - ))} -
- -