a2a-source-edit: write frontend/dist/index.html

This commit is contained in:
a2a-cloud
2026-07-13 00:34:24 +00:00
parent 38bf02ff3d
commit ed266a2281

139
frontend/dist/index.html vendored Normal file
View File

@@ -0,0 +1,139 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Support Refund Approval Card</title>
<style>
:root { color-scheme: light dark; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
body { margin: 0; background: #0f172a; color: #e5e7eb; }
main { display: grid; grid-template-columns: 320px 1fr; min-height: 100vh; }
aside { padding: 28px; background: #111827; border-right: 1px solid #334155; }
section { padding: 28px; }
.eyebrow { color: #38bdf8; text-transform: uppercase; letter-spacing: .14em; font-size: 12px; font-weight: 700; }
h1, h2 { margin: 0 0 12px; }
p { color: #cbd5e1; line-height: 1.55; }
.card, form { background: #1e293b; border: 1px solid #334155; border-radius: 18px; padding: 18px; margin: 16px 0; box-shadow: 0 20px 50px rgba(0,0,0,.25); }
button { border: 0; border-radius: 999px; padding: 12px 18px; background: #38bdf8; color: #0f172a; font-weight: 800; cursor: pointer; }
button.secondary { background: #334155; color: #e5e7eb; margin-left: 8px; }
textarea { width: 100%; min-height: 260px; border-radius: 14px; border: 1px solid #475569; padding: 14px; background: #020617; color: #e5e7eb; font: 13px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; box-sizing: border-box; }
label { display: grid; gap: 10px; color: #e2e8f0; font-weight: 700; }
pre { white-space: pre-wrap; overflow-wrap: anywhere; background: #020617; border: 1px solid #334155; border-radius: 14px; padding: 14px; min-height: 160px; }
.skills button { display: block; width: 100%; margin: 8px 0; text-align: left; border-radius: 14px; }
.muted { color: #94a3b8; font-size: 13px; }
@media (max-width: 800px) { main { grid-template-columns: 1fr; } aside { border-right: 0; border-bottom: 1px solid #334155; } }
</style>
</head>
<body>
<main>
<aside>
<p class="eyebrow">A2A Support Agent</p>
<h1>Refund approval card</h1>
<p id="agentMeta">Loading live Agent Card…</p>
<div class="card">
<strong>Safety boundary</strong>
<p>No email can execute a refund. The email handler drafts a customer reply and posts a Slack audit. Refund execution requires a separate approval token bound to the action digest.</p>
</div>
<div class="card skills" id="skills"></div>
</aside>
<section>
<p class="eyebrow">Chat-style runner</p>
<h2 id="skillName">process_support_email</h2>
<form id="runner">
<label>Message / approval JSON
<textarea id="args"></textarea>
</label>
<p>
<button type="submit">Send to agent</button>
<button class="secondary" id="approvalSample" type="button">Approval sample</button>
</p>
</form>
<div class="card">
<strong>Result</strong>
<pre id="result">No run yet.</pre>
</div>
<div class="card">
<strong>Published input schema</strong>
<pre id="schema">Loading…</pre>
</div>
<p class="muted">This packed frontend loads /app/config.json and calls only the agent's public A2A tool endpoints with inherited platform auth.</p>
</section>
</main>
<script type="module">
const SAMPLE_EMAIL = {
sender_email: "Customer Example <customer@example.com>",
subject: "Refund request for pi_test123",
body: "Please refund 25.00 USD for payment pi_test123.",
message_id: "<sample-support-email@example.com>",
stripe_payment_intent_id: "pi_test123",
requested_refund_amount_cents: 2500,
currency: "usd",
customer_name: "Customer"
};
const APPROVAL_SAMPLE = {
action_digest: "paste_action_digest_from_preview",
approval_token: "APPROVE-REFUND-first16digest",
stripe_payment_intent_id: "pi_test123",
amount_cents: 2500,
currency: "usd",
customer_email: "customer@example.com",
reason: "requested_by_customer"
};
const argsEl = document.querySelector('#args');
const resultEl = document.querySelector('#result');
const schemaEl = document.querySelector('#schema');
const skillsEl = document.querySelector('#skills');
const skillNameEl = document.querySelector('#skillName');
let config = null;
let selected = 'process_support_email';
argsEl.value = JSON.stringify(SAMPLE_EMAIL, null, 2);
async function requestJson(url, init = {}) {
const response = await fetch(url, { credentials: 'same-origin', ...init });
const text = await response.text();
const data = text ? JSON.parse(text) : null;
if (!response.ok) throw new Error((data && (data.detail || data.message)) || `request failed: ${response.status}`);
return data;
}
function renderSkill() {
const skill = (config.skills || []).find((item) => item.name === selected);
skillNameEl.textContent = selected;
schemaEl.textContent = JSON.stringify(skill?.input_schema || {}, null, 2);
}
function selectSkill(name) {
selected = name;
argsEl.value = JSON.stringify(name === 'execute_approved_refund' ? APPROVAL_SAMPLE : SAMPLE_EMAIL, null, 2);
resultEl.textContent = 'No run yet.';
renderSkill();
}
async function boot() {
config = await requestJson('./config.json');
document.querySelector('#agentMeta').textContent = `${config.agent.name} v${config.agent.version}`;
skillsEl.innerHTML = '<strong>Public skills</strong>';
for (const skill of config.skills || []) {
const button = document.createElement('button');
button.type = 'button';
button.textContent = `${skill.name}${(skill.tags || []).includes('a2a:email-handler') ? ' · email' : ''}`;
button.onclick = () => selectSkill(skill.name);
skillsEl.appendChild(button);
}
renderSkill();
}
document.querySelector('#approvalSample').onclick = () => selectSkill('execute_approved_refund');
document.querySelector('#runner').onsubmit = async (event) => {
event.preventDefault();
resultEl.textContent = 'Running…';
try {
const args = JSON.parse(argsEl.value || '{}');
const data = await requestJson(`${config.endpoints.invoke}/${encodeURIComponent(selected)}`, {
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ arguments: args })
});
resultEl.textContent = JSON.stringify(data, null, 2);
} catch (err) {
resultEl.textContent = err.message || String(err);
}
};
boot().catch((err) => { resultEl.textContent = err.message || String(err); });
</script>
</body>
</html>