diff options
Diffstat (limited to 'packages/cli/src/ui/components/messages/ToolMessage.tsx')
| -rw-r--r-- | packages/cli/src/ui/components/messages/ToolMessage.tsx | 158 |
1 files changed, 110 insertions, 48 deletions
diff --git a/packages/cli/src/ui/components/messages/ToolMessage.tsx b/packages/cli/src/ui/components/messages/ToolMessage.tsx index 32b23b9e..32b3b7e8 100644 --- a/packages/cli/src/ui/components/messages/ToolMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolMessage.tsx @@ -12,8 +12,15 @@ import { DiffRenderer } from './DiffRenderer.js'; import { Colors } from '../../colors.js'; import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js'; +const STATIC_HEIGHT = 1; +const RESERVED_LINE_COUNT = 5; // for tool name, status, padding etc. +const STATUS_INDICATOR_WIDTH = 3; + +export type TextEmphasis = 'high' | 'medium' | 'low'; + export interface ToolMessageProps extends IndividualToolCallDisplay { availableTerminalHeight: number; + emphasis?: TextEmphasis; } export const ToolMessage: React.FC<ToolMessageProps> = ({ @@ -22,63 +29,45 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({ resultDisplay, status, availableTerminalHeight, + emphasis = 'medium', }) => { - const statusIndicatorWidth = 3; - const hasResult = resultDisplay && resultDisplay.toString().trim().length > 0; - const staticHeight = /* Header */ 1; - - let displayableResult = resultDisplay; - let hiddenLines = 0; + const contentHeightEstimate = + availableTerminalHeight - STATIC_HEIGHT - RESERVED_LINE_COUNT; + const resultIsString = + typeof resultDisplay === 'string' && resultDisplay.trim().length > 0; + const lines = React.useMemo( + () => (resultIsString ? resultDisplay.split('\n') : []), + [resultIsString, resultDisplay], + ); // Truncate the overall string content if it's too long. // MarkdownRenderer will handle specific truncation for code blocks within this content. - if (typeof resultDisplay === 'string' && resultDisplay.length > 0) { - const lines = resultDisplay.split('\n'); - // Estimate available height for this specific tool message content area - // This is a rough estimate; ideally, we'd have a more precise measurement. - const contentHeightEstimate = availableTerminalHeight - staticHeight - 5; // Subtracting lines for tool name, status, padding etc. - if (lines.length > contentHeightEstimate && contentHeightEstimate > 0) { - displayableResult = lines.slice(0, contentHeightEstimate).join('\n'); - hiddenLines = lines.length - contentHeightEstimate; - } - } + // Estimate available height for this specific tool message content area + // This is a rough estimate; ideally, we'd have a more precise measurement. + const displayableResult = React.useMemo( + () => + resultIsString + ? lines.slice(0, contentHeightEstimate).join('\n') + : resultDisplay, + [lines, resultIsString, contentHeightEstimate, resultDisplay], + ); + const hiddenLines = lines.length - contentHeightEstimate; return ( <Box paddingX={1} paddingY={0} flexDirection="column"> <Box minHeight={1}> {/* Status Indicator */} - <Box minWidth={statusIndicatorWidth}> - {(status === ToolCallStatus.Pending || - status === ToolCallStatus.Executing) && <Spinner type="dots" />} - {status === ToolCallStatus.Success && ( - <Text color={Colors.AccentGreen}>✔</Text> - )} - {status === ToolCallStatus.Confirming && ( - <Text color={Colors.AccentYellow}>?</Text> - )} - {status === ToolCallStatus.Canceled && ( - <Text color={Colors.AccentYellow} bold> - - - </Text> - )} - {status === ToolCallStatus.Error && ( - <Text color={Colors.AccentRed} bold> - x - </Text> - )} - </Box> - <Box> - <Text - wrap="truncate-end" - strikethrough={status === ToolCallStatus.Canceled} - > - <Text bold>{name}</Text>{' '} - <Text color={Colors.SubtleComment}>{description}</Text> - </Text> - </Box> + <ToolStatusIndicator status={status} /> + <ToolInfo + name={name} + status={status} + description={description} + emphasis={emphasis} + /> + {emphasis === 'high' && <TrailingIndicator />} </Box> - {hasResult && ( - <Box paddingLeft={statusIndicatorWidth} width="100%"> + {displayableResult && ( + <Box paddingLeft={STATUS_INDICATOR_WIDTH} width="100%"> <Box flexDirection="column"> {typeof displayableResult === 'string' && ( <Box flexDirection="column"> @@ -89,7 +78,7 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({ /> </Box> )} - {typeof displayableResult === 'object' && ( + {typeof displayableResult !== 'string' && ( <DiffRenderer diffContent={displayableResult.fileDiff} filename={displayableResult.fileName} @@ -109,3 +98,76 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({ </Box> ); }; + +type ToolStatusIndicator = { + status: ToolCallStatus; +}; +const ToolStatusIndicator: React.FC<ToolStatusIndicator> = ({ status }) => ( + <Box minWidth={STATUS_INDICATOR_WIDTH}> + {status === ToolCallStatus.Pending && ( + <Text color={Colors.AccentGreen}>o</Text> + )} + {status === ToolCallStatus.Executing && <Spinner type="dots" />} + {status === ToolCallStatus.Success && ( + <Text color={Colors.AccentGreen}>✔</Text> + )} + {status === ToolCallStatus.Confirming && ( + <Text color={Colors.AccentYellow}>?</Text> + )} + {status === ToolCallStatus.Canceled && ( + <Text color={Colors.AccentYellow} bold> + - + </Text> + )} + {status === ToolCallStatus.Error && ( + <Text color={Colors.AccentRed} bold> + x + </Text> + )} + </Box> +); + +type ToolInfo = { + name: string; + description: string; + status: ToolCallStatus; + emphasis: TextEmphasis; +}; +const ToolInfo: React.FC<ToolInfo> = ({ + name, + description, + status, + emphasis, +}) => { + const nameColor = React.useMemo<string>(() => { + switch (emphasis) { + case 'high': + return Colors.Foreground; + case 'medium': + return Colors.Foreground; + case 'low': + return Colors.SubtleComment; + default: { + const exhaustiveCheck: never = emphasis; + return exhaustiveCheck; + } + } + }, [emphasis]); + return ( + <Box> + <Text + wrap="truncate-end" + strikethrough={status === ToolCallStatus.Canceled} + > + <Text color={nameColor} bold> + {name} + </Text>{' '} + <Text color={Colors.SubtleComment}>{description}</Text> + </Text> + </Box> + ); +}; + +const TrailingIndicator: React.FC = () => ( + <Text color={Colors.Foreground}> ←</Text> +); |
