ThinkingTool
Render assistant reasoning in a collapsible row. Use defaultOpen for uncontrolled expand, or expanded + onToggleExpand for controlled state. You can also render from mapped step/state/onComplete instead of part.
Reviewing component coverage and preview density.
Getting Started
$pnpm dlx shadcn@latest add https://agent-elements.21st.dev/r/thinking-tool.jsonExamples
Streaming text
Collapsed
Source
Copy and paste the following code into your project, or run the install command above to pull it in automatically.
components/tools/thinking-tool.tsx
import { memo } from "react";
import type { TimelineStep, StepState } from "../../types/timeline";
import { useToolComplete } from "../../hooks/use-tool-complete";
import { ToolRowBase } from "./tool-row-base";
import {
mapToolInvocationToStep,
mapToolStateToStepState,
} from "../../utils/tool-adapters";
export type ThinkingCollapsedProps = {
step: Extract<TimelineStep, { type: "tool-call" }>;
state: StepState;
onComplete: () => void;
defaultOpen?: boolean;
expanded?: boolean;
onToggleExpand?: () => void;
};
export function ThinkingCollapsed({
step,
state,
onComplete,
defaultOpen,
expanded,
onToggleExpand,
}: ThinkingCollapsedProps) {
useToolComplete(state === "animating", step.duration, onComplete);
return (
<ToolRowBase
shimmerLabel="Thinking"
completeLabel="Thought"
isAnimating={state === "animating"}
expandable={!!step.thoughtContent}
defaultOpen={defaultOpen}
expanded={expanded}
onToggleExpand={onToggleExpand}
>
<div className="max-h-[175px] overflow-y-auto">
<p className="text-sm text-muted-foreground whitespace-pre-wrap">
{step.thoughtContent}
</p>
</div>
</ToolRowBase>
);
}
export type ThinkingToolProps = {
part?: any;
step?: Extract<TimelineStep, { type: "tool-call" }>;
state?: StepState;
onComplete?: () => void;
defaultOpen?: boolean;
expanded?: boolean;
onToggleExpand?: () => void;
};
export const ThinkingTool = memo(function ThinkingTool({
part,
step: externalStep,
state: externalState,
onComplete: externalOnComplete,
defaultOpen,
expanded,
onToggleExpand,
}: ThinkingToolProps) {
let step: Extract<TimelineStep, { type: "tool-call" }>;
let stepState: StepState;
let onComplete: () => void;
if (externalStep && externalState && externalOnComplete) {
step = externalStep;
stepState = externalState;
onComplete = externalOnComplete;
} else if (part) {
step = mapToolInvocationToStep(part.toolCallId ?? part.id ?? "thinking", {
toolName: "Thinking",
args: part.input ?? part.args ?? {},
state:
part.state === "output-available"
? "result"
: part.state === "input-streaming"
? "partial-call"
: "call",
result: part.output ?? part.result,
});
stepState = mapToolStateToStepState(
part.state === "output-available"
? "result"
: part.state === "input-streaming"
? "partial-call"
: "call",
);
onComplete = () => {};
} else {
return null;
}
return (
<ThinkingCollapsed
step={step}
state={stepState}
onComplete={onComplete}
defaultOpen={defaultOpen}
expanded={expanded}
onToggleExpand={onToggleExpand}
/>
);
});
Also installs:
API reference
Prop
Type
Required
part
any
No
step
Extract<TimelineStep, { type: "tool-call" }>
No
state
StepState
No
onComplete
() => void
No
defaultOpen
boolean
No
expanded
boolean
No
onToggleExpand
() => void
No