mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-12 23:21:27 -07:00
feat(cli): custom witty message (#7641)
This commit is contained in:
@@ -277,6 +277,15 @@ export const SETTINGS_SCHEMA = {
|
||||
description: 'Show citations for generated text in the chat.',
|
||||
showInDialog: true,
|
||||
},
|
||||
customWittyPhrases: {
|
||||
type: 'array',
|
||||
label: 'Custom Witty Phrases',
|
||||
category: 'UI',
|
||||
requiresRestart: false,
|
||||
default: [] as string[],
|
||||
description: 'Custom witty phrases to display during loading.',
|
||||
showInDialog: false,
|
||||
},
|
||||
accessibility: {
|
||||
type: 'object',
|
||||
label: 'Accessibility',
|
||||
|
||||
@@ -1055,7 +1055,7 @@ describe('App UI', () => {
|
||||
);
|
||||
currentUnmount = unmount;
|
||||
|
||||
expect(lastFrame()).toContain("I'm Feeling Lucky (esc to cancel");
|
||||
expect(lastFrame()).toContain('(esc to cancel');
|
||||
});
|
||||
|
||||
it('should display a message if NO_COLOR is set', async () => {
|
||||
@@ -1070,7 +1070,7 @@ describe('App UI', () => {
|
||||
);
|
||||
currentUnmount = unmount;
|
||||
|
||||
expect(lastFrame()).toContain("I'm Feeling Lucky (esc to cancel");
|
||||
expect(lastFrame()).toContain('(esc to cancel');
|
||||
expect(lastFrame()).not.toContain('Select Theme');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -737,8 +737,10 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
||||
|
||||
const { handleInput: vimHandleInput } = useVim(buffer, handleFinalSubmit);
|
||||
|
||||
const { elapsedTime, currentLoadingPhrase } =
|
||||
useLoadingIndicator(streamingState);
|
||||
const { elapsedTime, currentLoadingPhrase } = useLoadingIndicator(
|
||||
streamingState,
|
||||
settings.merged.ui?.customWittyPhrases,
|
||||
);
|
||||
const showAutoAcceptIndicator = useAutoAcceptIndicator({ config, addItem });
|
||||
|
||||
const handleExit = useCallback(
|
||||
|
||||
@@ -28,7 +28,9 @@ describe('useLoadingIndicator', () => {
|
||||
useLoadingIndicator(StreamingState.Idle),
|
||||
);
|
||||
expect(result.current.elapsedTime).toBe(0);
|
||||
expect(result.current.currentLoadingPhrase).toBe(WITTY_LOADING_PHRASES[0]);
|
||||
expect(WITTY_LOADING_PHRASES).toContain(
|
||||
result.current.currentLoadingPhrase,
|
||||
);
|
||||
});
|
||||
|
||||
it('should reflect values when Responding', async () => {
|
||||
@@ -128,7 +130,9 @@ describe('useLoadingIndicator', () => {
|
||||
});
|
||||
|
||||
expect(result.current.elapsedTime).toBe(0);
|
||||
expect(result.current.currentLoadingPhrase).toBe(WITTY_LOADING_PHRASES[0]);
|
||||
expect(WITTY_LOADING_PHRASES).toContain(
|
||||
result.current.currentLoadingPhrase,
|
||||
);
|
||||
|
||||
// Timer should not advance
|
||||
await act(async () => {
|
||||
|
||||
@@ -9,7 +9,10 @@ import { useTimer } from './useTimer.js';
|
||||
import { usePhraseCycler } from './usePhraseCycler.js';
|
||||
import { useState, useEffect, useRef } from 'react'; // Added useRef
|
||||
|
||||
export const useLoadingIndicator = (streamingState: StreamingState) => {
|
||||
export const useLoadingIndicator = (
|
||||
streamingState: StreamingState,
|
||||
customWittyPhrases?: string[],
|
||||
) => {
|
||||
const [timerResetKey, setTimerResetKey] = useState(0);
|
||||
const isTimerActive = streamingState === StreamingState.Responding;
|
||||
|
||||
@@ -20,6 +23,7 @@ export const useLoadingIndicator = (streamingState: StreamingState) => {
|
||||
const currentLoadingPhrase = usePhraseCycler(
|
||||
isPhraseCyclingActive,
|
||||
isWaiting,
|
||||
customWittyPhrases,
|
||||
);
|
||||
|
||||
const [retainedElapsedTime, setRetainedElapsedTime] = useState(0);
|
||||
|
||||
@@ -21,9 +21,9 @@ describe('usePhraseCycler', () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should initialize with the first witty phrase when not active and not waiting', () => {
|
||||
it('should initialize with a witty phrase when not active and not waiting', () => {
|
||||
const { result } = renderHook(() => usePhraseCycler(false, false));
|
||||
expect(result.current).toBe(WITTY_LOADING_PHRASES[0]);
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
});
|
||||
|
||||
it('should show "Waiting for user confirmation..." when isWaiting is true', () => {
|
||||
@@ -37,10 +37,11 @@ describe('usePhraseCycler', () => {
|
||||
|
||||
it('should not cycle phrases if isActive is false and not waiting', () => {
|
||||
const { result } = renderHook(() => usePhraseCycler(false, false));
|
||||
const initialPhrase = result.current;
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS * 2);
|
||||
});
|
||||
expect(result.current).toBe(WITTY_LOADING_PHRASES[0]);
|
||||
expect(result.current).toBe(initialPhrase);
|
||||
});
|
||||
|
||||
it('should cycle through witty phrases when isActive is true and not waiting', () => {
|
||||
@@ -99,7 +100,7 @@ describe('usePhraseCycler', () => {
|
||||
|
||||
// Set to inactive - should reset to the default initial phrase
|
||||
rerender({ isActive: false, isWaiting: false });
|
||||
expect(result.current).toBe(WITTY_LOADING_PHRASES[0]);
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
|
||||
// Set back to active - should pick a random witty phrase (which our mock controls)
|
||||
act(() => {
|
||||
@@ -116,6 +117,56 @@ describe('usePhraseCycler', () => {
|
||||
expect(clearIntervalSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should use custom phrases when provided', () => {
|
||||
const customPhrases = ['Custom Phrase 1', 'Custom Phrase 2'];
|
||||
let callCount = 0;
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => {
|
||||
const val = callCount % 2;
|
||||
callCount++;
|
||||
return val / customPhrases.length;
|
||||
});
|
||||
|
||||
const { result, rerender } = renderHook(
|
||||
({ isActive, isWaiting, customPhrases: phrases }) =>
|
||||
usePhraseCycler(isActive, isWaiting, phrases),
|
||||
{
|
||||
initialProps: {
|
||||
isActive: true,
|
||||
isWaiting: false,
|
||||
customPhrases,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current).toBe(customPhrases[0]);
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(customPhrases[1]);
|
||||
|
||||
rerender({ isActive: true, isWaiting: false, customPhrases: undefined });
|
||||
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
});
|
||||
|
||||
it('should fall back to witty phrases if custom phrases are an empty array', () => {
|
||||
const { result } = renderHook(
|
||||
({ isActive, isWaiting, customPhrases: phrases }) =>
|
||||
usePhraseCycler(isActive, isWaiting, phrases),
|
||||
{
|
||||
initialProps: {
|
||||
isActive: true,
|
||||
isWaiting: false,
|
||||
customPhrases: [],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
});
|
||||
|
||||
it('should reset to a witty phrase when transitioning from waiting to active', () => {
|
||||
const { result, rerender } = renderHook(
|
||||
({ isActive, isWaiting }) => usePhraseCycler(isActive, isWaiting),
|
||||
|
||||
@@ -146,9 +146,18 @@ export const PHRASE_CHANGE_INTERVAL_MS = 15000;
|
||||
* @param isWaiting Whether to show a specific waiting phrase.
|
||||
* @returns The current loading phrase.
|
||||
*/
|
||||
export const usePhraseCycler = (isActive: boolean, isWaiting: boolean) => {
|
||||
export const usePhraseCycler = (
|
||||
isActive: boolean,
|
||||
isWaiting: boolean,
|
||||
customPhrases?: string[],
|
||||
) => {
|
||||
const loadingPhrases =
|
||||
customPhrases && customPhrases.length > 0
|
||||
? customPhrases
|
||||
: WITTY_LOADING_PHRASES;
|
||||
|
||||
const [currentLoadingPhrase, setCurrentLoadingPhrase] = useState(
|
||||
WITTY_LOADING_PHRASES[0],
|
||||
loadingPhrases[0],
|
||||
);
|
||||
const phraseIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
@@ -165,16 +174,14 @@ export const usePhraseCycler = (isActive: boolean, isWaiting: boolean) => {
|
||||
}
|
||||
// Select an initial random phrase
|
||||
const initialRandomIndex = Math.floor(
|
||||
Math.random() * WITTY_LOADING_PHRASES.length,
|
||||
Math.random() * loadingPhrases.length,
|
||||
);
|
||||
setCurrentLoadingPhrase(WITTY_LOADING_PHRASES[initialRandomIndex]);
|
||||
setCurrentLoadingPhrase(loadingPhrases[initialRandomIndex]);
|
||||
|
||||
phraseIntervalRef.current = setInterval(() => {
|
||||
// Select a new random phrase
|
||||
const randomIndex = Math.floor(
|
||||
Math.random() * WITTY_LOADING_PHRASES.length,
|
||||
);
|
||||
setCurrentLoadingPhrase(WITTY_LOADING_PHRASES[randomIndex]);
|
||||
const randomIndex = Math.floor(Math.random() * loadingPhrases.length);
|
||||
setCurrentLoadingPhrase(loadingPhrases[randomIndex]);
|
||||
}, PHRASE_CHANGE_INTERVAL_MS);
|
||||
} else {
|
||||
// Idle or other states, clear the phrase interval
|
||||
@@ -183,7 +190,7 @@ export const usePhraseCycler = (isActive: boolean, isWaiting: boolean) => {
|
||||
clearInterval(phraseIntervalRef.current);
|
||||
phraseIntervalRef.current = null;
|
||||
}
|
||||
setCurrentLoadingPhrase(WITTY_LOADING_PHRASES[0]);
|
||||
setCurrentLoadingPhrase(loadingPhrases[0]);
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -192,7 +199,7 @@ export const usePhraseCycler = (isActive: boolean, isWaiting: boolean) => {
|
||||
phraseIntervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [isActive, isWaiting]);
|
||||
}, [isActive, isWaiting, loadingPhrases]);
|
||||
|
||||
return currentLoadingPhrase;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user