a2a-source-edit: write frontend/dist/index.html
This commit is contained in:
139
frontend/dist/index.html
vendored
Normal file
139
frontend/dist/index.html
vendored
Normal 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>
|
||||
Reference in New Issue
Block a user