diff options
| author | Taylor Mullen <[email protected]> | 2025-04-17 18:06:21 -0400 |
|---|---|---|
| committer | N. Taylor Mullen <[email protected]> | 2025-04-17 15:29:34 -0700 |
| commit | cfc697a96d2e716a75e1c3b7f0f34fce81abaf1e (patch) | |
| tree | e06bcba67ca71a874048aa887b17457dbd409bdf /packages/cli/src/ui/components | |
| parent | 7928c1727f0b208ed34850cc89bbb36ea3dd23e5 (diff) | |
Run `npm run format`
- Also updated README.md accordingly.
Part of https://b.corp.google.com/issues/411384603
Diffstat (limited to 'packages/cli/src/ui/components')
14 files changed, 372 insertions, 287 deletions
diff --git a/packages/cli/src/ui/components/Footer.tsx b/packages/cli/src/ui/components/Footer.tsx index 06e6c681..215a4868 100644 --- a/packages/cli/src/ui/components/Footer.tsx +++ b/packages/cli/src/ui/components/Footer.tsx @@ -2,20 +2,18 @@ import React from 'react'; import { Box, Text } from 'ink'; interface FooterProps { - queryLength: number; + queryLength: number; } const Footer: React.FC<FooterProps> = ({ queryLength }) => { - return ( - <Box marginTop={1} justifyContent="space-between"> - <Box minWidth={15}> - <Text color="gray"> - {queryLength === 0 ? "? for shortcuts" : ""} - </Text> - </Box> - <Text color="blue">Gemini</Text> - </Box> - ); + return ( + <Box marginTop={1} justifyContent="space-between"> + <Box minWidth={15}> + <Text color="gray">{queryLength === 0 ? '? for shortcuts' : ''}</Text> + </Box> + <Text color="blue">Gemini</Text> + </Box> + ); }; -export default Footer;
\ No newline at end of file +export default Footer; diff --git a/packages/cli/src/ui/components/Header.tsx b/packages/cli/src/ui/components/Header.tsx index c5a99a30..37d42b57 100644 --- a/packages/cli/src/ui/components/Header.tsx +++ b/packages/cli/src/ui/components/Header.tsx @@ -4,35 +4,37 @@ import { UI_WIDTH, BOX_PADDING_X } from '../constants.js'; import { shortenPath } from '../../utils/paths.js'; interface HeaderProps { - cwd: string; + cwd: string; } const Header: React.FC<HeaderProps> = ({ cwd }) => { - return ( - <> - {/* Static Header Art */} - <Box marginBottom={1}> - <Text color="blue">{` + return ( + <> + {/* Static Header Art */} + <Box marginBottom={1}> + <Text color="blue">{` ______ ________ ____ ____ _____ ____ _____ _____ .' ___ ||_ __ ||_ \\ / _||_ _||_ \\|_ _||_ _| / .' \\_| | |_ \\_| | \\/ | | | | \\ | | | | | | ____ | _| _ | |\\ /| | | | | |\\ \\| | | | \\ \`.___] |_| |__/ | _| |_\\/_| |_ _| |_ _| |_\\ |_ _| |_ \`._____.'|________||_____||_____||_____||_____|\\____||_____|`}</Text> - </Box> - {/* CWD Display */} - <Box - borderStyle="round" - borderColor="blue" - paddingX={BOX_PADDING_X} - flexDirection="column" - marginBottom={1} - width={UI_WIDTH} - > - <Box paddingLeft={2}><Text color="gray">cwd: {shortenPath(cwd, /*maxLength*/ 70)}</Text></Box> - </Box> - </> - ); + </Box> + {/* CWD Display */} + <Box + borderStyle="round" + borderColor="blue" + paddingX={BOX_PADDING_X} + flexDirection="column" + marginBottom={1} + width={UI_WIDTH} + > + <Box paddingLeft={2}> + <Text color="gray">cwd: {shortenPath(cwd, /*maxLength*/ 70)}</Text> + </Box> + </Box> + </> + ); }; -export default Header;
\ No newline at end of file +export default Header; diff --git a/packages/cli/src/ui/components/HistoryDisplay.tsx b/packages/cli/src/ui/components/HistoryDisplay.tsx index bacbb258..285a6e30 100644 --- a/packages/cli/src/ui/components/HistoryDisplay.tsx +++ b/packages/cli/src/ui/components/HistoryDisplay.tsx @@ -10,30 +10,33 @@ import ToolGroupMessage from './messages/ToolGroupMessage.js'; import { PartListUnion } from '@google/genai'; interface HistoryDisplayProps { - history: HistoryItem[]; - onSubmit: (value: PartListUnion) => void; + history: HistoryItem[]; + onSubmit: (value: PartListUnion) => void; } -const HistoryDisplay: React.FC<HistoryDisplayProps> = ({ history, onSubmit }) => { - // No grouping logic needed here anymore - return ( - <Box flexDirection="column"> - {history.map((item) => ( - <Box key={item.id} marginBottom={1}> - {/* Render standard message types */} - {item.type === 'user' && <UserMessage text={item.text} />} - {item.type === 'gemini' && <GeminiMessage text={item.text} />} - {item.type === 'info' && <InfoMessage text={item.text} />} - {item.type === 'error' && <ErrorMessage text={item.text} />} +const HistoryDisplay: React.FC<HistoryDisplayProps> = ({ + history, + onSubmit, +}) => { + // No grouping logic needed here anymore + return ( + <Box flexDirection="column"> + {history.map((item) => ( + <Box key={item.id} marginBottom={1}> + {/* Render standard message types */} + {item.type === 'user' && <UserMessage text={item.text} />} + {item.type === 'gemini' && <GeminiMessage text={item.text} />} + {item.type === 'info' && <InfoMessage text={item.text} />} + {item.type === 'error' && <ErrorMessage text={item.text} />} - {/* Render the tool group component */} - {item.type === 'tool_group' && ( - <ToolGroupMessage toolCalls={item.tools} onSubmit={onSubmit} /> - )} - </Box> - ))} + {/* Render the tool group component */} + {item.type === 'tool_group' && ( + <ToolGroupMessage toolCalls={item.tools} onSubmit={onSubmit} /> + )} </Box> - ); + ))} + </Box> + ); }; -export default HistoryDisplay;
\ No newline at end of file +export default HistoryDisplay; diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index 92be10a4..b1832c04 100644 --- a/packages/cli/src/ui/components/InputPrompt.tsx +++ b/packages/cli/src/ui/components/InputPrompt.tsx @@ -3,37 +3,32 @@ import { Box, Text } from 'ink'; import TextInput from 'ink-text-input'; interface InputPromptProps { - query: string; - setQuery: (value: string) => void; - onSubmit: (value: string) => void; - isActive: boolean; + query: string; + setQuery: (value: string) => void; + onSubmit: (value: string) => void; + isActive: boolean; } const InputPrompt: React.FC<InputPromptProps> = ({ - query, - setQuery, - onSubmit, + query, + setQuery, + onSubmit, }) => { - return ( - <Box - marginTop={1} - borderStyle="round" - borderColor={'white'} - paddingX={1} - > - <Text color={'white'}>> </Text> - <Box flexGrow={1}> - <TextInput - value={query} - onChange={setQuery} - onSubmit={onSubmit} - showCursor={true} - focus={true} - placeholder={'Ask Gemini... (try "/init" or "/help")'} - /> - </Box> - </Box> - ); + return ( + <Box marginTop={1} borderStyle="round" borderColor={'white'} paddingX={1}> + <Text color={'white'}>> </Text> + <Box flexGrow={1}> + <TextInput + value={query} + onChange={setQuery} + onSubmit={onSubmit} + showCursor={true} + focus={true} + placeholder={'Ask Gemini... (try "/init" or "/help")'} + /> + </Box> + </Box> + ); }; -export default InputPrompt;
\ No newline at end of file +export default InputPrompt; diff --git a/packages/cli/src/ui/components/LoadingIndicator.tsx b/packages/cli/src/ui/components/LoadingIndicator.tsx index 8a3f9b5e..442ddf26 100644 --- a/packages/cli/src/ui/components/LoadingIndicator.tsx +++ b/packages/cli/src/ui/components/LoadingIndicator.tsx @@ -3,30 +3,32 @@ import { Box, Text } from 'ink'; import Spinner from 'ink-spinner'; interface LoadingIndicatorProps { - isLoading: boolean; - currentLoadingPhrase: string; - elapsedTime: number; + isLoading: boolean; + currentLoadingPhrase: string; + elapsedTime: number; } const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({ - isLoading, - currentLoadingPhrase, - elapsedTime, + isLoading, + currentLoadingPhrase, + elapsedTime, }) => { - if (!isLoading) { - return null; // Don't render anything if not loading - } + if (!isLoading) { + return null; // Don't render anything if not loading + } - return ( - <Box marginTop={1} paddingLeft={0}> - <Box marginRight={1}> - <Spinner type="dots" /> - </Box> - <Text color="cyan">{currentLoadingPhrase} ({elapsedTime}s)</Text> - <Box flexGrow={1}>{/* Spacer */}</Box> - <Text color="gray">(ESC to cancel)</Text> - </Box> - ); + return ( + <Box marginTop={1} paddingLeft={0}> + <Box marginRight={1}> + <Spinner type="dots" /> + </Box> + <Text color="cyan"> + {currentLoadingPhrase} ({elapsedTime}s) + </Text> + <Box flexGrow={1}>{/* Spacer */}</Box> + <Text color="gray">(ESC to cancel)</Text> + </Box> + ); }; -export default LoadingIndicator;
\ No newline at end of file +export default LoadingIndicator; diff --git a/packages/cli/src/ui/components/Tips.tsx b/packages/cli/src/ui/components/Tips.tsx index 5dbe60b2..88a14407 100644 --- a/packages/cli/src/ui/components/Tips.tsx +++ b/packages/cli/src/ui/components/Tips.tsx @@ -3,15 +3,20 @@ import { Box, Text } from 'ink'; import { UI_WIDTH } from '../constants.js'; const Tips: React.FC = () => { - return ( - <Box flexDirection="column" marginBottom={1} width={UI_WIDTH}> - <Text>Tips for getting started:</Text> - <Text>1. <Text bold>/help</Text> for more information.</Text> - <Text>2. <Text bold>/init</Text> to create a GEMINI.md for instructions & context.</Text> - <Text>3. Ask coding questions, edit code or run commands.</Text> - <Text>4. Be specific for the best results.</Text> - </Box> - ); + return ( + <Box flexDirection="column" marginBottom={1} width={UI_WIDTH}> + <Text>Tips for getting started:</Text> + <Text> + 1. <Text bold>/help</Text> for more information. + </Text> + <Text> + 2. <Text bold>/init</Text> to create a GEMINI.md for instructions & + context. + </Text> + <Text>3. Ask coding questions, edit code or run commands.</Text> + <Text>4. Be specific for the best results.</Text> + </Box> + ); }; -export default Tips;
\ No newline at end of file +export default Tips; diff --git a/packages/cli/src/ui/components/messages/DiffRenderer.tsx b/packages/cli/src/ui/components/messages/DiffRenderer.tsx index 5cae9004..a45efe2a 100644 --- a/packages/cli/src/ui/components/messages/DiffRenderer.tsx +++ b/packages/cli/src/ui/components/messages/DiffRenderer.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Box, Text } from 'ink' +import { Box, Text } from 'ink'; interface DiffLine { type: 'add' | 'del' | 'context' | 'hunk' | 'other'; @@ -30,29 +30,53 @@ function parseDiffWithLineNumbers(diffContent: string): DiffLine[] { continue; } if (!inHunk) { - // Skip standard Git header lines more robustly - if (line.startsWith('--- ') || line.startsWith('+++ ') || line.startsWith('diff --git') || line.startsWith('index ') || line.startsWith('similarity index') || line.startsWith('rename from') || line.startsWith('rename to') || line.startsWith('new file mode') || line.startsWith('deleted file mode')) continue; + // Skip standard Git header lines more robustly + if ( + line.startsWith('--- ') || + line.startsWith('+++ ') || + line.startsWith('diff --git') || + line.startsWith('index ') || + line.startsWith('similarity index') || + line.startsWith('rename from') || + line.startsWith('rename to') || + line.startsWith('new file mode') || + line.startsWith('deleted file mode') + ) + continue; // If it's not a hunk or header, skip (or handle as 'other' if needed) continue; } if (line.startsWith('+')) { currentNewLine++; // Increment before pushing - result.push({ type: 'add', newLine: currentNewLine, content: line.substring(1) }); + result.push({ + type: 'add', + newLine: currentNewLine, + content: line.substring(1), + }); } else if (line.startsWith('-')) { currentOldLine++; // Increment before pushing - result.push({ type: 'del', oldLine: currentOldLine, content: line.substring(1) }); + result.push({ + type: 'del', + oldLine: currentOldLine, + content: line.substring(1), + }); } else if (line.startsWith(' ')) { currentOldLine++; // Increment before pushing currentNewLine++; - result.push({ type: 'context', oldLine: currentOldLine, newLine: currentNewLine, content: line.substring(1) }); - } else if (line.startsWith('\\')) { // Handle "\ No newline at end of file" + result.push({ + type: 'context', + oldLine: currentOldLine, + newLine: currentNewLine, + content: line.substring(1), + }); + } else if (line.startsWith('\\')) { + // Handle "\ No newline at end of file" result.push({ type: 'other', content: line }); } } return result; } - interface DiffRendererProps { diffContent: string; filename?: string; @@ -61,7 +85,10 @@ interface DiffRendererProps { const DEFAULT_TAB_WIDTH = 4; // Spaces per tab for normalization -const DiffRenderer: React.FC<DiffRendererProps> = ({ diffContent, tabWidth = DEFAULT_TAB_WIDTH }) => { +const DiffRenderer: React.FC<DiffRendererProps> = ({ + diffContent, + tabWidth = DEFAULT_TAB_WIDTH, +}) => { if (!diffContent || typeof diffContent !== 'string') { return <Text color="yellow">No diff content.</Text>; } @@ -69,14 +96,15 @@ const DiffRenderer: React.FC<DiffRendererProps> = ({ diffContent, tabWidth = DEF const parsedLines = parseDiffWithLineNumbers(diffContent); // 1. Normalize whitespace (replace tabs with spaces) *before* further processing - const normalizedLines = parsedLines.map(line => ({ + const normalizedLines = parsedLines.map((line) => ({ ...line, - content: line.content.replace(/\t/g, ' '.repeat(tabWidth)) + content: line.content.replace(/\t/g, ' '.repeat(tabWidth)), })); // Filter out non-displayable lines (hunks, potentially 'other') using the normalized list - const displayableLines = normalizedLines.filter(l => l.type !== 'hunk' && l.type !== 'other'); - + const displayableLines = normalizedLines.filter( + (l) => l.type !== 'hunk' && l.type !== 'other', + ); if (displayableLines.length === 0) { return ( @@ -93,7 +121,7 @@ const DiffRenderer: React.FC<DiffRendererProps> = ({ diffContent, tabWidth = DEF if (line.content.trim() === '') continue; const firstCharIndex = line.content.search(/\S/); // Find index of first non-whitespace char - const currentIndent = (firstCharIndex === -1) ? 0 : firstCharIndex; // Indent is 0 if no non-whitespace found + const currentIndent = firstCharIndex === -1 ? 0 : firstCharIndex; // Indent is 0 if no non-whitespace found baseIndentation = Math.min(baseIndentation, currentIndent); } // If baseIndentation remained Infinity (e.g., no displayable lines with content), default to 0 @@ -102,7 +130,6 @@ const DiffRenderer: React.FC<DiffRendererProps> = ({ diffContent, tabWidth = DEF } // --- End Modification --- - return ( <Box borderStyle="round" borderColor="gray" flexDirection="column"> {/* Iterate over the lines that should be displayed (already normalized) */} @@ -139,9 +166,13 @@ const DiffRenderer: React.FC<DiffRendererProps> = ({ diffContent, tabWidth = DEF return ( // Using your original rendering structure <Box key={key} flexDirection="row"> - <Text color="gray">{gutterNumStr} </Text> - <Text color={color} dimColor={dim}>{prefixSymbol} </Text> - <Text color={color} dimColor={dim} wrap="wrap">{displayContent}</Text> + <Text color="gray">{gutterNumStr} </Text> + <Text color={color} dimColor={dim}> + {prefixSymbol}{' '} + </Text> + <Text color={color} dimColor={dim} wrap="wrap"> + {displayContent} + </Text> </Box> ); })} @@ -149,4 +180,4 @@ const DiffRenderer: React.FC<DiffRendererProps> = ({ diffContent, tabWidth = DEF ); }; -export default DiffRenderer;
\ No newline at end of file +export default DiffRenderer; diff --git a/packages/cli/src/ui/components/messages/ErrorMessage.tsx b/packages/cli/src/ui/components/messages/ErrorMessage.tsx index 7ed8f4f2..fb7f9fa5 100644 --- a/packages/cli/src/ui/components/messages/ErrorMessage.tsx +++ b/packages/cli/src/ui/components/messages/ErrorMessage.tsx @@ -2,23 +2,25 @@ import React from 'react'; import { Text, Box } from 'ink'; interface ErrorMessageProps { - text: string; + text: string; } const ErrorMessage: React.FC<ErrorMessageProps> = ({ text }) => { - const prefix = '✕ '; - const prefixWidth = prefix.length; + const prefix = '✕ '; + const prefixWidth = prefix.length; - return ( - <Box flexDirection="row"> - <Box width={prefixWidth}> - <Text color="red">{prefix}</Text> - </Box> - <Box flexGrow={1}> - <Text wrap="wrap" color="red">{text}</Text> - </Box> - </Box> - ); + return ( + <Box flexDirection="row"> + <Box width={prefixWidth}> + <Text color="red">{prefix}</Text> + </Box> + <Box flexGrow={1}> + <Text wrap="wrap" color="red"> + {text} + </Text> + </Box> + </Box> + ); }; -export default ErrorMessage;
\ No newline at end of file +export default ErrorMessage; diff --git a/packages/cli/src/ui/components/messages/GeminiMessage.tsx b/packages/cli/src/ui/components/messages/GeminiMessage.tsx index fe09eb33..ccccbfc6 100644 --- a/packages/cli/src/ui/components/messages/GeminiMessage.tsx +++ b/packages/cli/src/ui/components/messages/GeminiMessage.tsx @@ -3,42 +3,42 @@ import { Text, Box } from 'ink'; import { MarkdownRenderer } from '../../utils/MarkdownRenderer.js'; interface GeminiMessageProps { - text: string; + text: string; } const GeminiMessage: React.FC<GeminiMessageProps> = ({ text }) => { - const prefix = '✦ '; - const prefixWidth = prefix.length; + const prefix = '✦ '; + const prefixWidth = prefix.length; - // Handle potentially null or undefined text gracefully - const safeText = text || ''; + // Handle potentially null or undefined text gracefully + const safeText = text || ''; - // Use the static render method from the MarkdownRenderer class - // Pass safeText which is guaranteed to be a string - const renderedBlocks = MarkdownRenderer.render(safeText); - - // If the original text was actually empty/null, render the minimal state - if (!safeText && renderedBlocks.length === 0) { - return ( - <Box flexDirection="row"> - <Box width={prefixWidth}> - <Text color="blue">{prefix}</Text> - </Box> - <Box flexGrow={1}></Box> - </Box> - ); - } + // Use the static render method from the MarkdownRenderer class + // Pass safeText which is guaranteed to be a string + const renderedBlocks = MarkdownRenderer.render(safeText); + // If the original text was actually empty/null, render the minimal state + if (!safeText && renderedBlocks.length === 0) { return ( - <Box flexDirection="row"> - <Box width={prefixWidth}> - <Text color="blue">{prefix}</Text> - </Box> - <Box flexGrow={1} flexDirection="column"> - {renderedBlocks} - </Box> + <Box flexDirection="row"> + <Box width={prefixWidth}> + <Text color="blue">{prefix}</Text> </Box> + <Box flexGrow={1}></Box> + </Box> ); + } + + return ( + <Box flexDirection="row"> + <Box width={prefixWidth}> + <Text color="blue">{prefix}</Text> + </Box> + <Box flexGrow={1} flexDirection="column"> + {renderedBlocks} + </Box> + </Box> + ); }; -export default GeminiMessage;
\ No newline at end of file +export default GeminiMessage; diff --git a/packages/cli/src/ui/components/messages/InfoMessage.tsx b/packages/cli/src/ui/components/messages/InfoMessage.tsx index 8f5841b2..e6f24859 100644 --- a/packages/cli/src/ui/components/messages/InfoMessage.tsx +++ b/packages/cli/src/ui/components/messages/InfoMessage.tsx @@ -2,23 +2,25 @@ import React from 'react'; import { Text, Box } from 'ink'; interface InfoMessageProps { - text: string; + text: string; } const InfoMessage: React.FC<InfoMessageProps> = ({ text }) => { - const prefix = 'ℹ '; - const prefixWidth = prefix.length; + const prefix = 'ℹ '; + const prefixWidth = prefix.length; - return ( - <Box flexDirection="row"> - <Box width={prefixWidth}> - <Text color="yellow">{prefix}</Text> - </Box> - <Box flexGrow={1}> - <Text wrap="wrap" color="yellow">{text}</Text> - </Box> - </Box> - ); + return ( + <Box flexDirection="row"> + <Box width={prefixWidth}> + <Text color="yellow">{prefix}</Text> + </Box> + <Box flexGrow={1}> + <Text wrap="wrap" color="yellow"> + {text} + </Text> + </Box> + </Box> + ); }; -export default InfoMessage;
\ No newline at end of file +export default InfoMessage; diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx index a37d2f94..59c2cc42 100644 --- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx @@ -1,7 +1,12 @@ import React from 'react'; import { Box, Text, useInput } from 'ink'; import SelectInput from 'ink-select-input'; -import { ToolCallConfirmationDetails, ToolEditConfirmationDetails, ToolConfirmationOutcome, ToolExecuteConfirmationDetails } from '../../types.js'; // Adjust path as needed +import { + ToolCallConfirmationDetails, + ToolEditConfirmationDetails, + ToolConfirmationOutcome, + ToolExecuteConfirmationDetails, +} from '../../types.js'; // Adjust path as needed import { PartListUnion } from '@google/genai'; import DiffRenderer from './DiffRenderer.js'; import { UI_WIDTH } from '../../constants.js'; @@ -11,7 +16,9 @@ export interface ToolConfirmationMessageProps { onSubmit: (value: PartListUnion) => void; } -function isEditDetails(props: ToolCallConfirmationDetails): props is ToolEditConfirmationDetails { +function isEditDetails( + props: ToolCallConfirmationDetails, +): props is ToolEditConfirmationDetails { return (props as ToolEditConfirmationDetails).fileName !== undefined; } @@ -20,7 +27,9 @@ interface InternalOption { value: ToolConfirmationOutcome; } -const ToolConfirmationMessage: React.FC<ToolConfirmationMessageProps> = ({ confirmationDetails }) => { +const ToolConfirmationMessage: React.FC<ToolConfirmationMessageProps> = ({ + confirmationDetails, +}) => { const { onConfirm } = confirmationDetails; useInput((_, key) => { @@ -39,41 +48,53 @@ const ToolConfirmationMessage: React.FC<ToolConfirmationMessageProps> = ({ confi const options: InternalOption[] = []; if (isEditDetails(confirmationDetails)) { - title = "Edit"; // Title for the outer box + title = 'Edit'; // Title for the outer box // Body content is now the DiffRenderer, passing filename to it // The bordered box is removed from here and handled within DiffRenderer - bodyContent = ( - <DiffRenderer diffContent={confirmationDetails.fileDiff} /> - ); + bodyContent = <DiffRenderer diffContent={confirmationDetails.fileDiff} />; question = `Apply this change?`; options.push( - { label: '1. Yes, apply change', value: ToolConfirmationOutcome.ProceedOnce }, - { label: "2. Yes, always apply file edits", value: ToolConfirmationOutcome.ProceedAlways }, - { label: '3. No (esc)', value: ToolConfirmationOutcome.Cancel } + { + label: '1. Yes, apply change', + value: ToolConfirmationOutcome.ProceedOnce, + }, + { + label: '2. Yes, always apply file edits', + value: ToolConfirmationOutcome.ProceedAlways, + }, + { label: '3. No (esc)', value: ToolConfirmationOutcome.Cancel }, ); - } else { - const executionProps = confirmationDetails as ToolExecuteConfirmationDetails; - title = "Execute Command"; // Title for the outer box + const executionProps = + confirmationDetails as ToolExecuteConfirmationDetails; + title = 'Execute Command'; // Title for the outer box // For execution, we still need context display and description const commandDisplay = <Text color="cyan">{executionProps.command}</Text>; // Combine command and description into bodyContent for layout consistency bodyContent = ( - <Box flexDirection="column"> - <Box paddingX={1} marginLeft={1}>{commandDisplay}</Box> + <Box flexDirection="column"> + <Box paddingX={1} marginLeft={1}> + {commandDisplay} </Box> + </Box> ); question = `Allow execution?`; const alwaysLabel = `2. Yes, always allow '${executionProps.rootCommand}' commands`; options.push( - { label: '1. Yes, allow once', value: ToolConfirmationOutcome.ProceedOnce }, - { label: alwaysLabel, value: ToolConfirmationOutcome.ProceedAlways }, - { label: '3. No (esc)', value: ToolConfirmationOutcome.Cancel } + { + label: '1. Yes, allow once', + value: ToolConfirmationOutcome.ProceedOnce, + }, + { + label: alwaysLabel, + value: ToolConfirmationOutcome.ProceedAlways, + }, + { label: '3. No (esc)', value: ToolConfirmationOutcome.Cancel }, ); } @@ -82,7 +103,7 @@ const ToolConfirmationMessage: React.FC<ToolConfirmationMessageProps> = ({ confi {/* Body Content (Diff Renderer or Command Info) */} {/* No separate context display here anymore for edits */} <Box flexGrow={1} flexShrink={1} overflow="hidden" marginBottom={1}> - {bodyContent} + {bodyContent} </Box> {/* Confirmation Question */} @@ -98,4 +119,4 @@ const ToolConfirmationMessage: React.FC<ToolConfirmationMessageProps> = ({ confi ); }; -export default ToolConfirmationMessage;
\ No newline at end of file +export default ToolConfirmationMessage; diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx index 6ef3c5fc..7317345b 100644 --- a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx @@ -6,42 +6,45 @@ import { PartListUnion } from '@google/genai'; import ToolConfirmationMessage from './ToolConfirmationMessage.js'; interface ToolGroupMessageProps { - toolCalls: IndividualToolCallDisplay[]; - onSubmit: (value: PartListUnion) => void; + toolCalls: IndividualToolCallDisplay[]; + onSubmit: (value: PartListUnion) => void; } // Main component renders the border and maps the tools using ToolMessage -const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({ toolCalls, onSubmit }) => { - const hasPending = toolCalls.some(t => t.status === ToolCallStatus.Pending); - const borderColor = hasPending ? "yellow" : "blue"; +const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({ + toolCalls, + onSubmit, +}) => { + const hasPending = toolCalls.some((t) => t.status === ToolCallStatus.Pending); + const borderColor = hasPending ? 'yellow' : 'blue'; - return ( - <Box - flexDirection="column" - borderStyle="round" - borderColor={borderColor} - > - {toolCalls.map((tool) => { - return ( - <React.Fragment key={tool.callId}> - <ToolMessage - key={tool.callId} // Use callId as the key - name={tool.name} - description={tool.description} - resultDisplay={tool.resultDisplay} - status={tool.status} - /> - {tool.status === ToolCallStatus.Confirming && tool.confirmationDetails && ( - <ToolConfirmationMessage confirmationDetails={tool.confirmationDetails} onSubmit={onSubmit}></ToolConfirmationMessage> - )} - </React.Fragment> - ); - })} - {/* Optional: Add padding below the last item if needed, + return ( + <Box flexDirection="column" borderStyle="round" borderColor={borderColor}> + {toolCalls.map((tool) => { + return ( + <React.Fragment key={tool.callId}> + <ToolMessage + key={tool.callId} // Use callId as the key + name={tool.name} + description={tool.description} + resultDisplay={tool.resultDisplay} + status={tool.status} + /> + {tool.status === ToolCallStatus.Confirming && + tool.confirmationDetails && ( + <ToolConfirmationMessage + confirmationDetails={tool.confirmationDetails} + onSubmit={onSubmit} + ></ToolConfirmationMessage> + )} + </React.Fragment> + ); + })} + {/* Optional: Add padding below the last item if needed, though ToolMessage already has some vertical space implicitly */} - {/* {tools.length > 0 && <Box height={1} />} */} - </Box> - ); + {/* {tools.length > 0 && <Box height={1} />} */} + </Box> + ); }; export default ToolGroupMessage; diff --git a/packages/cli/src/ui/components/messages/ToolMessage.tsx b/packages/cli/src/ui/components/messages/ToolMessage.tsx index f8db54c4..cd18dae2 100644 --- a/packages/cli/src/ui/components/messages/ToolMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolMessage.tsx @@ -7,47 +7,68 @@ import DiffRenderer from './DiffRenderer.js'; import { MarkdownRenderer } from '../../utils/MarkdownRenderer.js'; interface ToolMessageProps { - name: string; - description: string; - resultDisplay: ToolResultDisplay | undefined; - status: ToolCallStatus; + name: string; + description: string; + resultDisplay: ToolResultDisplay | undefined; + status: ToolCallStatus; } -const ToolMessage: React.FC<ToolMessageProps> = ({ name, description, resultDisplay, status }) => { - const statusIndicatorWidth = 3; - const hasResult = (status === ToolCallStatus.Invoked || status === ToolCallStatus.Canceled) && resultDisplay && resultDisplay.toString().trim().length > 0; +const ToolMessage: React.FC<ToolMessageProps> = ({ + name, + description, + resultDisplay, + status, +}) => { + const statusIndicatorWidth = 3; + const hasResult = + (status === ToolCallStatus.Invoked || status === ToolCallStatus.Canceled) && + resultDisplay && + resultDisplay.toString().trim().length > 0; - return ( - <Box paddingX={1} paddingY={0} flexDirection="column"> - {/* Row for Status Indicator and Tool Info */} - <Box minHeight={1}> - {/* Status Indicator */} - <Box minWidth={statusIndicatorWidth}> - {status === ToolCallStatus.Pending && <Spinner type="dots" />} - {status === ToolCallStatus.Invoked && <Text color="green">✔</Text>} - {status === ToolCallStatus.Confirming && <Text color="blue">?</Text>} - {status === ToolCallStatus.Canceled && <Text color="red" bold>-</Text>} - - </Box> - <Box> - <Text color="blue" wrap="truncate-end" strikethrough={status === ToolCallStatus.Canceled}> - <Text bold>{name}</Text> <Text color="gray">{description}</Text> - </Text> - </Box> - </Box> + return ( + <Box paddingX={1} paddingY={0} flexDirection="column"> + {/* Row for Status Indicator and Tool Info */} + <Box minHeight={1}> + {/* Status Indicator */} + <Box minWidth={statusIndicatorWidth}> + {status === ToolCallStatus.Pending && <Spinner type="dots" />} + {status === ToolCallStatus.Invoked && <Text color="green">✔</Text>} + {status === ToolCallStatus.Confirming && <Text color="blue">?</Text>} + {status === ToolCallStatus.Canceled && ( + <Text color="red" bold> + - + </Text> + )} + </Box> + <Box> + <Text + color="blue" + wrap="truncate-end" + strikethrough={status === ToolCallStatus.Canceled} + > + <Text bold>{name}</Text> <Text color="gray">{description}</Text> + </Text> + </Box> + </Box> - {hasResult && ( - <Box paddingLeft={statusIndicatorWidth}> - <Box flexShrink={1} flexDirection="row"> - <Text color="gray">↳ </Text> - {/* Use default text color (white) or gray instead of dimColor */} - {typeof resultDisplay === 'string' && <Box flexDirection='column'>{MarkdownRenderer.render(resultDisplay)}</Box>} - {typeof resultDisplay === 'object' && <DiffRenderer diffContent={resultDisplay.fileDiff} />} - </Box> - </Box> + {hasResult && ( + <Box paddingLeft={statusIndicatorWidth}> + <Box flexShrink={1} flexDirection="row"> + <Text color="gray">↳ </Text> + {/* Use default text color (white) or gray instead of dimColor */} + {typeof resultDisplay === 'string' && ( + <Box flexDirection="column"> + {MarkdownRenderer.render(resultDisplay)} + </Box> + )} + {typeof resultDisplay === 'object' && ( + <DiffRenderer diffContent={resultDisplay.fileDiff} /> )} + </Box> </Box> - ); + )} + </Box> + ); }; export default ToolMessage; diff --git a/packages/cli/src/ui/components/messages/UserMessage.tsx b/packages/cli/src/ui/components/messages/UserMessage.tsx index 0dd451f6..08c0070f 100644 --- a/packages/cli/src/ui/components/messages/UserMessage.tsx +++ b/packages/cli/src/ui/components/messages/UserMessage.tsx @@ -2,23 +2,23 @@ import React from 'react'; import { Text, Box } from 'ink'; interface UserMessageProps { - text: string; + text: string; } const UserMessage: React.FC<UserMessageProps> = ({ text }) => { - const prefix = '> '; - const prefixWidth = prefix.length; + const prefix = '> '; + const prefixWidth = prefix.length; - return ( - <Box flexDirection="row"> - <Box width={prefixWidth}> - <Text color="gray">{prefix}</Text> - </Box> - <Box flexGrow={1}> - <Text wrap="wrap">{text}</Text> - </Box> - </Box> - ); + return ( + <Box flexDirection="row"> + <Box width={prefixWidth}> + <Text color="gray">{prefix}</Text> + </Box> + <Box flexGrow={1}> + <Text wrap="wrap">{text}</Text> + </Box> + </Box> + ); }; -export default UserMessage;
\ No newline at end of file +export default UserMessage; |
