summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/components/messages/ToolMessage.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/ui/components/messages/ToolMessage.tsx')
-rw-r--r--packages/cli/src/ui/components/messages/ToolMessage.tsx158
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>
+);