diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 9718b8e..a0102ea 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,66 +1,79 @@ import { useEffect, useMemo, useState } from "react"; import { + MAX_CSV_BYTES, callSkill, + fileToBrowserUpload, loadAgentConfig, loadSession, - sampleArgs, signInUrl, + unwrapA2AResult, } from "./a2a.js"; +const SAMPLE_CSV = "month,revenue\nJanuary,100\nFebruary,120\nMarch,150\n"; + export function App() { const [config, setConfig] = useState(null); const [session, setSession] = useState(null); - const [selectedSkillName, setSelectedSkillName] = useState(""); - const [argsText, setArgsText] = useState("{}"); + const [analysisId, setAnalysisId] = useState("studio-csv-v1"); + const [question, setQuestion] = useState("Which month has the highest revenue?"); + const [csvText, setCsvText] = useState(SAMPLE_CSV); + const [file, setFile] = useState(null); const [result, setResult] = useState(null); + const [history, setHistory] = useState([]); + const [status, setStatus] = useState("Loading CSVAnswers..."); const [error, setError] = useState(null); const [running, setRunning] = useState(false); useEffect(() => { loadAgentConfig() - .then((data) => { - setConfig(data); - const firstSkill = data.skills?.[0]; - if (firstSkill) { - setSelectedSkillName(firstSkill.name); - setArgsText(JSON.stringify(sampleArgs(firstSkill), null, 2)); + .then(async (loaded) => { + setConfig(loaded); + try { + const sessionData = await loadSession(loaded); + setSession(sessionData); + if (sessionData?.authenticated) await refreshHistory(loaded); + } catch { + setSession({ authenticated: false }); } - return loadSession(data).catch(() => null); + setStatus(null); }) - .then((sessionData) => setSession(sessionData)) - .catch((err) => setError(err.message || String(err))); + .catch((err) => { + setError(err.message || String(err)); + setStatus(null); + }); }, []); - const selectedSkill = useMemo( - () => config?.skills?.find((skill) => skill.name === selectedSkillName), - [config, selectedSkillName], - ); + const needsSignIn = Boolean(config?.auth?.invokeRequiresSession && !session?.authenticated); + const signInHref = useMemo(() => (config ? signInUrl(config) : null), [config]); + const maxKb = Math.round(MAX_CSV_BYTES / 1024); - 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; + try { + const response = await callSkill(activeConfig, "list_analyses", { limit: 10 }); + const data = unwrapA2AResult(response); + if (data?.ok) setHistory(data.analyses || []); + } catch (err) { + if (!String(err.message || err).includes("session")) console.warn(err); + } } - async function runSkill(event) { + async function runAnalysis(event) { event.preventDefault(); - if (!config || !selectedSkill) return; + if (!config) return; setRunning(true); setError(null); setResult(null); try { - const args = JSON.parse(argsText || "{}"); - const data = await callSkill(config, selectedSkill.name, args); + const args = { analysis_id: analysisId.trim(), question: question.trim() }; + const skill = file ? "analyze_csv_upload_base64" : "analyze_csv"; + if (file) args.upload = await fileToBrowserUpload(file); + else args.csv_text = csvText; + const response = await callSkill(config, skill, args); + const data = unwrapA2AResult(response); setResult(data); + if (!data?.ok) setError(data?.message || data?.code || "CSV analysis failed."); + else await refreshHistory(config); } catch (err) { setError(err.message || String(err)); } finally { @@ -68,91 +81,133 @@ export function App() { } } - if (!config) { - return ( -
-

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

-
- ); + async function reopen(id = analysisId) { + if (!config || !id) return; + setRunning(true); + setError(null); + try { + const response = await callSkill(config, "get_analysis", { analysis_id: id.trim() }); + const data = unwrapA2AResult(response); + setResult(data); + if (data?.ok) setAnalysisId(data.analysis_id); + else setError(data?.message || data?.code || "Analysis was not found."); + } catch (err) { + setError(err.message || String(err)); + } finally { + setRunning(false); + } } + function onFileChange(event) { + const selected = event.target.files?.[0] || null; + setError(null); + if (selected && selected.size > MAX_CSV_BYTES) { + setError(`CSV upload must be at most ${maxKb} KB.`); + event.target.value = ""; + setFile(null); + return; + } + setFile(selected); + } + + if (status) return

{status}

; + return ( -
- + -
-
-
-

Skill runner

-

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

-
- - Docs - -
+ {error ?
{error}
: null} - {selectedSkill ? ( -
+
+ +