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.
Getting Started
$pnpm add @sinups/ai-kit @mantine/core @mantine/hooks @tabler/icons-reactExamples
Streaming text
Collapsed
Source
The component source as it ships in the package. Useful as a reference, or as a starting point if you want to fork a piece.
tools/ThinkingTool.tsx
import React, { memo } from 'react';
import { ScrollArea } from '@mantine/core';
import { useToolComplete } from '../hooks/use-tool-complete';
import { ToolRowBase } from '../ToolRowBase/ToolRowBase';
import type { ToolPart } from '../types';
import type { StepState, ToolCallStep } from '../types/timeline';
import { noopComplete, useToolStep } from './use-tool-step';
import classes from './ThinkingTool.module.css';
export interface ThinkingCollapsedProps {
/** Timeline step describing the tool call */
step: ToolCallStep;
/** Animation state of the step, the card shows shimmer while `animating` */
state: StepState;
/** Called once `step.duration` elapses while animating */
onComplete: () => void;
/** Initial expanded state in uncontrolled mode */
defaultOpen?: boolean;
/** Controlled expanded state */
expanded?: boolean;
/** Called when the row is toggled */
onToggleExpand?: () => void;
/** Class name added to the root element */
className?: string;
/** Inline styles added to the root element */
style?: React.CSSProperties;
}
/** "Thinking" / "Thought" row that expands to reveal the reasoning text */
export function ThinkingCollapsed({
step,
state,
onComplete,
defaultOpen,
expanded,
onToggleExpand,
className,
style,
}: 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}
className={className}
style={style}
>
<ScrollArea.Autosize mah={175} type="auto">
<p className={classes.text}>{step.thoughtContent}</p>
</ScrollArea.Autosize>
</ToolRowBase>
);
}
export interface ThinkingToolProps {
/** Tool part in AI SDK v5 shape, `input.thought` or string output is the reasoning text */
part?: ToolPart;
/** Timeline step, used together with `state` and `onComplete` instead of `part` */
step?: ToolCallStep;
/** Animation state of the step */
state?: StepState;
/** Called once `step.duration` elapses while animating */
onComplete?: () => void;
/** Initial expanded state in uncontrolled mode */
defaultOpen?: boolean;
/** Controlled expanded state */
expanded?: boolean;
/** Called when the row is toggled */
onToggleExpand?: () => void;
/** Class name added to the root element */
className?: string;
/** Inline styles added to the root element */
style?: React.CSSProperties;
}
/** Accepts either a `tool-Thinking` part or an explicit timeline step */
export const ThinkingTool = memo(function ThinkingTool({
part,
step: externalStep,
state: externalState,
onComplete: externalOnComplete,
defaultOpen,
expanded,
onToggleExpand,
className,
style,
}: ThinkingToolProps) {
const hasExternalStep = Boolean(externalStep && externalState && externalOnComplete);
const fromPart = useToolStep(hasExternalStep ? undefined : part, 'Thinking', 'thinking');
let step: ToolCallStep;
let stepState: StepState;
let onComplete: () => void;
if (externalStep && externalState && externalOnComplete) {
step = externalStep;
stepState = externalState;
onComplete = externalOnComplete;
} else if (fromPart) {
step = fromPart.step;
stepState = fromPart.stepState;
onComplete = noopComplete;
} else {
return null;
}
return (
<ThinkingCollapsed
step={step}
state={stepState}
onComplete={onComplete}
defaultOpen={defaultOpen}
expanded={expanded}
onToggleExpand={onToggleExpand}
className={className}
style={style}
/>
);
});
ThinkingTool.displayName = 'ThinkingTool';
Depends on:
API reference
Prop
Type
Required
part
ToolPart
No
Tool part in AI SDK v5 shape, `input.thought` or string output is the reasoning text
step
ToolCallStep
No
Timeline step, used together with `state` and `onComplete` instead of `part`
state
StepState
No
Animation state of the step
onComplete
() => void
No
Called once `step.duration` elapses while animating
defaultOpen
boolean
No
Initial expanded state in uncontrolled mode
expanded
boolean
No
Controlled expanded state
onToggleExpand
() => void
No
Called when the row is toggled
className
string
No
Class name added to the root element
style
React.CSSProperties
No
Inline styles added to the root element