Tool Call Card
A self-contained card for visualizing a single LLM tool call: it names the function, pretty-prints the arguments as syntax-highlighted JSON, and drives a pending to running to success or error state machine with an animated status pill. The returned result collapses into a disclosure panel, latency and token badges summarize the call cost, and a retry button re-runs failed or completed calls, with several seeded examples covering each state.
MCP
Code
:root {
--bg: #0c0d10;
--surface: #15161b;
--surface-2: #1d1f27;
--ink: #e9eaf0;
--muted: #9a9cab;
--accent: #8b5cf6;
--accent-2: #22d3ee;
--ok: #34d399;
--warn: #fbbf24;
--danger: #f87171;
--line: rgba(255, 255, 255, 0.1);
--line-2: rgba(255, 255, 255, 0.18);
--r-sm: 8px;
--r-md: 12px;
--r-lg: 16px;
}
* {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
min-height: 100vh;
background:
radial-gradient(1200px 600px at 15% -10%, rgba(139, 92, 246, 0.14), transparent 60%),
radial-gradient(900px 500px at 110% 10%, rgba(34, 211, 238, 0.1), transparent 55%),
var(--bg);
color: var(--ink);
font-family: "Inter", system-ui, sans-serif;
line-height: 1.5;
}
.page {
max-width: 980px;
margin: 0 auto;
padding: clamp(24px, 5vw, 56px) clamp(16px, 4vw, 32px) 72px;
}
/* ---------- header ---------- */
.eyebrow {
margin: 0 0 10px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--accent-2);
}
.page__head h1 {
margin: 0 0 12px;
font-size: clamp(28px, 5vw, 40px);
font-weight: 700;
letter-spacing: -0.02em;
}
.lede {
margin: 0 0 8px;
max-width: 62ch;
color: var(--muted);
font-size: 15px;
}
.lede code {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 12.5px;
color: var(--ink);
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: 6px;
padding: 1px 6px;
}
.lede strong {
color: var(--ink);
}
/* ---------- grid ---------- */
.grid {
margin-top: clamp(24px, 4vw, 36px);
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: 20px;
}
/* ---------- card ---------- */
.card {
--card-accent: var(--muted);
position: relative;
background: linear-gradient(180deg, var(--surface), #121319);
border: 1px solid var(--line);
border-radius: var(--r-lg);
overflow: hidden;
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.03) inset, 0 20px 40px -28px rgba(0, 0, 0, 0.9);
transition: border-color 0.2s ease, transform 0.2s ease;
}
.card::before {
content: "";
position: absolute;
inset: 0 0 auto 0;
height: 3px;
background: var(--card-accent);
opacity: 0.85;
}
.card[data-state="success"] {
--card-accent: var(--ok);
}
.card[data-state="error"] {
--card-accent: var(--danger);
}
.card[data-state="running"] {
--card-accent: var(--accent-2);
}
.card[data-state="pending"] {
--card-accent: var(--accent);
}
.card:hover {
border-color: var(--line-2);
}
.card__head {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 16px 18px 12px;
}
.fn {
min-width: 0;
flex: 1;
}
.fn__label {
display: block;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 3px;
}
.fn__name {
display: inline-flex;
align-items: baseline;
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 15px;
font-weight: 600;
color: var(--ink);
word-break: break-all;
}
.fn__name .paren {
color: var(--muted);
font-weight: 400;
}
/* ---------- status pill ---------- */
.pill {
flex-shrink: 0;
display: inline-flex;
align-items: center;
gap: 7px;
padding: 5px 11px 5px 9px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.01em;
border: 1px solid transparent;
white-space: nowrap;
}
.pill__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
flex-shrink: 0;
}
.card[data-state="success"] .pill {
color: var(--ok);
background: rgba(52, 211, 153, 0.12);
border-color: rgba(52, 211, 153, 0.32);
}
.card[data-state="error"] .pill {
color: var(--danger);
background: rgba(248, 113, 113, 0.12);
border-color: rgba(248, 113, 113, 0.32);
}
.card[data-state="running"] .pill {
color: var(--accent-2);
background: rgba(34, 211, 238, 0.12);
border-color: rgba(34, 211, 238, 0.32);
}
.card[data-state="pending"] .pill {
color: var(--accent);
background: rgba(139, 92, 246, 0.12);
border-color: rgba(139, 92, 246, 0.32);
}
.card[data-state="running"] .pill__dot {
background: none;
width: 11px;
height: 11px;
border: 2px solid rgba(34, 211, 238, 0.3);
border-top-color: var(--accent-2);
border-radius: 50%;
animation: spin 0.7s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
.card[data-state="running"] .pill__dot {
animation-duration: 2s;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* ---------- section labels ---------- */
.blk {
padding: 0 18px;
}
.blk__label {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.09em;
text-transform: uppercase;
color: var(--muted);
margin: 8px 0 6px;
}
/* ---------- json block ---------- */
.code {
margin: 0;
padding: 12px 14px;
background: #101116;
border: 1px solid var(--line);
border-radius: var(--r-md);
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 12.5px;
line-height: 1.65;
color: #cdd0dd;
overflow-x: auto;
white-space: pre;
tab-size: 2;
}
.tok-key {
color: var(--accent-2);
}
.tok-str {
color: #a5e8b0;
}
.tok-num {
color: #f0b866;
}
.tok-bool {
color: var(--accent);
}
.tok-null {
color: var(--muted);
}
.tok-punc {
color: var(--muted);
}
/* ---------- result disclosure ---------- */
.result {
margin: 10px 18px 0;
border: 1px solid var(--line);
border-radius: var(--r-md);
background: rgba(255, 255, 255, 0.015);
overflow: hidden;
}
.result__toggle {
width: 100%;
display: flex;
align-items: center;
gap: 10px;
padding: 11px 14px;
background: none;
border: 0;
color: var(--ink);
font: inherit;
font-size: 13px;
font-weight: 600;
cursor: pointer;
text-align: left;
}
.result__toggle:disabled {
cursor: not-allowed;
color: var(--muted);
}
.result__toggle:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -2px;
border-radius: var(--r-md);
}
.chev {
width: 16px;
height: 16px;
flex-shrink: 0;
transition: transform 0.22s ease;
color: var(--muted);
}
.result__toggle[aria-expanded="true"] .chev {
transform: rotate(90deg);
}
.result__meta {
margin-left: auto;
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 11px;
font-weight: 500;
color: var(--muted);
}
.result__panel {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.25s ease;
}
.result__panel[data-open="true"] {
grid-template-rows: 1fr;
}
.result__inner {
overflow: hidden;
min-height: 0;
}
.result__body {
padding: 0 14px 14px;
}
.result__actions {
display: flex;
justify-content: flex-end;
padding: 4px 14px 12px;
}
.copy {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 10px;
border-radius: var(--r-sm);
border: 1px solid var(--line);
background: var(--surface-2);
color: var(--muted);
font: inherit;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: color 0.15s ease, border-color 0.15s ease;
}
.copy:hover {
color: var(--ink);
border-color: var(--line-2);
}
.copy:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.copy svg {
width: 13px;
height: 13px;
}
/* running placeholder */
.placeholder {
padding: 14px;
color: var(--muted);
font-size: 13px;
display: flex;
align-items: center;
gap: 9px;
}
.placeholder .bar {
height: 9px;
border-radius: 5px;
background: linear-gradient(90deg, var(--surface-2) 25%, #262833 37%, var(--surface-2) 63%);
background-size: 400% 100%;
animation: sheen 1.3s ease infinite;
}
@keyframes sheen {
0% {
background-position: 100% 0;
}
100% {
background-position: -100% 0;
}
}
/* ---------- footer of card: badges + actions ---------- */
.card__foot {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
padding: 14px 18px 16px;
margin-top: 4px;
border-top: 1px solid var(--line);
}
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 9px;
border-radius: 999px;
background: var(--surface-2);
border: 1px solid var(--line);
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 11.5px;
font-weight: 500;
color: var(--muted);
}
.badge svg {
width: 12px;
height: 12px;
color: var(--accent-2);
}
.badge b {
color: var(--ink);
font-weight: 600;
}
.badge--tok svg {
color: var(--accent);
}
.spacer {
flex: 1;
}
.btn {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 7px 14px;
border-radius: var(--r-sm);
border: 1px solid var(--line-2);
background: linear-gradient(180deg, var(--surface-2), #17181f);
color: var(--ink);
font: inherit;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: transform 0.12s ease, border-color 0.15s ease, background 0.15s ease;
}
.btn svg {
width: 14px;
height: 14px;
}
.btn:hover {
border-color: var(--accent);
background: linear-gradient(180deg, #23202f, #1a1723);
}
.btn:active {
transform: translateY(1px);
}
.btn:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.btn:disabled {
opacity: 0.45;
cursor: not-allowed;
border-color: var(--line);
}
.btn:disabled:hover {
background: linear-gradient(180deg, var(--surface-2), #17181f);
}
/* ---------- page footer legend ---------- */
.page__foot {
margin-top: 40px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 18px;
font-size: 12.5px;
color: var(--muted);
}
.dot {
width: 9px;
height: 9px;
border-radius: 50%;
display: inline-block;
margin-right: -10px;
}
.dot--ok {
background: var(--ok);
}
.dot--run {
background: var(--accent-2);
}
.dot--err {
background: var(--danger);
}
.dot--idle {
background: var(--accent);
}
/* ---------- toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 20px);
background: var(--surface-2);
border: 1px solid var(--line-2);
color: var(--ink);
padding: 10px 16px;
border-radius: 999px;
font-size: 13px;
font-weight: 500;
box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.9);
opacity: 0;
pointer-events: none;
transition: transform 0.28s ease, opacity 0.28s ease;
z-index: 50;
}
.toast.show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- responsive ---------- */
@media (max-width: 520px) {
.grid {
grid-template-columns: 1fr;
}
.card__head {
flex-direction: column;
}
.pill {
align-self: flex-start;
}
.code {
font-size: 11.5px;
}
}/* Tool Call Card — a pending→running→success/error state machine per card. */
(function () {
"use strict";
var STATUS_LABEL = {
pending: "Pending",
running: "Running",
success: "Success",
error: "Error",
};
/* Seeded tool calls, one per state so every branch is visible. */
var CALLS = [
{
id: "wx",
name: "get_weather",
args: { location: "Kyoto, JP", units: "metric", forecast_days: 3 },
state: "success",
latency: 412,
tokens: { prompt: 186, completion: 74 },
result: {
location: "Kyoto, JP",
temp_c: 27.4,
condition: "Partly cloudy",
humidity: 0.61,
forecast: [
{ day: "Tue", high: 29, low: 21 },
{ day: "Wed", high: 26, low: 20 },
],
},
},
{
id: "db",
name: "query_database",
args: {
sql: "SELECT id, email FROM users WHERE plan = 'pro' LIMIT 20",
readonly: true,
},
state: "running",
latency: 0,
tokens: { prompt: 241, completion: 0 },
result: null,
},
{
id: "http",
name: "http_request",
args: {
method: "POST",
url: "https://api.billing.internal/v2/charge",
body: { amount_cents: 4900, currency: "usd" },
},
state: "error",
latency: 1830,
tokens: { prompt: 210, completion: 38 },
result: {
error: "UpstreamTimeout",
status: 504,
message: "Billing gateway did not respond within 1500ms",
retryable: true,
},
},
{
id: "search",
name: "web_search",
args: { query: "state machines in ui components", top_k: 5 },
state: "pending",
latency: 0,
tokens: { prompt: 0, completion: 0 },
result: null,
},
];
/* ---------- JSON pretty-printer with lightweight highlighting ---------- */
function esc(s) {
return s
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
function highlight(value) {
var json = JSON.stringify(value, null, 2);
return json.replace(
/("(\\.|[^"\\])*"(\s*:)?|\b(true|false)\b|\bnull\b|-?\d+(\.\d+)?([eE][+-]?\d+)?)/g,
function (match) {
var cls = "tok-num";
if (/^"/.test(match)) {
cls = /:$/.test(match) ? "tok-key" : "tok-str";
} else if (/true|false/.test(match)) {
cls = "tok-bool";
} else if (/null/.test(match)) {
cls = "tok-null";
}
return '<span class="' + cls + '">' + esc(match) + "</span>";
}
);
}
/* ---------- clipboard with fallback ---------- */
function copyText(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text);
}
return new Promise(function (resolve, reject) {
try {
var ta = document.createElement("textarea");
ta.value = text;
ta.setAttribute("readonly", "");
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
var ok = document.execCommand("copy");
document.body.removeChild(ta);
ok ? resolve() : reject();
} catch (e) {
reject(e);
}
});
}
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 1800);
}
/* ---------- icons ---------- */
var ICON = {
chev:
'<svg class="chev" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M6 4l4 4-4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>',
clock:
'<svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.4"/><path d="M8 5v3.2l2 1.3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>',
tokens:
'<svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 1.5l5.6 3.2v6.6L8 14.5 2.4 11.3V4.7L8 1.5z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><path d="M8 8l5.6-3.3M8 8v6.5M8 8L2.4 4.7" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/></svg>',
retry:
'<svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M13 4.5A5.5 5.5 0 1 0 14 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M13 1.5v3.2h-3.2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
run:
'<svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M4.5 3.2l7.4 4.8-7.4 4.8V3.2z" fill="currentColor"/></svg>',
copy:
'<svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><rect x="5.5" y="5.5" width="8" height="8" rx="1.6" stroke="currentColor" stroke-width="1.3"/><path d="M3.5 10.5A1.5 1.5 0 0 1 2.5 9V3.5A1.5 1.5 0 0 1 4 2h5.5a1.5 1.5 0 0 1 1.5 1.5" stroke="currentColor" stroke-width="1.3"/></svg>',
check:
'<svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M3 8.5l3.2 3L13 5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>',
};
/* ---------- render a single card ---------- */
var root = document.getElementById("cards");
function fmtLatency(ms) {
if (ms >= 1000) return (ms / 1000).toFixed(2) + "s";
return Math.round(ms) + "ms";
}
function resultBlock(call) {
var hasResult = call.state === "success" || call.state === "error";
var running = call.state === "running";
var expandable = hasResult;
var summary =
call.state === "success"
? "Result"
: call.state === "error"
? "Error payload"
: running
? "Awaiting result…"
: "No result yet";
var bodyHtml;
if (hasResult) {
var lines = JSON.stringify(call.result, null, 2).split("\n").length;
bodyHtml =
'<div class="result__body"><pre class="code">' +
highlight(call.result) +
"</pre></div>" +
'<div class="result__actions"><button type="button" class="copy" data-copy>' +
ICON.copy +
"<span>Copy</span></button></div>";
var _ = lines;
} else if (running) {
bodyHtml =
'<div class="placeholder"><span class="pill__dot" style="border:2px solid rgba(34,211,238,.3);border-top-color:var(--accent-2);width:12px;height:12px;border-radius:50%;animation:spin .7s linear infinite"></span>' +
'<span style="flex:1"><span class="bar" style="display:block;width:70%;margin-bottom:6px"></span><span class="bar" style="display:block;width:45%"></span></span></div>';
} else {
bodyHtml =
'<div class="placeholder">Call has not run yet — press Run below.</div>';
}
var meta =
hasResult && call.result
? '<span class="result__meta">' +
Object.keys(call.result).length +
" keys</span>"
: "";
return (
'<div class="result">' +
'<button type="button" class="result__toggle" aria-expanded="false" ' +
(expandable ? "" : "disabled ") +
'data-toggle>' +
ICON.chev +
"<span>" +
summary +
"</span>" +
meta +
"</button>" +
'<div class="result__panel" data-panel><div class="result__inner">' +
bodyHtml +
"</div></div>" +
"</div>"
);
}
function cardHtml(call) {
var canAct = call.state !== "running";
var actionLabel = call.state === "pending" ? "Run" : "Retry";
var actionIcon = call.state === "pending" ? ICON.run : ICON.retry;
return (
'<article class="card" data-state="' +
call.state +
'" data-id="' +
call.id +
'" aria-label="Tool call ' +
call.name +
'">' +
'<div class="card__head">' +
'<div class="fn"><span class="fn__label">Function</span>' +
'<span class="fn__name">' +
esc(call.name) +
'<span class="paren">()</span></span></div>' +
'<span class="pill"><span class="pill__dot"></span>' +
STATUS_LABEL[call.state] +
"</span>" +
"</div>" +
'<div class="blk"><div class="blk__label">Arguments</div>' +
'<pre class="code">' +
highlight(call.args) +
"</pre></div>" +
resultBlock(call) +
'<div class="card__foot">' +
'<span class="badge badge--lat">' +
ICON.clock +
"<span>latency <b data-latency>" +
(call.latency ? fmtLatency(call.latency) : "—") +
"</b></span></span>" +
'<span class="badge badge--tok">' +
ICON.tokens +
"<span><b data-tokens>" +
(call.tokens.prompt + call.tokens.completion) +
"</b> tok</span></span>" +
'<span class="spacer"></span>' +
'<button type="button" class="btn" data-action ' +
(canAct ? "" : "disabled") +
">" +
actionIcon +
"<span>" +
actionLabel +
"</span></button>" +
"</div>" +
"</article>"
);
}
function render() {
root.innerHTML = CALLS.map(cardHtml).join("");
}
/* ---------- disclosure toggle ---------- */
function toggleDisclosure(btn) {
var open = btn.getAttribute("aria-expanded") === "true";
var panel = btn.parentElement.querySelector("[data-panel]");
btn.setAttribute("aria-expanded", String(!open));
panel.setAttribute("data-open", String(!open));
}
/* ---------- run / retry: drive the state machine ---------- */
var timers = {};
function setCallState(call, state) {
call.state = state;
var card = root.querySelector('[data-id="' + call.id + '"]');
if (!card) return;
card.outerHTML = cardHtml(call);
}
function runCall(call) {
if (call.state === "running") return;
if (timers[call.id]) return;
// move to running with a live latency counter
call.state = "running";
call.latency = 0;
call.tokens.completion = 0;
call.result = null;
setCallState(call, "running");
var card = root.querySelector('[data-id="' + call.id + '"]');
var latEl = card.querySelector("[data-latency]");
var tokEl = card.querySelector("[data-tokens]");
// Decide outcome + a realistic duration. The billing call fails.
var willFail = call.name === "http_request";
var duration = willFail ? 1500 + Math.random() * 500 : 350 + Math.random() * 900;
var completionTokens = willFail ? 38 : 40 + Math.floor(Math.random() * 90);
var start = performance.now();
function tick(now) {
var elapsed = Math.min(now - start, duration);
if (latEl) latEl.textContent = fmtLatency(elapsed);
if (tokEl) {
var grown = Math.floor((elapsed / duration) * completionTokens);
tokEl.textContent = call.tokens.prompt + grown;
}
if (elapsed < duration) {
timers[call.id] = requestAnimationFrame(tick);
} else {
delete timers[call.id];
finish(call, willFail, duration, completionTokens);
}
}
timers[call.id] = requestAnimationFrame(tick);
}
function finish(call, failed, duration, completionTokens) {
call.latency = duration;
call.tokens.completion = completionTokens;
if (failed) {
call.state = "error";
call.result = {
error: "UpstreamTimeout",
status: 504,
message: "Billing gateway did not respond within 1500ms",
retryable: true,
};
} else {
call.state = "success";
call.result = resultFor(call);
}
setCallState(call, call.state);
// auto-open the freshly resolved result
var card = root.querySelector('[data-id="' + call.id + '"]');
var btn = card && card.querySelector("[data-toggle]");
if (btn && !btn.disabled) toggleDisclosure(btn);
}
function resultFor(call) {
switch (call.name) {
case "get_weather":
return {
location: call.args.location,
temp_c: Math.round((24 + Math.random() * 6) * 10) / 10,
condition: "Partly cloudy",
humidity: Math.round(Math.random() * 40 + 45) / 100,
forecast: [
{ day: "Tue", high: 29, low: 21 },
{ day: "Wed", high: 26, low: 20 },
],
};
case "query_database":
return {
rows: 12,
took_ms: Math.round(call.latency),
sample: [
{ id: 8021, email: "[email protected]" },
{ id: 8044, email: "[email protected]" },
],
};
case "web_search":
return {
query: call.args.query,
top_k: call.args.top_k,
results: [
{ title: "State machines in UI", url: "https://example.dev/fsm-ui" },
{ title: "XState vs hand-rolled", url: "https://example.dev/xstate" },
],
};
default:
return { ok: true };
}
}
/* ---------- event delegation ---------- */
root.addEventListener("click", function (e) {
var toggle = e.target.closest("[data-toggle]");
if (toggle && !toggle.disabled) {
toggleDisclosure(toggle);
return;
}
var copyBtn = e.target.closest("[data-copy]");
if (copyBtn) {
var card = copyBtn.closest(".card");
var call = findCall(card.getAttribute("data-id"));
copyText(JSON.stringify(call.result, null, 2)).then(
function () {
var span = copyBtn.querySelector("span");
var prev = span.textContent;
copyBtn.innerHTML = ICON.check + "<span>Copied</span>";
toast("Result copied to clipboard");
setTimeout(function () {
copyBtn.innerHTML = ICON.copy + "<span>" + prev + "</span>";
}, 1400);
},
function () {
toast("Copy failed — select the text manually");
}
);
return;
}
var action = e.target.closest("[data-action]");
if (action && !action.disabled) {
var c = findCall(action.closest(".card").getAttribute("data-id"));
runCall(c);
}
});
function findCall(id) {
for (var i = 0; i < CALLS.length; i++) {
if (CALLS[i].id === id) return CALLS[i];
}
return null;
}
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tool Call Card</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="page">
<header class="page__head">
<p class="eyebrow">LLM runtime · agent tooling</p>
<h1>Tool Call Card</h1>
<p class="lede">
A card for a single tool call from a model. Watch it move through
<code>pending → running → success / error</code>, expand the returned
result, and inspect latency and token cost. Press <strong>Run</strong>
on an idle card or <strong>Retry</strong> on a finished one.
</p>
</header>
<section class="grid" id="cards" aria-label="Tool call cards"></section>
<footer class="page__foot">
<span class="dot dot--ok"></span> success
<span class="dot dot--run"></span> running
<span class="dot dot--err"></span> error
<span class="dot dot--idle"></span> pending
</footer>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>The Tool Call Card renders one function invocation from an LLM the way an agent runtime would surface it: a monospace function name and a color-coded status pill up top, a pretty-printed and lightly syntax-highlighted JSON block of the arguments, and a collapsible result panel that reveals whatever the tool returned. Each card walks a real state machine — pending, running, success, or error — with the pill, spinner, and accent color all reacting to the current state, and a footer of badges reporting wall-clock latency and prompt/completion token counts.
Press Run on an idle card to watch it move through running into a resolved state, or hit Retry on a finished or failed card to replay the call with fresh latency. The disclosure panel is a real ARIA disclosure — keyboard operable with visible focus — and the result body can be copied to the clipboard in one click. Four seeded examples ship in different states (a successful weather lookup, a live-running database query, a failed HTTP request with an error payload, and an idle web search) so every branch of the machine is visible at once.
The demo showcases the browser Clipboard API (navigator.clipboard.writeText) for copying results, with a graceful document.execCommand fallback for insecure contexts, alongside requestAnimationFrame-timed latency counters and a hand-rolled ARIA disclosure so the collapse animation and state transitions stay fully under script control.