mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-13 15:40:57 -07:00
feat(cli): implement width-aware phrase selection for footer tips
- Update usePhraseCycler to filter phrase list based on available width - Move status length estimation logic to AppContainer - Ensure tips are only selected if they fit the remaining terminal width - Update snapshots for usePhraseCycler
This commit is contained in:
@@ -1684,15 +1684,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
[handleSlashCommand, settings],
|
||||
);
|
||||
|
||||
const { elapsedTime, currentLoadingPhrase } = useLoadingIndicator({
|
||||
streamingState,
|
||||
shouldShowFocusHint,
|
||||
retryStatus,
|
||||
loadingPhrasesMode: settings.merged.ui.loadingPhrases,
|
||||
customWittyPhrases: settings.merged.ui.customWittyPhrases,
|
||||
errorVerbosity: settings.merged.ui.errorVerbosity,
|
||||
});
|
||||
|
||||
const handleGlobalKeypress = useCallback(
|
||||
(key: Key): boolean => {
|
||||
// Debug log keystrokes if enabled
|
||||
@@ -2072,6 +2063,50 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
!!emptyWalletRequest ||
|
||||
!!customDialog;
|
||||
|
||||
const newLayoutSetting = settings.merged.ui.newFooterLayout;
|
||||
const isExperimentalLayout = newLayoutSetting !== 'legacy';
|
||||
const showLoadingIndicator =
|
||||
(!embeddedShellFocused || isBackgroundShellVisible) &&
|
||||
streamingState === StreamingState.Responding &&
|
||||
!hasPendingActionRequired;
|
||||
|
||||
let estimatedStatusLength = 0;
|
||||
if (
|
||||
isExperimentalLayout &&
|
||||
activeHooks.length > 0 &&
|
||||
settings.merged.hooksConfig.notifications
|
||||
) {
|
||||
const hookLabel =
|
||||
activeHooks.length > 1 ? 'Executing Hooks' : 'Executing Hook';
|
||||
const hookNames = activeHooks
|
||||
.map(
|
||||
(h) =>
|
||||
h.name +
|
||||
(h.index && h.total && h.total > 1 ? ` (${h.index}/${h.total})` : ''),
|
||||
)
|
||||
.join(', ');
|
||||
estimatedStatusLength = hookLabel.length + hookNames.length + 10;
|
||||
} else if (showLoadingIndicator) {
|
||||
const thoughtText = thought?.subject || 'Waiting for model...';
|
||||
estimatedStatusLength = thoughtText.length + 25;
|
||||
} else if (hasPendingActionRequired) {
|
||||
estimatedStatusLength = 35;
|
||||
}
|
||||
|
||||
const maxLength = isExperimentalLayout
|
||||
? terminalWidth - estimatedStatusLength - 5
|
||||
: undefined;
|
||||
|
||||
const { elapsedTime, currentLoadingPhrase } = useLoadingIndicator({
|
||||
streamingState,
|
||||
shouldShowFocusHint,
|
||||
retryStatus,
|
||||
loadingPhrasesMode: settings.merged.ui.loadingPhrases,
|
||||
customWittyPhrases: settings.merged.ui.customWittyPhrases,
|
||||
errorVerbosity: settings.merged.ui.errorVerbosity,
|
||||
maxLength,
|
||||
});
|
||||
|
||||
const allowPlanMode =
|
||||
config.isPlanEnabled() &&
|
||||
streamingState === StreamingState.Idle &&
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
exports[`usePhraseCycler > should prioritize interactive shell waiting over normal waiting immediately 1`] = `"Waiting for user confirmation..."`;
|
||||
|
||||
exports[`usePhraseCycler > should prioritize interactive shell waiting over normal waiting immediately 2`] = `"Interactive shell awaiting input... press tab to focus shell"`;
|
||||
exports[`usePhraseCycler > should prioritize interactive shell waiting over normal waiting immediately 2`] = `"! Shell awaiting input (Tab to focus)"`;
|
||||
|
||||
exports[`usePhraseCycler > should reset phrase when transitioning from waiting to active 1`] = `"Waiting for user confirmation..."`;
|
||||
|
||||
exports[`usePhraseCycler > should show "Waiting for user confirmation..." when isWaiting is true 1`] = `"Waiting for user confirmation..."`;
|
||||
|
||||
exports[`usePhraseCycler > should show interactive shell waiting message immediately when isInteractiveShellWaiting is true 1`] = `"Interactive shell awaiting input... press tab to focus shell"`;
|
||||
exports[`usePhraseCycler > should show interactive shell waiting message immediately when isInteractiveShellWaiting is true 1`] = `"! Shell awaiting input (Tab to focus)"`;
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface UseLoadingIndicatorProps {
|
||||
loadingPhrasesMode?: LoadingPhrasesMode;
|
||||
customWittyPhrases?: string[];
|
||||
errorVerbosity?: 'low' | 'full';
|
||||
maxLength?: number;
|
||||
}
|
||||
|
||||
export const useLoadingIndicator = ({
|
||||
@@ -32,6 +33,7 @@ export const useLoadingIndicator = ({
|
||||
loadingPhrasesMode,
|
||||
customWittyPhrases,
|
||||
errorVerbosity = 'full',
|
||||
maxLength,
|
||||
}: UseLoadingIndicatorProps) => {
|
||||
const [timerResetKey, setTimerResetKey] = useState(0);
|
||||
const isTimerActive = streamingState === StreamingState.Responding;
|
||||
@@ -46,6 +48,7 @@ export const useLoadingIndicator = ({
|
||||
shouldShowFocusHint,
|
||||
loadingPhrasesMode,
|
||||
customWittyPhrases,
|
||||
maxLength,
|
||||
);
|
||||
|
||||
const [retainedElapsedTime, setRetainedElapsedTime] = useState(0);
|
||||
|
||||
@@ -20,6 +20,7 @@ export const INTERACTIVE_SHELL_WAITING_PHRASE =
|
||||
* @param shouldShowFocusHint Whether to show the shell focus hint.
|
||||
* @param loadingPhrasesMode Which phrases to show: tips, witty, all, or off.
|
||||
* @param customPhrases Optional list of custom phrases to use instead of built-in witty phrases.
|
||||
* @param maxLength Optional maximum length for the selected phrase.
|
||||
* @returns The current loading phrase.
|
||||
*/
|
||||
export const usePhraseCycler = (
|
||||
@@ -28,6 +29,7 @@ export const usePhraseCycler = (
|
||||
shouldShowFocusHint: boolean,
|
||||
loadingPhrasesMode: LoadingPhrasesMode = 'tips',
|
||||
customPhrases?: string[],
|
||||
maxLength?: number,
|
||||
) => {
|
||||
const [currentLoadingPhrase, setCurrentLoadingPhrase] = useState<
|
||||
string | undefined
|
||||
@@ -65,31 +67,48 @@ export const usePhraseCycler = (
|
||||
|
||||
const setRandomPhrase = () => {
|
||||
let phraseList: readonly string[];
|
||||
let currentMode = loadingPhrasesMode;
|
||||
|
||||
switch (loadingPhrasesMode) {
|
||||
// In 'all' mode, we decide once per phrase cycle what to show
|
||||
if (loadingPhrasesMode === 'all') {
|
||||
if (!hasShownFirstRequestTipRef.current) {
|
||||
currentMode = 'tips';
|
||||
hasShownFirstRequestTipRef.current = true;
|
||||
} else {
|
||||
currentMode = Math.random() < 1 / 2 ? 'tips' : 'witty';
|
||||
}
|
||||
}
|
||||
|
||||
switch (currentMode) {
|
||||
case 'tips':
|
||||
phraseList = INFORMATIVE_TIPS;
|
||||
break;
|
||||
case 'witty':
|
||||
phraseList = wittyPhrases;
|
||||
break;
|
||||
case 'all':
|
||||
// Show a tip on the first request after startup, then continue with 1/2 chance
|
||||
if (!hasShownFirstRequestTipRef.current) {
|
||||
phraseList = INFORMATIVE_TIPS;
|
||||
hasShownFirstRequestTipRef.current = true;
|
||||
} else {
|
||||
const showTip = Math.random() < 1 / 2;
|
||||
phraseList = showTip ? INFORMATIVE_TIPS : wittyPhrases;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
phraseList = INFORMATIVE_TIPS;
|
||||
break;
|
||||
}
|
||||
|
||||
const randomIndex = Math.floor(Math.random() * phraseList.length);
|
||||
setCurrentLoadingPhrase(phraseList[randomIndex]);
|
||||
// If we have a maxLength, we need to account for potential prefixes.
|
||||
// Tips are prefixed with "Tip: " in the Composer UI.
|
||||
const prefixLength = currentMode === 'tips' ? 5 : 0;
|
||||
const adjustedMaxLength =
|
||||
maxLength !== undefined ? maxLength - prefixLength : undefined;
|
||||
|
||||
const filteredList =
|
||||
adjustedMaxLength !== undefined
|
||||
? phraseList.filter((p) => p.length <= adjustedMaxLength)
|
||||
: phraseList;
|
||||
|
||||
if (filteredList.length > 0) {
|
||||
const randomIndex = Math.floor(Math.random() * filteredList.length);
|
||||
setCurrentLoadingPhrase(filteredList[randomIndex]);
|
||||
} else {
|
||||
// If no phrases fit, try to fallback to a very short list or nothing
|
||||
setCurrentLoadingPhrase(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
// Select an initial random phrase
|
||||
@@ -112,6 +131,7 @@ export const usePhraseCycler = (
|
||||
shouldShowFocusHint,
|
||||
loadingPhrasesMode,
|
||||
customPhrases,
|
||||
maxLength,
|
||||
]);
|
||||
|
||||
return currentLoadingPhrase;
|
||||
|
||||
Reference in New Issue
Block a user