'use client'
import { useEffect, useMemo, useState } from 'react'
import { useReducedMotion } from 'motion/react'
import {
ToolCallTimeline,
type AgentStep,
} from '@/components/ui/tool-call-timeline'
type Frame = AgentStep[]
/** Deterministic staged ops run, fetch → reconcile → sub-agent verify */
const FRAMES: Frame[] = [
[
{
id: 'fetch',
tool: 'xero.invoices.list',
summary: 'Pulling open invoices for Apex Holdings…',
args: '{ "org": "apex-holdings", "status": "AUTHORISED", "since": "2026-03-01" }',
status: 'running',
},
{
id: 'reconcile',
tool: 'ledger.reconcile',
summary: 'Waiting on invoice set',
status: 'pending',
},
{
id: 'verify',
tool: 'agent.spawn',
summary: 'Sub-agent verification (queued)',
status: 'pending',
children: [
{
id: 'verify-stripe',
tool: 'stripe.charges.list',
summary: 'Match Stripe charges to invoice lines',
status: 'pending',
},
{
id: 'verify-diff',
tool: 'diff.summarize',
summary: 'Flag amount mismatches over $1',
status: 'pending',
},
],
},
],
[
{
id: 'fetch',
tool: 'xero.invoices.list',
summary: 'Loaded 128 AUTHORISED invoices since March 1',
args: '{ "org": "apex-holdings", "status": "AUTHORISED", "since": "2026-03-01" }',
result: '{ "count": 128, "next": null }',
durationMs: 842,
status: 'done',
},
{
id: 'reconcile',
tool: 'ledger.reconcile',
summary: 'Matching payments against the trial balance…',
args: '{ "invoices": 128, "source": "xero" }',
status: 'running',
},
{
id: 'verify',
tool: 'agent.spawn',
summary: 'Sub-agent verification (queued)',
status: 'pending',
children: [
{
id: 'verify-stripe',
tool: 'stripe.charges.list',
summary: 'Match Stripe charges to invoice lines',
status: 'pending',
},
{
id: 'verify-diff',
tool: 'diff.summarize',
summary: 'Flag amount mismatches over $1',
status: 'pending',
},
],
},
],
[
{
id: 'fetch',
tool: 'xero.invoices.list',
summary: 'Loaded 128 AUTHORISED invoices since March 1',
args: '{ "org": "apex-holdings", "status": "AUTHORISED", "since": "2026-03-01" }',
result: '{ "count": 128, "next": null }',
durationMs: 842,
status: 'done',
},
{
id: 'reconcile',
tool: 'ledger.reconcile',
summary: 'Trial balance matched, 2 open exceptions',
args: '{ "invoices": 128, "source": "xero" }',
result: '{ "matched": 126, "exceptions": 2 }',
durationMs: 2140,
status: 'done',
},
{
id: 'verify',
tool: 'agent.spawn',
summary: 'Running verification sub-agent…',
status: 'running',
children: [
{
id: 'verify-stripe',
tool: 'stripe.charges.list',
summary: 'Pulling March charges for Acme Logistics',
args: '{ "customer": "cus_AcmeLog9k2", "created[gte]": 1740787200 }',
status: 'running',
},
{
id: 'verify-diff',
tool: 'diff.summarize',
summary: 'Flag amount mismatches over $1',
status: 'pending',
},
],
},
],
[
{
id: 'fetch',
tool: 'xero.invoices.list',
summary: 'Loaded 128 AUTHORISED invoices since March 1',
args: '{ "org": "apex-holdings", "status": "AUTHORISED", "since": "2026-03-01" }',
result: '{ "count": 128, "next": null }',
durationMs: 842,
status: 'done',
},
{
id: 'reconcile',
tool: 'ledger.reconcile',
summary: 'Trial balance matched, 2 open exceptions',
args: '{ "invoices": 128, "source": "xero" }',
result: '{ "matched": 126, "exceptions": 2 }',
durationMs: 2140,
status: 'done',
},
{
id: 'verify',
tool: 'agent.spawn',
summary: 'Verification complete, one duplicate charge confirmed',
durationMs: 3610,
status: 'done',
children: [
{
id: 'verify-stripe',
tool: 'stripe.charges.list',
summary: 'Found two $1,240 charges on 2026-03-14',
args: '{ "customer": "cus_AcmeLog9k2", "created[gte]": 1740787200 }',
result: '{ "data": [{ "id": "ch_1", "amount": 124000 }, { "id": "ch_2", "amount": 124000 }] }',
durationMs: 1180,
status: 'done',
},
{
id: 'verify-diff',
tool: 'diff.summarize',
summary: 'inv_2026_03_8841 double-charged; refund candidate',
args: '{ "threshold_cents": 100 }',
result: '{ "mismatches": 1, "candidate": "inv_2026_03_8841" }',
durationMs: 420,
status: 'done',
},
],
},
],
]
const HOLDS = [1200, 1400, 1600, 3200]
export default function ToolCallTimelineDemo() {
const reduced = useReducedMotion()
const [index, setIndex] = useState(0)
useEffect(() => {
if (reduced) {
setIndex(FRAMES.length - 1)
return
}
const hold = HOLDS[index] ?? 2000
const id = window.setTimeout(() => {
setIndex((i) => (i + 1) % FRAMES.length)
}, hold)
return () => window.clearTimeout(id)
}, [index, reduced])
const steps = useMemo(
() => (reduced ? FRAMES[FRAMES.length - 1]! : FRAMES[index]!),
[index, reduced]
)
return (
<div className="mx-auto flex min-h-[600px] w-full max-w-2xl flex-col justify-center px-4 py-12 sm:px-6 sm:py-16">
<div className="rounded-2xl border border-border/60 bg-card p-5 sm:p-6">
<ToolCallTimeline
label="Agent run"
steps={steps}
defaultExpandedIds={reduced ? ['fetch', 'verify-stripe'] : ['fetch']}
/>
</div>
</div>
)
}