From ce0b40c9021f88c48a76227fd8813cf3ab034eec Mon Sep 17 00:00:00 2001 From: a2a-cloud Date: Sun, 19 Jul 2026 18:58:32 +0000 Subject: [PATCH] a2a-source-edit: write frontend/src/App.jsx --- frontend/src/App.jsx | 286 ++++++++++++++++++++++++++----------------- 1 file changed, 172 insertions(+), 114 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 00ded85..6e758a4 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,17 +1,58 @@ import { useEffect, useMemo, useState } from "react"; -import { - callSkill, - loadAgentConfig, - loadSession, - sampleArgs, - signInUrl, -} from "./a2a.js"; +import { callSkill, loadAgentConfig, loadSession, signInUrl } from "./a2a.js"; + +const FIXTURE = { + client_name: "Northstar Property Group", + property_name: "Maple Ridge Apartments", + property_type: "multifamily", + city: "Austin", + goal: "owner update", + tone: "professional", + include_next_steps: true, + intake: + "Owner wants a concise update on leasing momentum, two delayed make-readies, and how the team will prevent renewal friction this month. Traffic is steady but follow-up is inconsistent. Maintenance has three open turns, one vendor delay, and residents are asking for clearer completion dates.", + uploaded_documents: [], +}; + +function fileToBase64(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const value = String(reader.result || ""); + resolve(value.includes(",") ? value.split(",").pop() : value); + }; + reader.onerror = () => reject(reader.error || new Error("Unable to read file")); + reader.readAsDataURL(file); + }); +} + +function downloadDescriptor(descriptor, fallbackText) { + const filename = descriptor?.filename || "high-utility-one-page-startups-w-e3c7-3-output.md"; + const mediaType = descriptor?.media_type || "text/markdown"; + let bytes; + if (descriptor?.content_base64) { + const binary = atob(descriptor.content_base64); + bytes = new Uint8Array(binary.length); + for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index); + } else { + bytes = new TextEncoder().encode(fallbackText || ""); + } + const blob = new Blob([bytes], { type: mediaType }); + const url = URL.createObjectURL(blob); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = filename; + document.body.appendChild(anchor); + anchor.click(); + anchor.remove(); + URL.revokeObjectURL(url); +} export function App() { const [config, setConfig] = useState(null); const [session, setSession] = useState(null); - const [selectedSkillName, setSelectedSkillName] = useState(""); - const [argsText, setArgsText] = useState("{}"); + const [form, setForm] = useState(FIXTURE); + const [files, setFiles] = useState([]); const [result, setResult] = useState(null); const [error, setError] = useState(null); const [running, setRunning] = useState(false); @@ -20,46 +61,43 @@ export function App() { loadAgentConfig() .then((data) => { setConfig(data); - 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 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); + function updateField(field, value) { + setForm((current) => ({ ...current, [field]: value })); } - async function runSkill(event) { + async function buildUploadedDocuments() { + const selected = Array.from(files).slice(0, 2); + const docs = []; + for (const file of selected) { + if (file.size > 256000) throw new Error(`${file.name} is larger than 256 KB.`); + const mediaType = file.type || "text/plain"; + if (!["text/plain", "text/markdown", "application/json", "text/csv"].includes(mediaType)) { + throw new Error(`${file.name} must be a text, markdown, JSON, or CSV file.`); + } + docs.push({ filename: file.name, media_type: mediaType, data_base64: await fileToBase64(file) }); + } + return docs; + } + + async function submit(event) { event.preventDefault(); - if (!config || !selectedSkill) return; + if (!config || needsSignIn) return; setRunning(true); setError(null); setResult(null); try { - const args = JSON.parse(argsText || "{}"); - const data = await callSkill(config, selectedSkill.name, args); + const uploaded_documents = await buildUploadedDocuments(); + const data = await callSkill(config, "generate_document", { request: { ...form, uploaded_documents } }); + if (data?.status === "validation_error") throw new Error(data.warnings?.[0] || "Validation failed"); setResult(data); } catch (err) { setError(err.message || String(err)); @@ -69,90 +107,110 @@ export function App() { } if (!config) { - return ( -
-

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

-
- ); + return

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

; } return ( -
- - -
-
-
-

Skill runner

-

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

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