Skip to content

Commit 9c41d60

Browse files
committed
Thinking block UX: show up to 5 lines, collapse all after finished streaming
1 parent 55671c5 commit 9c41d60

File tree

4 files changed

+31
-2
lines changed

4 files changed

+31
-2
lines changed

cli/src/components/blocks/agent-branch-wrapper.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ interface AgentBodyProps {
3939

4040
/** Props stored in ref for stable handler access in AgentBody */
4141
interface AgentBodyPropsRef {
42+
agentBlock: AgentContentBlock
4243
keyPrefix: string
4344
nestedBlocks: ContentBlock[]
4445
parentIsStreaming: boolean
@@ -87,6 +88,7 @@ const AgentBody = memo(
8788
// Store props in ref for stable handler access (avoids 12+ useMemo dependencies)
8889
const propsRef = useRef<AgentBodyPropsRef>(null!)
8990
propsRef.current = {
91+
agentBlock,
9092
keyPrefix,
9193
nestedBlocks,
9294
parentIsStreaming,
@@ -112,6 +114,7 @@ const AgentBody = memo(
112114
onToggleCollapsed={p.onToggleCollapsed}
113115
availableWidth={p.availableWidth}
114116
isNested={true}
117+
isMessageComplete={p.agentBlock.status === 'complete'}
115118
/>
116119
)
117120
},

cli/src/components/blocks/blocks-renderer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export const BlocksRenderer = memo(
100100
onToggleCollapsed={p.onToggleCollapsed}
101101
availableWidth={p.availableWidth}
102102
isNested={false}
103+
isMessageComplete={p.isComplete ?? false}
103104
/>
104105
)
105106
},

cli/src/components/blocks/thinking-block.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ interface ThinkingBlockProps {
1313
onToggleCollapsed: (id: string) => void
1414
availableWidth: number
1515
isNested: boolean
16+
/** Whether the parent message is complete (used to hide native reasoning blocks) */
17+
isMessageComplete: boolean
1618
}
1719

1820
export const ThinkingBlock = memo(
@@ -21,6 +23,7 @@ export const ThinkingBlock = memo(
2123
onToggleCollapsed,
2224
availableWidth,
2325
isNested,
26+
isMessageComplete,
2427
}: ThinkingBlockProps) => {
2528
const firstBlock = blocks[0]
2629
const thinkingId = firstBlock?.thinkingId
@@ -39,6 +42,14 @@ export const ThinkingBlock = memo(
3942
}
4043
}, [onToggleCollapsed, thinkingId])
4144

45+
// thinkingOpen === true means still streaming
46+
// thinkingOpen === false means explicitly closed with </think> tag
47+
// thinkingOpen === undefined means native reasoning block - complete when message is complete
48+
const isThinkingComplete =
49+
firstBlock?.thinkingOpen === false ||
50+
(firstBlock?.thinkingOpen === undefined && isMessageComplete)
51+
52+
// Hide if no content or no thinkingId (but NOT when thinking is complete)
4253
if (!combinedContent || !thinkingId) {
4354
return null
4455
}
@@ -48,6 +59,7 @@ export const ThinkingBlock = memo(
4859
<Thinking
4960
content={combinedContent}
5061
isCollapsed={isCollapsed}
62+
isThinkingComplete={isThinkingComplete}
5163
onToggle={handleToggle}
5264
availableWidth={availWidth}
5365
/>

cli/src/components/thinking.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import { useTerminalDimensions } from '../hooks/use-terminal-dimensions'
66
import { useTheme } from '../hooks/use-theme'
77
import { getLastNVisualLines } from '../utils/text-layout'
88

9-
const PREVIEW_LINE_COUNT = 3
9+
const PREVIEW_LINE_COUNT = 5
1010

1111
interface ThinkingProps {
1212
content: string
1313
isCollapsed: boolean
14+
/** Whether the thinking has completed (streaming finished) */
15+
isThinkingComplete: boolean
1416
onToggle: () => void
1517
availableWidth?: number
1618
}
@@ -19,6 +21,7 @@ export const Thinking = memo(
1921
({
2022
content,
2123
isCollapsed,
24+
isThinkingComplete,
2225
onToggle,
2326
availableWidth,
2427
}: ThinkingProps): ReactNode => {
@@ -36,6 +39,13 @@ export const Thinking = memo(
3639
PREVIEW_LINE_COUNT,
3740
)
3841

42+
// Toggle indicator: show caret when complete, bullet when streaming
43+
const toggleIndicator = isThinkingComplete
44+
? isCollapsed
45+
? '▸ '
46+
: '▾ '
47+
: '• '
48+
3949
return (
4050
<Button
4151
style={{
@@ -47,10 +57,13 @@ export const Thinking = memo(
4757
onClick={onToggle}
4858
>
4959
<text style={{ fg: theme.foreground }}>
50-
<span></span>
60+
<span>{toggleIndicator}</span>
5161
<span attributes={TextAttributes.BOLD}>Thinking</span>
5262
</text>
5363
{isCollapsed ? (
64+
// When complete: show no preview (just "▸ Thinking")
65+
// When streaming: show up to 5 lines preview
66+
!isThinkingComplete &&
5467
lines.length > 0 && (
5568
<box style={{ paddingLeft: 2 }}>
5669
<text

0 commit comments

Comments
 (0)