feat(cli): overhaul thinking UI (#18725)

This commit is contained in:
Keith Guerin
2026-03-06 20:20:27 -08:00
committed by GitHub
parent 9455ecd78c
commit e5d58c2b5a
29 changed files with 763 additions and 184 deletions
@@ -13,6 +13,30 @@ import { normalizeEscapedNewlines } from '../../utils/textUtils.js';
interface ThinkingMessageProps {
thought: ThoughtSummary;
terminalWidth: number;
isFirstThinking?: boolean;
}
const THINKING_LEFT_PADDING = 1;
function normalizeThoughtLines(thought: ThoughtSummary): string[] {
const subject = normalizeEscapedNewlines(thought.subject).trim();
const description = normalizeEscapedNewlines(thought.description).trim();
if (!subject && !description) {
return [];
}
if (!subject) {
return description.split('\n');
}
if (!description) {
return [subject];
}
const bodyLines = description.split('\n');
return [subject, ...bodyLines];
}
/**
@@ -21,60 +45,47 @@ interface ThinkingMessageProps {
*/
export const ThinkingMessage: React.FC<ThinkingMessageProps> = ({
thought,
terminalWidth,
isFirstThinking,
}) => {
const { summary, body } = useMemo(() => {
const subject = normalizeEscapedNewlines(thought.subject).trim();
const description = normalizeEscapedNewlines(thought.description).trim();
const fullLines = useMemo(() => normalizeThoughtLines(thought), [thought]);
if (!subject && !description) {
return { summary: '', body: '' };
}
if (!subject) {
const lines = description
.split('\n')
.map((l) => l.trim())
.filter(Boolean);
return {
summary: lines[0] || '',
body: lines.slice(1).join('\n'),
};
}
return {
summary: subject,
body: description,
};
}, [thought]);
if (!summary && !body) {
if (fullLines.length === 0) {
return null;
}
return (
<Box width="100%" marginBottom={1} paddingLeft={1} flexDirection="column">
{summary && (
<Box paddingLeft={2}>
<Box width={terminalWidth} flexDirection="column">
{isFirstThinking && (
<Text color={theme.text.primary} italic>
{' '}
Thinking...{' '}
</Text>
)}
<Box
marginLeft={THINKING_LEFT_PADDING}
paddingLeft={1}
borderStyle="single"
borderLeft={true}
borderRight={false}
borderTop={false}
borderBottom={false}
borderColor={theme.text.secondary}
flexDirection="column"
>
<Text> </Text>
{fullLines.length > 0 && (
<Text color={theme.text.primary} bold italic>
{summary}
{fullLines[0]}
</Text>
</Box>
)}
{body && (
<Box
borderStyle="single"
borderLeft
borderRight={false}
borderTop={false}
borderBottom={false}
borderColor={theme.border.default}
paddingLeft={1}
>
<Text color={theme.text.secondary} italic>
{body}
)}
{fullLines.slice(1).map((line, index) => (
<Text key={`body-line-${index}`} color={theme.text.secondary} italic>
{line}
</Text>
</Box>
)}
))}
</Box>
</Box>
);
};