Files
gemini-cli/packages/cli/src/ui/components/messages/ThinkingMessage.tsx

92 lines
2.2 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import { useMemo } from 'react';
import { Box, Text } from 'ink';
import type { ThoughtSummary } from '@google/gemini-cli-core';
import { theme } from '../../semantic-colors.js';
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];
}
/**
* Renders a model's thought as a distinct bubble.
* Leverages Ink layout for wrapping and borders.
*/
export const ThinkingMessage: React.FC<ThinkingMessageProps> = ({
thought,
terminalWidth,
isFirstThinking,
}) => {
const fullLines = useMemo(() => normalizeThoughtLines(thought), [thought]);
if (fullLines.length === 0) {
return null;
}
return (
<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>
{fullLines[0]}
</Text>
)}
{fullLines.slice(1).map((line, index) => (
<Text key={`body-line-${index}`} color={theme.text.secondary} italic>
{line}
</Text>
))}
</Box>
</Box>
);
};