AI Agents Fancy Stable
ToolCall
One tool invocation in a disclosure card: status dot, tool name, duration, and the request and result payloads pretty-printed on expand
Preview
svelte
<script lang="ts">
import { onMount } from "svelte";
import { ToolCall } from "fancy-ui-svelte";
import type { ToolCallData } from "fancy-ui-svelte";
/** How often the running call's stopwatch refreshes. */
const TICK_MS = 250;
/** How long a pretend call runs before another one takes its place. */
const CYCLE_MS = 9000;
/** What reduced motion sees instead of a clock that never settles. */
const STILL_MS = 4200;
let elapsedMs = $state(0);
const search: ToolCallData = {
id: "call_1",
name: "search_docs",
status: "done",
input: { query: "retry policy", limit: 3 },
output: { hits: 3, top: "billing/retries.md", tookMs: 812 },
durationMs: 1400,
};
const fetching: ToolCallData = $derived({
id: "call_2",
name: "fetch_changelog",
status: "running",
input: { repo: "acme/billing", since: "2026-07-01" },
durationMs: elapsedMs,
});
const posting: ToolCallData = {
id: "call_3",
name: "post_summary",
status: "error",
input: { channel: "#releases", blocks: 4 },
error: "429 Too Many Requests — that channel is rate limited for another 38s.",
durationMs: 240,
};
onMount(() => {
// A clock that never stops is exactly what reduced motion is asking us not
// to run, so it gets one plausible reading and no timer at all.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
elapsedMs = STILL_MS;
return;
}
const started = Date.now();
// Wrapping at the cycle keeps the demo honest: a tool call left open on a
// docs page for an hour should not claim to have been running for one.
const timer = setInterval(() => {
elapsedMs = (Date.now() - started) % CYCLE_MS;
}, TICK_MS);
return () => clearInterval(timer);
});
</script>
<div class="flex w-full max-w-xl flex-col gap-2 p-6">
<ToolCall call={search} />
<ToolCall call={fetching} />
<ToolCall call={posting} />
<p class="text-muted-foreground mt-2 text-xs">
Click any header to see what went out and what came back. The failed call opened itself — but
close it and it stays closed.
</p>
</div>Installation
pnpm add fancy-ui-svelte
import { ToolCall } from 'fancy-ui-svelte'
Usage
svelte
<script lang="ts">
import { ToolCall } from 'fancy-ui-svelte';
</script>
<ToolCall />Examples
Basic Usage
svelte
<script lang="ts">
import { onMount } from "svelte";
import { ToolCall } from "$lib/fancy-ui/tool-call";
import type { ToolCallData } from "$lib/fancy-ui";
/** How often the running call's stopwatch refreshes. */
const TICK_MS = 250;
/** How long a pretend call runs before another one takes its place. */
const CYCLE_MS = 9000;
/** What reduced motion sees instead of a clock that never settles. */
const STILL_MS = 4200;
let elapsedMs = $state(0);
const search: ToolCallData = {
id: "call_1",
name: "search_docs",
status: "done",
input: { query: "retry policy", limit: 3 },
output: { hits: 3, top: "billing/retries.md", tookMs: 812 },
durationMs: 1400,
};
const fetching: ToolCallData = $derived({
id: "call_2",
name: "fetch_changelog",
status: "running",
input: { repo: "acme/billing", since: "2026-07-01" },
durationMs: elapsedMs,
});
const posting: ToolCallData = {
id: "call_3",
name: "post_summary",
status: "error",
input: { channel: "#releases", blocks: 4 },
error: "429 Too Many Requests — that channel is rate limited for another 38s.",
durationMs: 240,
};
onMount(() => {
// A clock that never stops is exactly what reduced motion is asking us not
// to run, so it gets one plausible reading and no timer at all.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
elapsedMs = STILL_MS;
return;
}
const started = Date.now();
// Wrapping at the cycle keeps the demo honest: a tool call left open on a
// docs page for an hour should not claim to have been running for one.
const timer = setInterval(() => {
elapsedMs = (Date.now() - started) % CYCLE_MS;
}, TICK_MS);
return () => clearInterval(timer);
});
</script>
<div class="flex w-full max-w-xl flex-col gap-2 p-6">
<ToolCall call={search} />
<ToolCall call={fetching} />
<ToolCall call={posting} />
<p class="text-muted-foreground mt-2 text-xs">
Click any header to see what went out and what came back. The failed call opened itself — but
close it and it stays closed.
</p>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
call * | ToolCallData | - | The invocation to render: id, name, status, and whatever payloads exist so far |
open | boolean | - | Expanded state (bindable). Left undefined the card stays collapsed, except that a call with status "error" opens itself — until the reader toggles it by hand |
onToggle | (open: boolean) => void | - | Called on every open/close, by click or on the card's own initiative |
Slots
| Slot | Description |
|---|---|
input | Replaces the default request rendering; receives call.input |
output | Replaces the default result rendering; receives call.output |
icon | Leading icon, replacing the default wrench |
Links
Related components
Ag
AgentPlan
Glanceable checklist of an agent's plan: a done/total count and completion bar over one row per step, with a status glyph, an optional detail line, and substeps indented under a rail
AI Agents Stable
Ai
AiDataTable
Compact comparison table for a model's structured answer: real table semantics, tabular numeric columns, check-or-dash booleans, and one tintable column — rendered in the order it arrived, with no sorting
AI Agents Stable
Ap
ApprovalCard
A human-in-the-loop gate before a side effect: the agent states what it is about to do, and the footer swaps its approve and deny buttons for a one-line verdict once someone decides
AI Agents Stable