mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-20 10:10:56 -07:00
fix(ui): cleanup estimated string length hacks in composer (#23694)
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import { useSettings } from '../contexts/SettingsContext.js';
|
||||
import { CoreToolCallStatus, ApprovalMode } from '@google/gemini-cli-core';
|
||||
import { type HistoryItemToolGroup, StreamingState } from '../types.js';
|
||||
import { INTERACTIVE_SHELL_WAITING_PHRASE } from './usePhraseCycler.js';
|
||||
import { isContextUsageHigh } from '../utils/contextUsage.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
/**
|
||||
* A hook that encapsulates complex status and action-required logic for the Composer.
|
||||
*/
|
||||
export const useComposerStatus = () => {
|
||||
const uiState = useUIState();
|
||||
const settings = useSettings();
|
||||
|
||||
const hasPendingToolConfirmation = useMemo(
|
||||
() =>
|
||||
(uiState.pendingHistoryItems ?? [])
|
||||
.filter(
|
||||
(item): item is HistoryItemToolGroup => item.type === 'tool_group',
|
||||
)
|
||||
.some((item) =>
|
||||
item.tools.some(
|
||||
(tool) => tool.status === CoreToolCallStatus.AwaitingApproval,
|
||||
),
|
||||
),
|
||||
[uiState.pendingHistoryItems],
|
||||
);
|
||||
|
||||
const hasPendingActionRequired =
|
||||
hasPendingToolConfirmation ||
|
||||
Boolean(uiState.commandConfirmationRequest) ||
|
||||
Boolean(uiState.authConsentRequest) ||
|
||||
(uiState.confirmUpdateExtensionRequests?.length ?? 0) > 0 ||
|
||||
Boolean(uiState.loopDetectionConfirmationRequest) ||
|
||||
Boolean(uiState.quota.proQuotaRequest) ||
|
||||
Boolean(uiState.quota.validationRequest) ||
|
||||
Boolean(uiState.customDialog);
|
||||
|
||||
const isInteractiveShellWaiting = Boolean(
|
||||
uiState.currentLoadingPhrase?.includes(INTERACTIVE_SHELL_WAITING_PHRASE),
|
||||
);
|
||||
|
||||
const showLoadingIndicator =
|
||||
(!uiState.embeddedShellFocused || uiState.isBackgroundShellVisible) &&
|
||||
uiState.streamingState === StreamingState.Responding &&
|
||||
!hasPendingActionRequired;
|
||||
|
||||
const showApprovalModeIndicator = uiState.showApprovalModeIndicator;
|
||||
|
||||
const modeContentObj = useMemo(() => {
|
||||
const hideMinimalModeHintWhileBusy =
|
||||
!uiState.cleanUiDetailsVisible &&
|
||||
(showLoadingIndicator || uiState.activeHooks.length > 0);
|
||||
|
||||
if (hideMinimalModeHintWhileBusy) return null;
|
||||
|
||||
switch (showApprovalModeIndicator) {
|
||||
case ApprovalMode.YOLO:
|
||||
return { text: 'YOLO', color: theme.status.error };
|
||||
case ApprovalMode.PLAN:
|
||||
return { text: 'plan', color: theme.status.success };
|
||||
case ApprovalMode.AUTO_EDIT:
|
||||
return { text: 'auto edit', color: theme.status.warning };
|
||||
case ApprovalMode.DEFAULT:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, [
|
||||
uiState.cleanUiDetailsVisible,
|
||||
showLoadingIndicator,
|
||||
uiState.activeHooks.length,
|
||||
showApprovalModeIndicator,
|
||||
]);
|
||||
|
||||
const showMinimalContext = isContextUsageHigh(
|
||||
uiState.sessionStats.lastPromptTokenCount,
|
||||
uiState.currentModel,
|
||||
settings.merged.model?.compressionThreshold,
|
||||
);
|
||||
|
||||
const loadingPhrases = settings.merged.ui.loadingPhrases;
|
||||
const showTips = loadingPhrases === 'tips' || loadingPhrases === 'all';
|
||||
const showWit = loadingPhrases === 'witty' || loadingPhrases === 'all';
|
||||
|
||||
/**
|
||||
* Use the setting if provided, otherwise default to true for the new UX.
|
||||
* This allows tests to override the collapse behavior.
|
||||
*/
|
||||
const shouldCollapseDuringApproval =
|
||||
settings.merged.ui.collapseDrawerDuringApproval !== false;
|
||||
|
||||
return {
|
||||
hasPendingActionRequired,
|
||||
shouldCollapseDuringApproval,
|
||||
isInteractiveShellWaiting,
|
||||
showLoadingIndicator,
|
||||
showTips,
|
||||
showWit,
|
||||
modeContentObj,
|
||||
showMinimalContext,
|
||||
};
|
||||
};
|
||||
@@ -66,11 +66,11 @@ export const usePhraseCycler = (
|
||||
|
||||
if (shouldShowFocusHint || isWaiting) {
|
||||
// These are handled by the return value directly for immediate feedback
|
||||
return;
|
||||
return clearTimers;
|
||||
}
|
||||
|
||||
if (!isActive || (!showTips && !showWit)) {
|
||||
return;
|
||||
return clearTimers;
|
||||
}
|
||||
|
||||
const wittyPhrasesList =
|
||||
@@ -101,6 +101,7 @@ export const usePhraseCycler = (
|
||||
: INFORMATIVE_TIPS;
|
||||
|
||||
if (filteredTips.length > 0) {
|
||||
// codeql[js/insecure-randomness] false positive: used for non-sensitive UI flavor text (tips)
|
||||
const selected =
|
||||
filteredTips[Math.floor(Math.random() * filteredTips.length)];
|
||||
setCurrentTipState(selected);
|
||||
@@ -132,6 +133,7 @@ export const usePhraseCycler = (
|
||||
: wittyPhrasesList;
|
||||
|
||||
if (filteredWitty.length > 0) {
|
||||
// codeql[js/insecure-randomness] false positive: used for non-sensitive UI flavor text (witty phrases)
|
||||
const selected =
|
||||
filteredWitty[Math.floor(Math.random() * filteredWitty.length)];
|
||||
setCurrentWittyPhraseState(selected);
|
||||
|
||||
Reference in New Issue
Block a user