Prevent UI hang on long tool confirmations.

Problem:
When a tool confirmation dialog appeared for a potentially long-running
operation (e.g., `npm install`), accepting the confirmation would cause
the UI to appear to hang. The confirmation dialog would remain visible,
and no further UI updates would occur until the long-running task
completed. This provided a poor user experience as the application
seemed unresponsive.

Fix:
This change addresses the issue by ensuring the UI is updated to remove
the confirmation dialog *before* the long-running operation begins.
It also marks the tool as executing so a spinner can be shown.

Fixes https://b.corp.google.com/issues/415844994

Signed, sealed, delivered, it's yours!
   - Gemini, your friendly neighborhood code-slinger
This commit is contained in:
Taylor Mullen 2025-05-06 22:26:38 -07:00 committed by N. Taylor Mullen
parent 782686bcf3
commit a588d5cd10
3 changed files with 30 additions and 1 deletions

View File

@ -25,7 +25,8 @@ export const ToolMessage: React.FC<IndividualToolCallDisplay> = ({
<Box minHeight={1}>
{/* Status Indicator */}
<Box minWidth={statusIndicatorWidth}>
{status === ToolCallStatus.Pending && <Spinner type="dots" />}
{(status === ToolCallStatus.Pending ||
status === ToolCallStatus.Executing) && <Spinner type="dots" />}
{status === ToolCallStatus.Success && (
<Text color={Colors.AccentGreen}></Text>
)}

View File

@ -396,6 +396,32 @@ export const useGeminiStream = (
// Call the original server-side handler first
originalConfirmationDetails.onConfirm(outcome);
// Ensure UI updates before potentially long-running operations
if (currentToolGroupMessageId !== null) {
updateItem(
currentToolGroupMessageId,
(currentItem: HistoryItem) => {
if (currentItem?.type !== 'tool_group')
return currentItem as Partial<Omit<HistoryItem, 'id'>>;
return {
...currentItem,
tools: (currentItem.tools || []).map((tool) =>
tool.callId === request.callId
? {
...tool,
confirmationDetails: undefined,
status: ToolCallStatus.Executing,
}
: tool,
),
} as Partial<Omit<HistoryItem, 'id'>>;
},
);
refreshStatic();
}
await new Promise((resolve) => setTimeout(resolve, 0)); // Allow UI to re-render
if (outcome === ToolConfirmationOutcome.Cancel) {
let resultDisplay: ToolResultDisplay | undefined;
if ('fileDiff' in originalConfirmationDetails) {
@ -470,6 +496,7 @@ export const useGeminiStream = (
setShowHelp,
toolRegistry,
setInitError,
refreshStatic,
],
);

View File

@ -27,6 +27,7 @@ export enum ToolCallStatus {
Pending = 'Pending',
Canceled = 'Canceled',
Confirming = 'Confirming',
Executing = 'Executing',
Success = 'Success',
Error = 'Error',
}