mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-15 05:47:18 -07:00
Implement compact UX for topic.
This commit is contained in:
@@ -17,6 +17,10 @@ import {
|
||||
isGrepResult,
|
||||
isListResult,
|
||||
isReadManyFilesResult,
|
||||
UPDATE_TOPIC_DISPLAY_NAME,
|
||||
UPDATE_TOPIC_TOOL_NAME,
|
||||
TOPIC_PARAM_TITLE,
|
||||
TOPIC_PARAM_STRATEGIC_INTENT,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { type IndividualToolCallDisplay, isTodoList } from '../../types.js';
|
||||
import { useAlternateBuffer } from '../../hooks/useAlternateBuffer.js';
|
||||
@@ -306,10 +310,33 @@ function getGenericSuccessData(
|
||||
return { description, summary, payload };
|
||||
}
|
||||
|
||||
function getUpdateTopicData(args?: Record<string, unknown>): ViewParts {
|
||||
const rawTitle = args?.[TOPIC_PARAM_TITLE];
|
||||
const title = typeof rawTitle === 'string' ? rawTitle : undefined;
|
||||
const rawIntent = args?.[TOPIC_PARAM_STRATEGIC_INTENT];
|
||||
const intent = typeof rawIntent === 'string' ? rawIntent : undefined;
|
||||
|
||||
const description = (
|
||||
<Text color={theme.text.primary} bold wrap="truncate-end">
|
||||
{title || 'Topic'}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const summary = intent ? (
|
||||
<Text color={theme.text.secondary} wrap="truncate-end">
|
||||
{' '}
|
||||
-- {intent}
|
||||
</Text>
|
||||
) : undefined;
|
||||
|
||||
return { description, summary };
|
||||
}
|
||||
|
||||
export const DenseToolMessage: React.FC<DenseToolMessageProps> = (props) => {
|
||||
const {
|
||||
callId,
|
||||
name,
|
||||
args,
|
||||
status,
|
||||
resultDisplay,
|
||||
confirmationDetails,
|
||||
@@ -323,6 +350,8 @@ export const DenseToolMessage: React.FC<DenseToolMessageProps> = (props) => {
|
||||
const isAlternateBuffer = useAlternateBuffer();
|
||||
const { isExpanded: isExpandedInContext, toggleExpansion } = useToolActions();
|
||||
|
||||
// ... (rest of component)
|
||||
|
||||
// Handle optional context members
|
||||
const [localIsExpanded, setLocalIsExpanded] = useState(false);
|
||||
const isExpanded = isExpandedInContext
|
||||
@@ -370,6 +399,9 @@ export const DenseToolMessage: React.FC<DenseToolMessageProps> = (props) => {
|
||||
|
||||
// State-to-View Coordination
|
||||
const viewParts = useMemo((): ViewParts => {
|
||||
if (name === UPDATE_TOPIC_DISPLAY_NAME || name === UPDATE_TOPIC_TOOL_NAME) {
|
||||
return getUpdateTopicData(args);
|
||||
}
|
||||
if (diff) {
|
||||
return getFileOpData(
|
||||
diff,
|
||||
@@ -431,6 +463,8 @@ export const DenseToolMessage: React.FC<DenseToolMessageProps> = (props) => {
|
||||
availableTerminalHeight,
|
||||
originalDescription,
|
||||
isAlternateBuffer,
|
||||
name,
|
||||
args,
|
||||
]);
|
||||
|
||||
const { description, summary } = viewParts;
|
||||
@@ -497,25 +531,42 @@ export const DenseToolMessage: React.FC<DenseToolMessageProps> = (props) => {
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Box marginLeft={2} flexDirection="row" flexWrap="wrap">
|
||||
<ToolStatusIndicator status={status} name={name} />
|
||||
<Box maxWidth={25} flexShrink={1} flexGrow={0}>
|
||||
<Text color={theme.text.primary} bold wrap="truncate-end">
|
||||
{name}{' '}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box marginLeft={1} flexShrink={1} flexGrow={0}>
|
||||
{description}
|
||||
</Box>
|
||||
{summary && (
|
||||
<Box
|
||||
key="tool-summary"
|
||||
ref={isAlternateBuffer && diff ? toggleRef : undefined}
|
||||
marginLeft={1}
|
||||
flexGrow={0}
|
||||
>
|
||||
{summary}
|
||||
</Box>
|
||||
<Box marginLeft={2} flexDirection="row">
|
||||
{name === UPDATE_TOPIC_DISPLAY_NAME ||
|
||||
name === UPDATE_TOPIC_TOOL_NAME ? (
|
||||
<>
|
||||
<Box flexShrink={1} flexGrow={0}>
|
||||
{description}
|
||||
</Box>
|
||||
{summary && (
|
||||
<Box marginLeft={1} flexGrow={0} flexShrink={1}>
|
||||
{summary}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ToolStatusIndicator status={status} name={name} />
|
||||
<Box maxWidth={25} flexShrink={1} flexGrow={0}>
|
||||
<Text color={theme.text.primary} bold wrap="truncate-end">
|
||||
{name}{' '}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box marginLeft={1} flexShrink={1} flexGrow={0}>
|
||||
{description}
|
||||
</Box>
|
||||
{summary && (
|
||||
<Box
|
||||
key="tool-summary"
|
||||
ref={isAlternateBuffer && diff ? toggleRef : undefined}
|
||||
marginLeft={1}
|
||||
flexGrow={0}
|
||||
flexShrink={1}
|
||||
>
|
||||
{summary}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ import {
|
||||
EDIT_DISPLAY_NAME,
|
||||
READ_FILE_DISPLAY_NAME,
|
||||
GLOB_DISPLAY_NAME,
|
||||
UPDATE_TOPIC_DISPLAY_NAME,
|
||||
TOPIC_PARAM_TITLE,
|
||||
TOPIC_PARAM_STRATEGIC_INTENT,
|
||||
} from '@google/gemini-cli-core';
|
||||
import os from 'node:os';
|
||||
import { createMockSettings } from '../../../test-utils/settings.js';
|
||||
@@ -464,6 +467,60 @@ describe('<ToolGroupMessage />', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
describe('UpdateTopicTool', () => {
|
||||
it('renders update_topic with Title -- Description format even when compact mode is disabled', async () => {
|
||||
const toolCalls = [
|
||||
createToolCall({
|
||||
name: UPDATE_TOPIC_DISPLAY_NAME,
|
||||
args: {
|
||||
[TOPIC_PARAM_TITLE]: 'Research',
|
||||
[TOPIC_PARAM_STRATEGIC_INTENT]: 'Researching Agent Skills',
|
||||
},
|
||||
}),
|
||||
];
|
||||
const item = createItem(toolCalls);
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: createMockSettings({
|
||||
ui: { compactToolOutput: false },
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Research');
|
||||
expect(output).toContain('-- Researching Agent Skills');
|
||||
expect(output).not.toContain('update_topic');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders update_topic with Topic -- Description format when title is missing', async () => {
|
||||
const toolCalls = [
|
||||
createToolCall({
|
||||
name: UPDATE_TOPIC_DISPLAY_NAME,
|
||||
args: {
|
||||
[TOPIC_PARAM_STRATEGIC_INTENT]: 'Tactical update',
|
||||
},
|
||||
}),
|
||||
];
|
||||
const item = createItem(toolCalls);
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
},
|
||||
);
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Topic');
|
||||
expect(output).toContain('-- Tactical update');
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders two tool groups where only the last line of the previous group is visible', async () => {
|
||||
const toolCalls1 = [
|
||||
createToolCall({
|
||||
|
||||
@@ -33,6 +33,8 @@ import {
|
||||
WEB_FETCH_DISPLAY_NAME,
|
||||
WRITE_FILE_DISPLAY_NAME,
|
||||
READ_MANY_FILES_DISPLAY_NAME,
|
||||
UPDATE_TOPIC_DISPLAY_NAME,
|
||||
UPDATE_TOPIC_TOOL_NAME,
|
||||
isFileDiff,
|
||||
isGrepResult,
|
||||
isListResult,
|
||||
@@ -51,6 +53,8 @@ const COMPACT_OUTPUT_ALLOWLIST = new Set([
|
||||
WEB_FETCH_DISPLAY_NAME,
|
||||
WRITE_FILE_DISPLAY_NAME,
|
||||
READ_MANY_FILES_DISPLAY_NAME,
|
||||
UPDATE_TOPIC_DISPLAY_NAME,
|
||||
UPDATE_TOPIC_TOOL_NAME,
|
||||
]);
|
||||
|
||||
// Helper to identify if a tool should use the compact view
|
||||
@@ -60,6 +64,15 @@ export const isCompactTool = (
|
||||
): boolean => {
|
||||
const hasCompactOutputSupport = COMPACT_OUTPUT_ALLOWLIST.has(tool.name);
|
||||
const displayStatus = mapCoreStatusToDisplayStatus(tool.status);
|
||||
|
||||
// update_topic always uses the dense/compact representation
|
||||
if (
|
||||
tool.name === UPDATE_TOPIC_DISPLAY_NAME ||
|
||||
tool.name === UPDATE_TOPIC_TOOL_NAME
|
||||
) {
|
||||
return displayStatus !== ToolCallStatus.Confirming;
|
||||
}
|
||||
|
||||
return (
|
||||
isCompactModeEnabled &&
|
||||
hasCompactOutputSupport &&
|
||||
|
||||
@@ -51,6 +51,7 @@ export function mapToDisplay(
|
||||
parentCallId: call.request.parentCallId,
|
||||
name: displayName,
|
||||
description,
|
||||
args: call.request.args,
|
||||
renderOutputAsMarkdown,
|
||||
};
|
||||
|
||||
|
||||
@@ -119,6 +119,7 @@ export interface IndividualToolCallDisplay {
|
||||
parentCallId?: string;
|
||||
name: string;
|
||||
description: string;
|
||||
args?: Record<string, unknown>;
|
||||
resultDisplay: ToolResultDisplay | undefined;
|
||||
status: CoreToolCallStatus;
|
||||
// True when the tool was initiated directly by the user (slash/@/shell flows).
|
||||
|
||||
@@ -193,6 +193,7 @@ export const GREP_DISPLAY_NAME = 'SearchText';
|
||||
export const WEB_SEARCH_DISPLAY_NAME = 'GoogleSearch';
|
||||
export const WEB_FETCH_DISPLAY_NAME = 'WebFetch';
|
||||
export const READ_MANY_FILES_DISPLAY_NAME = 'ReadManyFiles';
|
||||
export const UPDATE_TOPIC_DISPLAY_NAME = 'Update Topic Context';
|
||||
|
||||
/**
|
||||
* Mapping of legacy tool names to their current names.
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TOPIC_PARAM_SUMMARY,
|
||||
TOPIC_PARAM_STRATEGIC_INTENT,
|
||||
} from './definitions/coreTools.js';
|
||||
import { UPDATE_TOPIC_DISPLAY_NAME } from './tool-names.js';
|
||||
import {
|
||||
BaseDeclarativeTool,
|
||||
BaseToolInvocation,
|
||||
@@ -153,7 +154,7 @@ export class UpdateTopicTool extends BaseDeclarativeTool<
|
||||
const declaration = getUpdateTopicDeclaration();
|
||||
super(
|
||||
UPDATE_TOPIC_TOOL_NAME,
|
||||
'Update Topic Context',
|
||||
UPDATE_TOPIC_DISPLAY_NAME,
|
||||
declaration.description ?? '',
|
||||
Kind.Think,
|
||||
declaration.parametersJsonSchema,
|
||||
|
||||
Reference in New Issue
Block a user