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

118 lines
3.0 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import React from 'react';
import { Box, Text } from 'ink';
import { ToolCallStatus } from '../../types.js';
import { GeminiRespondingSpinner } from '../GeminiRespondingSpinner.js';
import {
SHELL_COMMAND_NAME,
SHELL_NAME,
TOOL_STATUS,
} from '../../constants.js';
import { theme } from '../../semantic-colors.js';
import { SHELL_TOOL_NAME } from '@google/gemini-cli-core';
export const STATUS_INDICATOR_WIDTH = 3;
export type TextEmphasis = 'high' | 'medium' | 'low';
type ToolStatusIndicatorProps = {
status: ToolCallStatus;
name: string;
};
export const ToolStatusIndicator: React.FC<ToolStatusIndicatorProps> = ({
status,
name,
}) => {
const isShell =
name === SHELL_COMMAND_NAME ||
name === SHELL_NAME ||
name === SHELL_TOOL_NAME;
const statusColor = isShell ? theme.ui.symbol : theme.status.warning;
return (
<Box minWidth={STATUS_INDICATOR_WIDTH}>
{status === ToolCallStatus.Pending && (
<Text color={theme.status.success}>{TOOL_STATUS.PENDING}</Text>
)}
{status === ToolCallStatus.Executing && (
<GeminiRespondingSpinner
spinnerType="toggle"
nonRespondingDisplay={TOOL_STATUS.EXECUTING}
/>
)}
{status === ToolCallStatus.Success && (
<Text color={theme.status.success} aria-label={'Success:'}>
{TOOL_STATUS.SUCCESS}
</Text>
)}
{status === ToolCallStatus.Confirming && (
<Text color={statusColor} aria-label={'Confirming:'}>
{TOOL_STATUS.CONFIRMING}
</Text>
)}
{status === ToolCallStatus.Canceled && (
<Text color={statusColor} aria-label={'Canceled:'} bold>
{TOOL_STATUS.CANCELED}
</Text>
)}
{status === ToolCallStatus.Error && (
<Text color={theme.status.error} aria-label={'Error:'} bold>
{TOOL_STATUS.ERROR}
</Text>
)}
</Box>
);
};
type ToolInfoProps = {
name: string;
description: string;
status: ToolCallStatus;
emphasis: TextEmphasis;
};
export const ToolInfo: React.FC<ToolInfoProps> = ({
name,
description,
status,
emphasis,
}) => {
const nameColor = React.useMemo<string>(() => {
switch (emphasis) {
case 'high':
return theme.text.primary;
case 'medium':
return theme.text.primary;
case 'low':
return theme.text.secondary;
default: {
const exhaustiveCheck: never = emphasis;
return exhaustiveCheck;
}
}
}, [emphasis]);
return (
<Box overflow="hidden" height={1} flexGrow={1} flexShrink={1}>
<Text strikethrough={status === ToolCallStatus.Canceled} wrap="truncate">
<Text color={nameColor} bold>
{name}
</Text>{' '}
<Text color={theme.text.secondary}>{description}</Text>
</Text>
</Box>
);
};
export const TrailingIndicator: React.FC = () => (
<Text color={theme.text.primary} wrap="truncate">
{' '}
</Text>
);