{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "thinking-state", "type": "registry:component", "title": "Thinking", "description": "Expandable traces — steps, reasoning, search, coding.", "registryDependencies": [ "https://www.beautifului.dev/r/foundation.json" ], "files": [ { "path": "components/primitives/ThinkingState.tsx", "content": "\"use client\";\n\nimport { useEffect, useLayoutEffect, useRef, useState } from \"react\";\n\n/* ─────────────────────────────────────────────────────────\n * THINKING — expandable agent trace, four variants\n *\n * Steps step list with spinner → muted checks\n * Reasoning prose reasoning that expands, then settles\n * Search web-search trace: query + sources read\n * Coding tool trace: files read, edits, commands\n *\n * The trace runs once, settles, and remains expandable.\n * ───────────────────────────────────────────────────────── */\n\nconst STAGES = [800, 600, 1800, 2600, 1600];\n\nfunction useSequence(steps: number[]) {\n const [stage, setStage] = useState(0);\n useEffect(() => {\n if (stage >= steps.length - 1) return;\n const t = setTimeout(() => setStage((s) => s + 1), steps[stage]);\n return () => clearTimeout(t);\n }, [stage, steps]);\n return stage;\n}\n\ntype Row = {\n primary: string;\n secondary?: string;\n mono?: boolean;\n add?: number;\n del?: number;\n href?: string;\n};\n\nconst VARIANTS: Record<\n string,\n { active: string; done: string; rows: Row[]; query?: string }\n> = {\n Steps: {\n active: \"Thinking\",\n done: \"Thought for 4 seconds\",\n rows: [\n { primary: \"Reading flavor briefs\" },\n { primary: \"Scanning supplier lists\" },\n { primary: \"Comparing tasting notes\", secondary: \"6 flavors\" },\n { primary: \"Writing the scoop report\" },\n ],\n },\n Reasoning: {\n active: \"Thinking\",\n done: \"Thought for 4 seconds\",\n rows: [\n { primary: \"Summer demand spikes for stone-fruit flavors — peach and apricot lead.\" },\n { primary: \"I should check cone inventory before promoting a waffle-bowl special.\" },\n ],\n },\n Search: {\n active: \"Searching the web\",\n done: \"Searched the web\",\n query: \"best waffle cone supplier\",\n rows: [\n { primary: \"Joy Cone\", secondary: \"joycone.com\", href: \"https://joycone.com/fs_products/waffle-cones/\" },\n { primary: \"WebstaurantStore\", secondary: \"webstaurantstore.com\", href: \"https://www.webstaurantstore.com/ice-cream-shop-supplies.html\" },\n { primary: \"The Konery\", secondary: \"thekonery.com\", href: \"https://www.thekonery.com/\" },\n ],\n },\n Coding: {\n active: \"Running tools\",\n done: \"Ran 3 tools\",\n rows: [\n { primary: \"Read\", secondary: \"flavors.ts\", mono: true },\n { primary: \"Edit\", secondary: \"ChurnSchedule.tsx\", mono: true, add: 74, del: 41 },\n { primary: \"Run\", secondary: \"npm run freeze\", mono: true },\n ],\n },\n};\n\nfunction Dot({ tone }: { tone: string }) {\n return (\n \n \n \n \n \n \n );\n}\n\nconst TONES = [\"bg-accent\", \"bg-orange\", \"bg-green\"];\n\nexport default function ThinkingState({ variant = \"Steps\", onSettled }: { variant?: string; onSettled?: () => void }) {\n const stage = useSequence(STAGES);\n const [manualExpanded, setManualExpanded] = useState(null);\n const [selectedTool, setSelectedTool] = useState(null);\n const v = VARIANTS[variant] ?? VARIANTS.Steps;\n const autoExpanded = stage >= 1 && stage < 4;\n const expanded = manualExpanded ?? autoExpanded;\n const working = stage < 3;\n const visible = stage < 2 ? 0 : stage === 2 ? Math.min(2, v.rows.length) : v.rows.length;\n const traceRef = useRef(null);\n const [lineHeight, setLineHeight] = useState(0);\n useLayoutEffect(() => {\n if (traceRef.current) setLineHeight(traceRef.current.offsetHeight);\n }, [visible, expanded, variant, stage]);\n\n /* let embedders sequence content after the trace settles */\n const settledRef = useRef(false);\n useEffect(() => {\n if (working || settledRef.current) return;\n settledRef.current = true;\n onSettled?.();\n }, [working, onSettled]);\n\n return (\n \n {/* header — shared across variants */}\n setManualExpanded((current) => !(current ?? autoExpanded))}\n className=\"-mx-1.5 flex w-fit items-center gap-2 rounded-control px-1.5 py-1\n transition-colors duration-100 hover:bg-hover-2\"\n >\n \n \n \n \n {working ? (\n \n {v.active}\n \n ) : (\n \n {v.done}\n \n )}\n \n \n \n \n \n\n {/* expandable trace */}\n \n
\n
\n \n
\n {v.query && (\n
\n \n \n \n \n {v.query}\n
\n )}\n {v.rows.slice(0, visible).map((row, i) => {\n const content = (\n <>\n {variant === \"Search\" && }\n {variant === \"Steps\" && (\n i < visible - 1 || !working ? (\n \n \n \n ) : (\n \n )\n )}\n \n {row.primary}\n \n {row.secondary && (\n \n {row.secondary}\n \n )}\n {row.add !== undefined && (\n \n +{row.add}{\" \"}\n −{row.del}\n \n )}\n \n );\n const rowClass = \"flex min-h-7 w-full items-center gap-2 rounded-[6px] px-1.5 py-0.5 text-left\";\n const animation = { animation: `fade-up 320ms cubic-bezier(0.23,1,0.32,1) ${i * 120}ms both` };\n\n if (variant === \"Search\") {\n return (\n \n {content}\n \n );\n }\n\n if (variant === \"Coding\") {\n const selected = selectedTool === row.primary;\n return (\n setSelectedTool(selected ? null : row.primary)}\n className={`${rowClass} transition-colors duration-150 ${selected ? \"bg-inset\" : \"hover:bg-hover\"}`}\n style={animation}\n >\n {content}\n \n );\n }\n\n return (\n
\n {content}\n
\n );\n })}\n {variant === \"Search\" && stage >= 3 && (\n \n +7 more\n \n )}\n
\n
\n
\n \n \n );\n}\n", "type": "registry:component", "target": "components/primitives/ThinkingState.tsx" } ] }