mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-27 13:34:15 -07:00
feat: add experimental in-progress steering hints
This commit is contained in:
@@ -160,6 +160,8 @@ const baseMockUiState = {
|
||||
proQuotaRequest: null,
|
||||
validationRequest: null,
|
||||
},
|
||||
hintMode: false,
|
||||
hintBuffer: '',
|
||||
};
|
||||
|
||||
export const mockAppState: AppState = {
|
||||
@@ -209,6 +211,10 @@ const mockUIActions: UIActions = {
|
||||
setActiveBackgroundShellPid: vi.fn(),
|
||||
setIsBackgroundShellListOpen: vi.fn(),
|
||||
setAuthContext: vi.fn(),
|
||||
onHintInput: vi.fn(),
|
||||
onHintBackspace: vi.fn(),
|
||||
onHintClear: vi.fn(),
|
||||
onHintSubmit: vi.fn(),
|
||||
handleRestart: vi.fn(),
|
||||
handleNewAgentsSelect: vi.fn(),
|
||||
};
|
||||
|
||||
@@ -94,7 +94,11 @@ import { basename } from 'node:path';
|
||||
import { computeTerminalTitle } from '../utils/windowTitle.js';
|
||||
import { useTextBuffer } from './components/shared/text-buffer.js';
|
||||
import { useLogger } from './hooks/useLogger.js';
|
||||
import { useGeminiStream } from './hooks/useGeminiStream.js';
|
||||
import {
|
||||
buildUserSteeringHintPrompt,
|
||||
generateSteeringAckMessage,
|
||||
useGeminiStream,
|
||||
} from './hooks/useGeminiStream.js';
|
||||
import { type BackgroundShell } from './hooks/shellCommandProcessor.js';
|
||||
import { useVim } from './hooks/vim.js';
|
||||
import { type LoadableSettingScope, SettingScope } from '../config/settings.js';
|
||||
@@ -963,6 +967,18 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
}
|
||||
}, [pendingRestorePrompt, inputHistory, historyManager.history]);
|
||||
|
||||
const consumePendingHints = useCallback(() => {
|
||||
const userHints = config.consumeUserHints();
|
||||
if (userHints.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return userHints.join('\n');
|
||||
}, [config]);
|
||||
const getUserHint = useCallback(
|
||||
() => consumePendingHints(),
|
||||
[consumePendingHints],
|
||||
);
|
||||
|
||||
const {
|
||||
streamingState,
|
||||
submitQuery,
|
||||
@@ -1001,6 +1017,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
terminalWidth,
|
||||
terminalHeight,
|
||||
embeddedShellFocused,
|
||||
getUserHint,
|
||||
);
|
||||
|
||||
toggleBackgroundShellRef.current = toggleBackgroundShell;
|
||||
@@ -1103,10 +1120,38 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
],
|
||||
);
|
||||
|
||||
const handleHintSubmit = useCallback(
|
||||
(hint: string) => {
|
||||
const trimmed = hint.trim();
|
||||
if (!trimmed) {
|
||||
return;
|
||||
}
|
||||
config.addUserHint(trimmed);
|
||||
// Render hints as regular user input so they look like normal commands.
|
||||
historyManager.addItem({
|
||||
type: MessageType.USER,
|
||||
text: trimmed,
|
||||
});
|
||||
},
|
||||
[config, historyManager],
|
||||
);
|
||||
|
||||
const handleFinalSubmit = useCallback(
|
||||
async (submittedValue: string) => {
|
||||
const isSlash = isSlashCommand(submittedValue.trim());
|
||||
const isIdle = streamingState === StreamingState.Idle;
|
||||
const isAgentRunning =
|
||||
streamingState === StreamingState.Responding ||
|
||||
isToolExecuting([
|
||||
...pendingSlashCommandHistoryItems,
|
||||
...pendingGeminiHistoryItems,
|
||||
]);
|
||||
|
||||
if (isAgentRunning && !isSlash) {
|
||||
handleHintSubmit(submittedValue);
|
||||
addInput(submittedValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSlash || (isIdle && isMcpReady)) {
|
||||
if (!isSlash) {
|
||||
@@ -1148,7 +1193,10 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
isMcpReady,
|
||||
streamingState,
|
||||
messageQueue.length,
|
||||
pendingSlashCommandHistoryItems,
|
||||
pendingGeminiHistoryItems,
|
||||
config,
|
||||
handleHintSubmit,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1814,6 +1862,43 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
[pendingSlashCommandHistoryItems, pendingGeminiHistoryItems],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!isConfigInitialized ||
|
||||
streamingState !== StreamingState.Idle ||
|
||||
!isMcpReady
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pendingHint = consumePendingHints();
|
||||
if (!pendingHint) {
|
||||
return;
|
||||
}
|
||||
|
||||
const geminiClient = config.getGeminiClient();
|
||||
void generateSteeringAckMessage(geminiClient, pendingHint).then(
|
||||
(ackText) => {
|
||||
historyManager.addItem({
|
||||
type: 'info',
|
||||
icon: '· ',
|
||||
color: 'gray',
|
||||
marginBottom: 1,
|
||||
text: ackText,
|
||||
} as Omit<HistoryItem, 'id'>);
|
||||
},
|
||||
);
|
||||
void submitQuery([{ text: buildUserSteeringHintPrompt(pendingHint) }]);
|
||||
}, [
|
||||
config,
|
||||
consumePendingHints,
|
||||
historyManager,
|
||||
isConfigInitialized,
|
||||
isMcpReady,
|
||||
streamingState,
|
||||
submitQuery,
|
||||
]);
|
||||
|
||||
const allToolCalls = useMemo(
|
||||
() =>
|
||||
pendingHistoryItems
|
||||
@@ -1975,6 +2060,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
isBackgroundShellListOpen,
|
||||
adminSettingsChanged,
|
||||
newAgents,
|
||||
hintMode: false,
|
||||
hintBuffer: '',
|
||||
}),
|
||||
[
|
||||
isThemeDialogOpen,
|
||||
@@ -2137,6 +2224,10 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
setActiveBackgroundShellPid,
|
||||
setIsBackgroundShellListOpen,
|
||||
setAuthContext,
|
||||
onHintInput: () => {},
|
||||
onHintBackspace: () => {},
|
||||
onHintClear: () => {},
|
||||
onHintSubmit: () => {},
|
||||
handleRestart: async () => {
|
||||
if (process.send) {
|
||||
const remoteSettings = config.getRemoteAdminSettings();
|
||||
|
||||
@@ -71,7 +71,8 @@ export const Footer: React.FC = () => {
|
||||
const justifyContent = hideCWD && hideModelInfo ? 'center' : 'space-between';
|
||||
const displayVimMode = vimEnabled ? vimMode : undefined;
|
||||
|
||||
const showDebugProfiler = debugMode || isDevelopment;
|
||||
const showDebugProfiler =
|
||||
debugMode || (isDevelopment && settings.merged.general.devtools);
|
||||
|
||||
return (
|
||||
<Box
|
||||
|
||||
@@ -96,6 +96,7 @@ describe('<Header />', () => {
|
||||
},
|
||||
background: {
|
||||
primary: '',
|
||||
hintMode: '',
|
||||
diff: { added: '', removed: '' },
|
||||
},
|
||||
border: {
|
||||
|
||||
@@ -44,6 +44,18 @@ describe('<HistoryItemDisplay />', () => {
|
||||
expect(lastFrame()).toContain('Hello');
|
||||
});
|
||||
|
||||
it('renders HintMessage for "hint" type', () => {
|
||||
const item: HistoryItem = {
|
||||
...baseItem,
|
||||
type: 'hint',
|
||||
text: 'Try using ripgrep first',
|
||||
};
|
||||
const { lastFrame } = renderWithProviders(
|
||||
<HistoryItemDisplay {...baseItem} item={item} />,
|
||||
);
|
||||
expect(lastFrame()).toContain('Try using ripgrep first');
|
||||
});
|
||||
|
||||
it('renders UserMessage for "user" type with slash command', () => {
|
||||
const item: HistoryItem = {
|
||||
...baseItem,
|
||||
|
||||
@@ -35,6 +35,7 @@ import { ChatList } from './views/ChatList.js';
|
||||
import { HooksList } from './views/HooksList.js';
|
||||
import { ModelMessage } from './messages/ModelMessage.js';
|
||||
import { ThinkingMessage } from './messages/ThinkingMessage.js';
|
||||
import { HintMessage } from './messages/HintMessage.js';
|
||||
import { getInlineThinkingMode } from '../utils/inlineThinkingMode.js';
|
||||
import { useSettings } from '../contexts/SettingsContext.js';
|
||||
|
||||
@@ -71,6 +72,9 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
||||
{itemForDisplay.type === 'thinking' && inlineThinkingMode !== 'off' && (
|
||||
<ThinkingMessage thought={itemForDisplay.thought} />
|
||||
)}
|
||||
{itemForDisplay.type === 'hint' && (
|
||||
<HintMessage text={itemForDisplay.text} />
|
||||
)}
|
||||
{itemForDisplay.type === 'user' && (
|
||||
<UserMessage text={itemForDisplay.text} width={terminalWidth} />
|
||||
)}
|
||||
@@ -102,6 +106,7 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
||||
text={itemForDisplay.text}
|
||||
icon={itemForDisplay.icon}
|
||||
color={itemForDisplay.color}
|
||||
marginBottom={itemForDisplay.marginBottom}
|
||||
/>
|
||||
)}
|
||||
{itemForDisplay.type === 'warning' && (
|
||||
|
||||
@@ -13,19 +13,21 @@ interface InfoMessageProps {
|
||||
text: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
marginBottom?: number;
|
||||
}
|
||||
|
||||
export const InfoMessage: React.FC<InfoMessageProps> = ({
|
||||
text,
|
||||
icon,
|
||||
color,
|
||||
marginBottom,
|
||||
}) => {
|
||||
color ??= theme.status.warning;
|
||||
const prefix = icon ?? 'ℹ ';
|
||||
const prefixWidth = prefix.length;
|
||||
|
||||
return (
|
||||
<Box flexDirection="row" marginTop={1}>
|
||||
<Box flexDirection="row" marginTop={1} marginBottom={marginBottom ?? 0}>
|
||||
<Box width={prefixWidth}>
|
||||
<Text color={color}>{prefix}</Text>
|
||||
</Box>
|
||||
|
||||
@@ -73,6 +73,10 @@ export interface UIActions {
|
||||
setActiveBackgroundShellPid: (pid: number) => void;
|
||||
setIsBackgroundShellListOpen: (isOpen: boolean) => void;
|
||||
setAuthContext: (context: { requiresRestart?: boolean }) => void;
|
||||
onHintInput: (char: string) => void;
|
||||
onHintBackspace: () => void;
|
||||
onHintClear: () => void;
|
||||
onHintSubmit: (hint: string) => void;
|
||||
handleRestart: () => void;
|
||||
handleNewAgentsSelect: (choice: NewAgentsChoice) => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -173,6 +173,8 @@ export interface UIState {
|
||||
isBackgroundShellListOpen: boolean;
|
||||
adminSettingsChanged: boolean;
|
||||
newAgents: AgentDefinition[] | null;
|
||||
hintMode: boolean;
|
||||
hintBuffer: string;
|
||||
transientMessage: {
|
||||
text: string;
|
||||
type: TransientMessageType;
|
||||
|
||||
@@ -56,6 +56,11 @@ const MockedGeminiClientClass = vi.hoisted(() =>
|
||||
this.startChat = mockStartChat;
|
||||
this.sendMessageStream = mockSendMessageStream;
|
||||
this.addHistory = vi.fn();
|
||||
this.generateContent = vi.fn().mockResolvedValue({
|
||||
candidates: [
|
||||
{ content: { parts: [{ text: 'Got it. Focusing on tests only.' }] } },
|
||||
],
|
||||
});
|
||||
this.getCurrentSequenceModel = vi.fn().mockReturnValue('test-model');
|
||||
this.getChat = vi.fn().mockReturnValue({
|
||||
recordCompletedToolCalls: vi.fn(),
|
||||
@@ -661,6 +666,113 @@ describe('useGeminiStream', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should inject steering hint prompt for continuation', async () => {
|
||||
const toolCallResponseParts: Part[] = [{ text: 'tool final response' }];
|
||||
const completedToolCalls: TrackedToolCall[] = [
|
||||
{
|
||||
request: {
|
||||
callId: 'call1',
|
||||
name: 'tool1',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-ack',
|
||||
},
|
||||
status: 'success',
|
||||
responseSubmittedToGemini: false,
|
||||
response: {
|
||||
callId: 'call1',
|
||||
responseParts: toolCallResponseParts,
|
||||
errorType: undefined,
|
||||
},
|
||||
tool: {
|
||||
displayName: 'MockTool',
|
||||
},
|
||||
invocation: {
|
||||
getDescription: () => `Mock description`,
|
||||
} as unknown as AnyToolInvocation,
|
||||
} as TrackedCompletedToolCall,
|
||||
];
|
||||
|
||||
mockSendMessageStream.mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
type: ServerGeminiEventType.Content,
|
||||
value: 'Applied the requested adjustment.',
|
||||
};
|
||||
})(),
|
||||
);
|
||||
|
||||
let capturedOnComplete:
|
||||
| ((completedTools: TrackedToolCall[]) => Promise<void>)
|
||||
| null = null;
|
||||
mockUseToolScheduler.mockImplementation((onComplete) => {
|
||||
capturedOnComplete = onComplete;
|
||||
return [
|
||||
[],
|
||||
mockScheduleToolCalls,
|
||||
mockMarkToolsAsSubmitted,
|
||||
vi.fn(),
|
||||
mockCancelAllToolCalls,
|
||||
0,
|
||||
];
|
||||
});
|
||||
|
||||
renderHookWithProviders(() =>
|
||||
useGeminiStream(
|
||||
new MockedGeminiClientClass(mockConfig),
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockLoadedSettings,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false,
|
||||
() => 'vscode' as EditorType,
|
||||
() => {},
|
||||
() => Promise.resolve(),
|
||||
false,
|
||||
() => {},
|
||||
() => {},
|
||||
() => {},
|
||||
80,
|
||||
24,
|
||||
undefined,
|
||||
() => 'focus on tests only',
|
||||
),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
if (capturedOnComplete) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
await capturedOnComplete(completedToolCalls);
|
||||
}
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSendMessageStream).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
const sentParts = mockSendMessageStream.mock.calls[0][0] as Part[];
|
||||
const injectedHintPart = sentParts[0] as { text?: string };
|
||||
expect(injectedHintPart.text).toContain(
|
||||
'User steering update: "focus on tests only"',
|
||||
);
|
||||
expect(injectedHintPart.text).toContain(
|
||||
'Classify it as ADD_TASK, MODIFY_TASK, CANCEL_TASK, or EXTRA_CONTEXT.',
|
||||
);
|
||||
expect(injectedHintPart.text).toContain(
|
||||
'Do not cancel/skip tasks unless the user explicitly cancels them.',
|
||||
);
|
||||
expect(
|
||||
mockAddItem.mock.calls.some(
|
||||
([item]) =>
|
||||
item?.type === 'info' &&
|
||||
typeof item.text === 'string' &&
|
||||
item.text.includes('Got it. Focusing on tests only.'),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle all tool calls being cancelled', async () => {
|
||||
const cancelledToolCalls: TrackedToolCall[] = [
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
GeminiEventType as ServerGeminiEventType,
|
||||
getErrorMessage,
|
||||
getResponseText,
|
||||
isNodeError,
|
||||
MessageSenderType,
|
||||
logUserPrompt,
|
||||
@@ -47,7 +48,12 @@ import type {
|
||||
GeminiErrorEventValue,
|
||||
RetryAttemptPayload,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { type Part, type PartListUnion, FinishReason } from '@google/genai';
|
||||
import {
|
||||
type Content,
|
||||
type Part,
|
||||
type PartListUnion,
|
||||
FinishReason,
|
||||
} from '@google/genai';
|
||||
import type {
|
||||
HistoryItem,
|
||||
HistoryItemThinking,
|
||||
@@ -81,6 +87,7 @@ import path from 'node:path';
|
||||
import { useSessionStats } from '../contexts/SessionContext.js';
|
||||
import { useKeypress } from './useKeypress.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
type ToolResponseWithParts = ToolCallResponseInfo & {
|
||||
llmContent?: PartListUnion;
|
||||
@@ -98,6 +105,102 @@ enum StreamProcessingStatus {
|
||||
Error,
|
||||
}
|
||||
|
||||
const USER_STEERING_INSTRUCTION =
|
||||
'Internal instruction: Re-evaluate the active plan using this user steering update. ' +
|
||||
'Classify it as ADD_TASK, MODIFY_TASK, CANCEL_TASK, or EXTRA_CONTEXT. ' +
|
||||
'Apply minimal-diff changes only to affected tasks and keep unaffected tasks active. ' +
|
||||
'Do not cancel/skip tasks unless the user explicitly cancels them. ' +
|
||||
'Acknowledge the steering briefly and state the course correction.';
|
||||
|
||||
export function buildUserSteeringHintPrompt(hintText: string): string {
|
||||
const trimmedText = hintText.trim();
|
||||
return `User steering update: "${trimmedText}"\n${USER_STEERING_INSTRUCTION}`;
|
||||
}
|
||||
|
||||
const STEERING_ACK_INSTRUCTION =
|
||||
'Write one short, friendly sentence acknowledging a user steering update for an in-progress task. ' +
|
||||
'Be concrete when possible (e.g., mention skipped/cancelled item numbers). ' +
|
||||
'Do not apologize, do not mention internal policy, and do not add extra steps.';
|
||||
const STEERING_ACK_TIMEOUT_MS = 1200;
|
||||
const STEERING_ACK_MAX_INPUT_CHARS = 320;
|
||||
const STEERING_ACK_MAX_OUTPUT_CHARS = 90;
|
||||
const STEERING_ACK_INPUT_TRUNCATION_SUFFIX = '\n...[truncated]';
|
||||
|
||||
function buildSteeringFallbackMessage(hintText: string): string {
|
||||
const normalized = hintText.replace(/\s+/g, ' ').trim();
|
||||
if (!normalized) {
|
||||
return 'Understood. Adjusting the plan.';
|
||||
}
|
||||
if (normalized.length <= 64) {
|
||||
return `Understood. ${normalized}`;
|
||||
}
|
||||
return `Understood. ${normalized.slice(0, 61)}...`;
|
||||
}
|
||||
|
||||
export async function generateSteeringAckMessage(
|
||||
geminiClient: GeminiClient,
|
||||
hintText: string,
|
||||
): Promise<string> {
|
||||
const truncateSteeringAckInput = (input: string): string => {
|
||||
if (input.length <= STEERING_ACK_MAX_INPUT_CHARS) {
|
||||
return input;
|
||||
}
|
||||
if (
|
||||
STEERING_ACK_MAX_INPUT_CHARS <=
|
||||
STEERING_ACK_INPUT_TRUNCATION_SUFFIX.length
|
||||
) {
|
||||
return input.slice(0, STEERING_ACK_MAX_INPUT_CHARS);
|
||||
}
|
||||
return (
|
||||
input.slice(
|
||||
0,
|
||||
STEERING_ACK_MAX_INPUT_CHARS -
|
||||
STEERING_ACK_INPUT_TRUNCATION_SUFFIX.length,
|
||||
) + STEERING_ACK_INPUT_TRUNCATION_SUFFIX
|
||||
);
|
||||
};
|
||||
|
||||
const fallbackText = buildSteeringFallbackMessage(hintText);
|
||||
const safeHint = truncateSteeringAckInput(
|
||||
hintText.replace(/\s+/g, ' ').trim(),
|
||||
);
|
||||
const contents: Content[] = [
|
||||
{
|
||||
role: 'user',
|
||||
parts: [
|
||||
{
|
||||
text: `${STEERING_ACK_INSTRUCTION}\n\nUser input:\n"""${safeHint}"""`,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const abortController = new AbortController();
|
||||
const timeout = setTimeout(
|
||||
() => abortController.abort(),
|
||||
STEERING_ACK_TIMEOUT_MS,
|
||||
);
|
||||
try {
|
||||
const response = await geminiClient.generateContent(
|
||||
{ model: 'flash-lite-helper' },
|
||||
contents,
|
||||
abortController.signal,
|
||||
);
|
||||
const responseText = getResponseText(response)?.replace(/\s+/g, ' ').trim();
|
||||
if (!responseText) {
|
||||
return fallbackText;
|
||||
}
|
||||
if (responseText.length > STEERING_ACK_MAX_OUTPUT_CHARS) {
|
||||
return responseText.slice(0, STEERING_ACK_MAX_OUTPUT_CHARS).trimEnd();
|
||||
}
|
||||
return responseText;
|
||||
} catch {
|
||||
return fallbackText;
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
function isShellToolData(data: unknown): data is ShellToolData {
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
return false;
|
||||
@@ -185,6 +288,7 @@ export const useGeminiStream = (
|
||||
terminalWidth: number,
|
||||
terminalHeight: number,
|
||||
isShellFocused?: boolean,
|
||||
getUserHint?: () => string | null,
|
||||
) => {
|
||||
const [initError, setInitError] = useState<string | null>(null);
|
||||
const [retryStatus, setRetryStatus] = useState<RetryAttemptPayload | null>(
|
||||
@@ -1561,6 +1665,28 @@ export const useGeminiStream = (
|
||||
const responsesToSend: Part[] = geminiTools.flatMap(
|
||||
(toolCall) => toolCall.response.responseParts,
|
||||
);
|
||||
|
||||
if (getUserHint) {
|
||||
const userHint = getUserHint();
|
||||
if (userHint && userHint.trim().length > 0) {
|
||||
const hintText = userHint.trim();
|
||||
responsesToSend.unshift({
|
||||
text: buildUserSteeringHintPrompt(hintText),
|
||||
});
|
||||
void generateSteeringAckMessage(geminiClient, hintText).then(
|
||||
(ackText) => {
|
||||
addItem({
|
||||
type: 'info',
|
||||
icon: '· ',
|
||||
color: theme.text.secondary,
|
||||
marginBottom: 1,
|
||||
text: ackText,
|
||||
} as Omit<HistoryItem, 'id'>);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const callIdsToMarkAsSubmitted = geminiTools.map(
|
||||
(toolCall) => toolCall.request.callId,
|
||||
);
|
||||
@@ -1593,6 +1719,7 @@ export const useGeminiStream = (
|
||||
modelSwitchedFromQuotaError,
|
||||
addItem,
|
||||
registerBackgroundShell,
|
||||
getUserHint,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ const noColorSemanticColors: SemanticColors = {
|
||||
},
|
||||
background: {
|
||||
primary: '',
|
||||
hintMode: '',
|
||||
diff: {
|
||||
added: '',
|
||||
removed: '',
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface SemanticColors {
|
||||
};
|
||||
background: {
|
||||
primary: string;
|
||||
hintMode: string;
|
||||
diff: {
|
||||
added: string;
|
||||
removed: string;
|
||||
@@ -48,6 +49,7 @@ export const lightSemanticColors: SemanticColors = {
|
||||
},
|
||||
background: {
|
||||
primary: lightTheme.Background,
|
||||
hintMode: '#E8E0F0',
|
||||
diff: {
|
||||
added: lightTheme.DiffAdded,
|
||||
removed: lightTheme.DiffRemoved,
|
||||
@@ -80,6 +82,7 @@ export const darkSemanticColors: SemanticColors = {
|
||||
},
|
||||
background: {
|
||||
primary: darkTheme.Background,
|
||||
hintMode: '#352A45',
|
||||
diff: {
|
||||
added: darkTheme.DiffAdded,
|
||||
removed: darkTheme.DiffRemoved,
|
||||
|
||||
@@ -131,6 +131,7 @@ export class Theme {
|
||||
},
|
||||
background: {
|
||||
primary: this.colors.Background,
|
||||
hintMode: this.type === 'light' ? '#E8E0F0' : '#352A45',
|
||||
diff: {
|
||||
added: this.colors.DiffAdded,
|
||||
removed: this.colors.DiffRemoved,
|
||||
@@ -400,6 +401,7 @@ export function createCustomTheme(customTheme: CustomTheme): Theme {
|
||||
},
|
||||
background: {
|
||||
primary: customTheme.background?.primary ?? colors.Background,
|
||||
hintMode: 'magenta',
|
||||
diff: {
|
||||
added: customTheme.background?.diff?.added ?? colors.DiffAdded,
|
||||
removed: customTheme.background?.diff?.removed ?? colors.DiffRemoved,
|
||||
|
||||
@@ -123,6 +123,7 @@ export type HistoryItemInfo = HistoryItemBase & {
|
||||
text: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
marginBottom?: number;
|
||||
};
|
||||
|
||||
export type HistoryItemError = HistoryItemBase & {
|
||||
@@ -225,6 +226,11 @@ export type HistoryItemThinking = HistoryItemBase & {
|
||||
thought: ThoughtSummary;
|
||||
};
|
||||
|
||||
export type HistoryItemHint = HistoryItemBase & {
|
||||
type: 'hint';
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type HistoryItemChatList = HistoryItemBase & {
|
||||
type: 'chat_list';
|
||||
chats: ChatDetail[];
|
||||
@@ -349,6 +355,7 @@ export type HistoryItemWithoutId =
|
||||
| HistoryItemMcpStatus
|
||||
| HistoryItemChatList
|
||||
| HistoryItemThinking
|
||||
| HistoryItemHint
|
||||
| HistoryItemHooksList;
|
||||
|
||||
export type HistoryItem = HistoryItemWithoutId & { id: number };
|
||||
|
||||
Reference in New Issue
Block a user