diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 07548df..1a62dd3 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,66 +1,59 @@ import { useEffect, useMemo, useState } from "react"; -import { - callSkill, - loadAgentConfig, - loadSession, - sampleArgs, - signInUrl, -} from "./a2a.js"; +import { callSkill, loadAgentConfig, loadSession, signInUrl } from "./a2a.js"; + +const fixtureUpdates = [ + { account: "Acme Manufacturing", owner: "Priya", stage: "Security review", amount_usd: 84000, forecast_category: "commit", status: "blocked", days_in_stage: 18, update: "Waiting on vendor risk questionnaire." }, + { account: "Northstar Health", owner: "Mateo", stage: "Proposal", amount_usd: 52000, forecast_category: "best_case", status: "at_risk", days_in_stage: 11, update: "Economic buyer asked for ROI proof." }, + { account: "Bluebird Logistics", owner: "Priya", stage: "Discovery", amount_usd: 27000, forecast_category: "pipeline", status: "on_track", days_in_stage: 4, update: "Expansion pain confirmed." }, + { account: "Summit Retail", owner: "Nora", stage: "Procurement", amount_usd: 125000, forecast_category: "commit", status: "slipped", days_in_stage: 31, update: "Legal redlines moved close date." }, +]; + +const defaultRequest = { + dashboard_name: "Weekly Sales Ops Pulse", + segment: "Mid-market", + period_label: "This week", + min_exception_amount_usd: 25000, + updates: fixtureUpdates, + notes: "Paste weekly deal updates here later; the demo fixture is prefilled so the first useful dashboard is one click away.", +}; export function App() { const [config, setConfig] = useState(null); const [session, setSession] = useState(null); - const [selectedSkillName, setSelectedSkillName] = useState(""); - const [argsText, setArgsText] = useState("{}"); + const [requestText, setRequestText] = useState(JSON.stringify(defaultRequest, null, 2)); 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)); + const sessionData = await loadSession(data).catch(() => null); + setSession(sessionData); + if (!data.auth?.invokeRequiresSession || sessionData?.authenticated) { + callSkill(data, "list_dashboard_history", { limit: 5 }) + .then((payload) => setHistory(payload.history || [])) + .catch(() => setHistory([])); } - 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 && needsSignIn ? signInUrl(config) : null), [config, needsSignIn]); - 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 runSkill(event) { + async function buildDashboard(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); - setResult(data); + const parsed = JSON.parse(requestText); + const payload = await callSkill(config, "build_dashboard", { request: parsed }); + setResult(payload); + setHistory(payload.history || history); } catch (err) { setError(err.message || String(err)); } finally { @@ -68,91 +61,140 @@ export function App() { } } - if (!config) { - return ( -
-

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

-
- ); + function downloadCsv() { + const artifact = result?.artifact; + if (!artifact?.content) return; + const blob = new Blob([artifact.content], { type: artifact.media_type || "text/csv" }); + const url = URL.createObjectURL(blob); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = artifact.name || "sales-ops-dashboard.csv"; + document.body.appendChild(anchor); + anchor.click(); + anchor.remove(); + URL.revokeObjectURL(url); } + if (!config) { + return

{error || "Loading dashboard app..."}

; + } + + const rows = result?.["dashboard-data"] || []; + const trends = result?.trends || []; + const exceptions = result?.exceptions || []; + const filters = result?.filters || {}; + return ( -
- + -
-
-
-

Skill runner

-

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

+
+
+
+

One-click fixture

+

Build dashboard

- - Docs - -
+