AI Agents Fancy Stable
ReasoningPanel
Collapsible reasoning trace that streams while the model thinks, then folds itself into a one-line summary
Preview
svelte
<script lang="ts">
import { onMount } from "svelte";
import { ReasoningPanel } from "fancy-ui-svelte";
const TRACE = [
"The user wants the total per region, but the orders table only stores a city.",
" So I need the city → region mapping before anything else can be aggregated.",
" There is a `regions` lookup table with a `city_id` foreign key — that joins cleanly.",
" One catch: cities with no region row would vanish under an inner join.",
" A left join keeps them and buckets them as “Unassigned”, which is the safer default.",
" Grouping on the region name rather than its id keeps the output readable.",
];
/** How long a finished trace rests before the loop starts over. */
const REST_MS = 1200;
const CHUNK_MS = 220;
let text = $state("");
let streaming = $state(false);
let since = $state<number | undefined>(undefined);
let open = $state<boolean | undefined>(undefined);
onMount(() => {
// A looping demo that keeps re-animating is exactly what reduced motion is
// asking us not to do, so it gets the finished trace, expanded and still.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
text = TRACE.join("");
open = true;
return;
}
let timer: ReturnType<typeof setTimeout>;
let i = 0;
function tick() {
if (i < TRACE.length) {
text += TRACE[i];
i += 1;
timer = setTimeout(tick, CHUNK_MS);
return;
}
streaming = false;
timer = setTimeout(restart, REST_MS);
}
function restart() {
text = "";
i = 0;
streaming = true;
since = Date.now();
timer = setTimeout(tick, CHUNK_MS);
}
restart();
return () => clearTimeout(timer);
});
</script>
<div class="w-full max-w-xl">
<ReasoningPanel {text} {streaming} {since} bind:open label="Reasoning" />
<p class="text-muted-foreground mt-3 text-xs">
Streams while it thinks, then folds itself into a summary. Click the header at any point and it
stays exactly where you left it.
</p>
</div>Installation
pnpm add fancy-ui-svelte
import { ReasoningPanel } from 'fancy-ui-svelte'
Usage
svelte
<script lang="ts">
import { ReasoningPanel } from 'fancy-ui-svelte';
</script>
<ReasoningPanel />Examples
Basic Usage
svelte
<script lang="ts">
import { onMount } from "svelte";
import { ReasoningPanel } from "$lib/fancy-ui/reasoning-panel";
const TRACE = [
"The user wants the total per region, but the orders table only stores a city.",
" So I need the city → region mapping before anything else can be aggregated.",
" There is a `regions` lookup table with a `city_id` foreign key — that joins cleanly.",
" One catch: cities with no region row would vanish under an inner join.",
" A left join keeps them and buckets them as “Unassigned”, which is the safer default.",
" Grouping on the region name rather than its id keeps the output readable.",
];
/** How long a finished trace rests before the loop starts over. */
const REST_MS = 1200;
const CHUNK_MS = 220;
let text = $state("");
let streaming = $state(false);
let since = $state<number | undefined>(undefined);
let open = $state<boolean | undefined>(undefined);
onMount(() => {
// A looping demo that keeps re-animating is exactly what reduced motion is
// asking us not to do, so it gets the finished trace, expanded and still.
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
text = TRACE.join("");
open = true;
return;
}
let timer: ReturnType<typeof setTimeout>;
let i = 0;
function tick() {
if (i < TRACE.length) {
text += TRACE[i];
i += 1;
timer = setTimeout(tick, CHUNK_MS);
return;
}
streaming = false;
timer = setTimeout(restart, REST_MS);
}
function restart() {
text = "";
i = 0;
streaming = true;
since = Date.now();
timer = setTimeout(tick, CHUNK_MS);
}
restart();
return () => clearTimeout(timer);
});
</script>
<div class="w-full max-w-xl">
<ReasoningPanel {text} {streaming} {since} bind:open label="Reasoning" />
<p class="text-muted-foreground mt-3 text-xs">
Streams while it thinks, then folds itself into a summary. Click the header at any point and it
stays exactly where you left it.
</p>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
text * | string | - | The reasoning trace so far; hand over a longer string to stream more in |
streaming | boolean | false | Whether the trace is still growing; drives the timer, shimmer, and autoscroll |
open | boolean | - | Expanded state (bindable). Left undefined, the panel opens while streaming and collapses 600ms after it ends, until the reader toggles it by hand |
label | string | "Reasoning" | Header text |
since | number | - | Epoch ms the current burst started at; pins the live timer's origin |
durationMs | number | - | Final duration for the summary line; falls back to the duration the panel measured itself |
maxHeight | string | "12rem" | Scroll height of the trace once expanded |
onToggle | (open: boolean) => void | - | Called on every open/close, by click or on the panel's own initiative |
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