{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agent-steps",
  "title": "Agent Steps",
  "description": "A timeline of tool calls with status, arguments, results, and parallel sub-steps.",
  "dependencies": [
    "lucide-react"
  ],
  "files": [
    {
      "path": "components/spectrumui/blocks/ai-assistants/agent-steps.tsx",
      "content": "'use client';\n\nimport { useState } from 'react';\nimport { Check, ChevronDown, CircleDashed, X } from 'lucide-react';\nimport { cn } from '@/lib/utils';\n\nconst KEYFRAMES = `\n@keyframes su-pop { 0% { opacity: 0; transform: scale(0.85) } 100% { opacity: 1; transform: none } }\n`;\nimport type { ToolCall, ToolCallStatus } from './types';\n\nconst STATUS_STYLES: Record<ToolCallStatus, string> = {\n  pending: 'bg-neutral-300 dark:bg-neutral-600',\n  running: 'bg-sky-500/80 motion-safe:animate-pulse',\n  success: 'bg-emerald-500/80',\n  error: 'bg-red-500/80',\n  cancelled: 'bg-neutral-400 dark:bg-neutral-500',\n};\n\nexport type AgentStepsVariant = 'Default' | 'Compact';\n\nexport interface AgentStepsProps {\n  steps: ToolCall[];\n  variant?: AgentStepsVariant;\n  className?: string;\n}\n\nfunction duration(step: ToolCall) {\n  if (!step.startedAt || !step.completedAt) return null;\n  return `${((step.completedAt - step.startedAt) / 1000).toFixed(1)}s`;\n}\n\nexport function AgentSteps({ steps, variant = 'Default', className }: AgentStepsProps) {\n  return (\n    <ol className={cn('w-full max-w-[480px] text-[13px]', className)}>\n      <style dangerouslySetInnerHTML={{ __html: KEYFRAMES }} />\n      {steps.map((step, index) => (\n        <StepRow\n          key={step.id}\n          step={step}\n          last={index === steps.length - 1}\n          compact={variant === 'Compact'}\n        />\n      ))}\n    </ol>\n  );\n}\n\nfunction StepRow({ step, last, compact }: { step: ToolCall; last: boolean; compact: boolean }) {\n  const [open, setOpen] = useState(false);\n  const elapsed = duration(step);\n  const expandable = !compact && Boolean(step.result || step.children?.length);\n\n  return (\n    <li className=\"relative flex gap-3\">\n      <div className=\"flex flex-col items-center\">\n        <span className=\"grid size-5 shrink-0 place-items-center\">\n          {step.status === 'success' ? (\n            <span className=\"grid size-4 place-items-center rounded-full bg-emerald-500/15 text-emerald-600 motion-safe:animate-[su-pop_200ms_cubic-bezier(0.23,1,0.32,1)_both] dark:text-emerald-400\">\n              <Check className=\"size-2.5\" strokeWidth={3} />\n            </span>\n          ) : step.status === 'error' ? (\n            <span className=\"grid size-4 place-items-center rounded-full bg-red-500/15 text-red-600 motion-safe:animate-[su-pop_200ms_cubic-bezier(0.23,1,0.32,1)_both] dark:text-red-400\">\n              <X className=\"size-2.5\" strokeWidth={3} />\n            </span>\n          ) : step.status === 'pending' ? (\n            <CircleDashed className=\"size-3.5 text-neutral-300 dark:text-neutral-600\" />\n          ) : (\n            <span className={cn('size-2 rounded-full', STATUS_STYLES[step.status])} />\n          )}\n        </span>\n        {!last && <span className=\"w-px flex-1 bg-black/[0.07] dark:bg-white/[0.08]\" />}\n      </div>\n\n      <div className={cn('min-w-0 flex-1', last ? 'pb-0' : compact ? 'pb-2.5' : 'pb-4')}>\n        <button\n          type=\"button\"\n          onClick={expandable ? () => setOpen((o) => !o) : undefined}\n          aria-expanded={expandable ? open : undefined}\n          className={cn(\n            'flex w-full items-center gap-2 text-left',\n            expandable &&\n              'rounded transition-transform duration-150 active:scale-[0.995] focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-neutral-400',\n          )}\n        >\n          <span className=\"font-mono text-[12px] font-medium text-neutral-800 dark:text-neutral-200\">\n            {step.name}\n          </span>\n          {step.status === 'running' && (\n            <span className=\"font-mono text-[10.5px] uppercase tracking-wide text-sky-600 dark:text-sky-400\">\n              running\n            </span>\n          )}\n          {step.status === 'pending' && (\n            <span className=\"font-mono text-[10.5px] uppercase tracking-wide text-neutral-400 dark:text-neutral-600\">\n              queued\n            </span>\n          )}\n          <span className=\"ml-auto flex shrink-0 items-center gap-1.5\">\n            {elapsed && (\n              <span className=\"font-mono text-[10.5px] tabular-nums text-neutral-400 dark:text-neutral-600\">\n                {elapsed}\n              </span>\n            )}\n            {expandable && (\n              <ChevronDown\n                className={cn(\n                  'size-3 text-neutral-400 transition-transform duration-200 ease-[cubic-bezier(0.23,1,0.32,1)]',\n                  open && 'rotate-180',\n                )}\n              />\n            )}\n          </span>\n        </button>\n\n        {!compact && step.args && (\n          <p className=\"mt-1 truncate font-mono text-[11px] text-neutral-400 dark:text-neutral-600\">\n            {JSON.stringify(step.args)}\n          </p>\n        )}\n\n        {expandable && (\n          <div\n            className=\"grid transition-[grid-template-rows] duration-[240ms] ease-[cubic-bezier(0.32,0.72,0,1)]\"\n            style={{ gridTemplateRows: open ? '1fr' : '0fr' }}\n          >\n            <div className=\"overflow-hidden\">\n              {step.result && (\n                <p className=\"mt-2 rounded-lg bg-black/[0.03] px-2.5 py-2 text-[12px] leading-[1.6] text-neutral-600 dark:bg-white/[0.04] dark:text-neutral-400\">\n                  {step.result}\n                </p>\n              )}\n              {step.children && step.children.length > 0 && (\n                <div className=\"mt-2 grid gap-1.5 sm:grid-cols-2\">\n                  {step.children.map((child) => (\n                    <div\n                      key={child.id}\n                      className=\"rounded-lg border border-black/[0.06] px-2.5 py-2 dark:border-white/[0.07]\"\n                    >\n                      <div className=\"flex items-center gap-1.5\">\n                        <span className={cn('size-1.5 rounded-full', STATUS_STYLES[child.status])} />\n                        <span className=\"truncate font-mono text-[11px] font-medium text-neutral-700 dark:text-neutral-300\">\n                          {child.name}\n                        </span>\n                        {child.parallel && (\n                          <span className=\"ml-auto font-mono text-[9px] uppercase tracking-wide text-neutral-400 dark:text-neutral-600\">\n                            parallel\n                          </span>\n                        )}\n                      </div>\n                      {child.result && (\n                        <p className=\"mt-1 text-[11px] leading-[1.5] text-neutral-500 dark:text-neutral-500\">\n                          {child.result}\n                        </p>\n                      )}\n                    </div>\n                  ))}\n                </div>\n              )}\n            </div>\n          </div>\n        )}\n      </div>\n    </li>\n  );\n}\n\nexport default AgentSteps;\n",
      "type": "registry:block",
      "target": "components/spectrumui/blocks/ai-assistants/agent-steps.tsx"
    },
    {
      "path": "components/spectrumui/blocks/ai-assistants/types.ts",
      "content": "export type MessageRole = 'user' | 'assistant' | 'system';\n\nexport type MessageState = 'streaming' | 'complete' | 'error';\n\nexport interface Attachment {\n  id: string;\n  name: string;\n  size: number;\n  type: string;\n  url?: string;\n  previewUrl?: string;\n}\n\nexport interface Citation {\n  id: string;\n  index: number;\n  url: string;\n  title: string;\n  snippet?: string;\n  favicon?: string;\n}\n\nexport type ToolCallStatus = 'pending' | 'running' | 'success' | 'error' | 'cancelled';\n\nexport interface ToolCall {\n  id: string;\n  name: string;\n  args?: Record<string, unknown>;\n  result?: string;\n  status: ToolCallStatus;\n  startedAt?: number;\n  completedAt?: number;\n  children?: ToolCall[];\n  parallel?: boolean;\n}\n\nexport interface ReasoningStep {\n  id: string;\n  content: string;\n}\n\nexport interface Reasoning {\n  steps: ReasoningStep[];\n  status: 'thinking' | 'complete';\n  durationMs?: number;\n}\n\nexport interface Message {\n  id: string;\n  role: MessageRole;\n  content: string;\n  createdAt?: number;\n  state?: MessageState;\n  attachments?: Attachment[];\n  citations?: Citation[];\n  toolCalls?: ToolCall[];\n  reasoning?: Reasoning;\n}\n\nexport interface Conversation {\n  id: string;\n  label: string;\n}\n\nexport interface ModelOption {\n  id: string;\n  name: string;\n  description?: string;\n  badge?: string;\n  disabled?: boolean;\n}\n\nexport interface SuggestedPrompt {\n  id: string;\n  label: string;\n  prompt?: string;\n}\n\nexport type MessageFeedback = 'positive' | 'negative';\n\nexport interface UsageState {\n  promptTokens: number;\n  completionTokens: number;\n  contextWindow: number;\n  estimatedCostUsd?: number;\n}\n",
      "type": "registry:file",
      "target": "components/spectrumui/blocks/ai-assistants/types.ts"
    }
  ],
  "type": "registry:block"
}
