mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-16 09:01:17 -07:00
feat: Blend educative tips with witty phrases during loading times (fun, subtle learning...) (#10569)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
@@ -21,9 +21,11 @@ describe('useLoadingIndicator', () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers(); // Restore real timers after each test
|
||||
act(() => vi.runOnlyPendingTimers);
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should initialize with default values when Idle', () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { result } = renderHook(() =>
|
||||
useLoadingIndicator(StreamingState.Idle),
|
||||
);
|
||||
@@ -34,6 +36,7 @@ describe('useLoadingIndicator', () => {
|
||||
});
|
||||
|
||||
it('should reflect values when Responding', async () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { result } = renderHook(() =>
|
||||
useLoadingIndicator(StreamingState.Responding),
|
||||
);
|
||||
@@ -82,6 +85,7 @@ describe('useLoadingIndicator', () => {
|
||||
});
|
||||
|
||||
it('should reset elapsedTime and use a witty phrase when transitioning from WaitingForConfirmation to Responding', async () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { result, rerender } = renderHook(
|
||||
({ streamingState }) => useLoadingIndicator(streamingState),
|
||||
{ initialProps: { streamingState: StreamingState.Responding } },
|
||||
@@ -115,6 +119,7 @@ describe('useLoadingIndicator', () => {
|
||||
});
|
||||
|
||||
it('should reset timer and phrase when streamingState changes from Responding to Idle', async () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { result, rerender } = renderHook(
|
||||
({ streamingState }) => useLoadingIndicator(streamingState),
|
||||
{ initialProps: { streamingState: StreamingState.Responding } },
|
||||
|
||||
@@ -22,6 +22,7 @@ describe('usePhraseCycler', () => {
|
||||
});
|
||||
|
||||
it('should initialize with a witty phrase when not active and not waiting', () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { result } = renderHook(() => usePhraseCycler(false, false));
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
});
|
||||
@@ -45,6 +46,7 @@ describe('usePhraseCycler', () => {
|
||||
});
|
||||
|
||||
it('should cycle through witty phrases when isActive is true and not waiting', () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { result } = renderHook(() => usePhraseCycler(true, false));
|
||||
// Initial phrase should be one of the witty phrases
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
@@ -70,12 +72,19 @@ describe('usePhraseCycler', () => {
|
||||
}
|
||||
|
||||
// Mock Math.random to make the test deterministic.
|
||||
let callCount = 0;
|
||||
const mockRandomValues = [
|
||||
0.5, // -> witty
|
||||
0, // -> index 0
|
||||
0.5, // -> witty
|
||||
1 / WITTY_LOADING_PHRASES.length, // -> index 1
|
||||
0.5, // -> witty
|
||||
0, // -> index 0
|
||||
];
|
||||
let randomCallCount = 0;
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => {
|
||||
// Cycle through 0, 1, 0, 1, ...
|
||||
const val = callCount % 2;
|
||||
callCount++;
|
||||
return val / WITTY_LOADING_PHRASES.length;
|
||||
const val = mockRandomValues[randomCallCount % mockRandomValues.length];
|
||||
randomCallCount++;
|
||||
return val;
|
||||
});
|
||||
|
||||
const { result, rerender } = renderHook(
|
||||
@@ -120,7 +129,7 @@ describe('usePhraseCycler', () => {
|
||||
it('should use custom phrases when provided', () => {
|
||||
const customPhrases = ['Custom Phrase 1', 'Custom Phrase 2'];
|
||||
let callCount = 0;
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => {
|
||||
const randomMock = vi.spyOn(Math, 'random').mockImplementation(() => {
|
||||
const val = callCount % 2;
|
||||
callCount++;
|
||||
return val / customPhrases.length;
|
||||
@@ -146,12 +155,17 @@ describe('usePhraseCycler', () => {
|
||||
|
||||
expect(result.current).toBe(customPhrases[1]);
|
||||
|
||||
// Test fallback to default phrases.
|
||||
randomMock.mockRestore();
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
|
||||
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', () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { result } = renderHook(
|
||||
({ isActive, isWaiting, customPhrases: phrases }) =>
|
||||
usePhraseCycler(isActive, isWaiting, phrases),
|
||||
@@ -168,6 +182,7 @@ describe('usePhraseCycler', () => {
|
||||
});
|
||||
|
||||
it('should reset to a witty phrase when transitioning from waiting to active', () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { result, rerender } = renderHook(
|
||||
({ isActive, isWaiting }) => usePhraseCycler(isActive, isWaiting),
|
||||
{ initialProps: { isActive: true, isWaiting: false } },
|
||||
|
||||
@@ -139,6 +139,148 @@ export const WITTY_LOADING_PHRASES = [
|
||||
'Releasing the HypnoDrones...',
|
||||
];
|
||||
|
||||
export const INFORMATIVE_TIPS = [
|
||||
//Settings tips start here
|
||||
'Set your preferred editor for opening files (/settings)...',
|
||||
'Toggle Vim mode for a modal editing experience (/settings)...',
|
||||
'Disable automatic updates if you prefer manual control (/settings)...',
|
||||
'Turn off nagging update notifications (settings.json)...',
|
||||
'Enable checkpointing to recover your session after a crash (settings.json)...',
|
||||
'Change CLI output format to JSON for scripting (/settings)...',
|
||||
'Personalize your CLI with a new color theme (/settings)...',
|
||||
'Create and use your own custom themes (settings.json)...',
|
||||
'Hide window title for a more minimal UI (/settings)...',
|
||||
"Don't like these tips? You can hide them (/settings)...",
|
||||
'Hide the startup banner for a cleaner launch (/settings)...',
|
||||
'Reclaim vertical space by hiding the footer (/settings)...',
|
||||
'Show memory usage for performance monitoring (/settings)...',
|
||||
'Show citations to see where the model gets information (/settings)...',
|
||||
'Disable loading phrases for a quieter experience (/settings)...',
|
||||
'Add custom witty phrases to the loading screen (settings.json)...',
|
||||
'Choose a specific Gemini model for conversations (/settings)...',
|
||||
'Limit the number of turns in your session history (/settings)...',
|
||||
'Automatically summarize large tool outputs to save tokens (settings.json)...',
|
||||
'Control when chat history gets compressed based on token usage (settings.json)...',
|
||||
'Define custom context file names, like CONTEXT.md (settings.json)...',
|
||||
'Set max directories to scan for context files (/settings)...',
|
||||
'Expand your workspace with additional directories (/directory)...',
|
||||
'Control how /memory refresh loads context files (/settings)...',
|
||||
'Toggle respect for .gitignore files in context (/settings)...',
|
||||
'Toggle respect for .geminiignore files in context (/settings)...',
|
||||
'Enable recursive file search for @-file completions (/settings)...',
|
||||
'Run tools in a secure sandbox environment (settings.json)...',
|
||||
'Use an interactive terminal for shell commands (/settings)...',
|
||||
'Restrict available built-in tools (settings.json)...',
|
||||
'Exclude specific tools from being used (settings.json)...',
|
||||
'Bypass confirmation for trusted tools (settings.json)...',
|
||||
'Use a custom command for tool discovery (settings.json)...',
|
||||
'Define a custom command for calling discovered tools (settings.json)...',
|
||||
'Define and manage connections to MCP servers (settings.json)...',
|
||||
'Enable folder trust to enhance security (/settings)...',
|
||||
'Change your authentication method (/settings)...',
|
||||
'Enforce auth type for enterprise use (settings.json)...',
|
||||
'Let Node.js auto-configure memory (settings.json)...',
|
||||
'Customize the DNS resolution order (settings.json)...',
|
||||
'Exclude env vars from the context (settings.json)...',
|
||||
'Configure a custom command for filing bug reports (settings.json)...',
|
||||
'Enable or disable telemetry collection (/settings)...',
|
||||
'Send telemetry data to a local file or GCP (settings.json)...',
|
||||
'Configure the OTLP endpoint for telemetry (settings.json)...',
|
||||
'Choose whether to log prompt content (settings.json)...',
|
||||
'Enable AI-powered prompt completion while typing (/settings)...',
|
||||
'Enable debug logging of keystrokes to the console (/settings)...',
|
||||
'Enable automatic session cleanup of old conversations (/settings)...',
|
||||
'Show Gemini CLI status in the terminal window title (/settings)...',
|
||||
'Use the entire width of the terminal for output (/settings)...',
|
||||
'Enable screen reader mode for better accessibility (/settings)...',
|
||||
'Skip the next speaker check for faster responses (/settings)...',
|
||||
'Use ripgrep for faster file content search (/settings)...',
|
||||
'Enable truncation of large tool outputs to save tokens (/settings)...',
|
||||
'Set the character threshold for truncating tool outputs (/settings)...',
|
||||
'Set the number of lines to keep when truncating outputs (/settings)...',
|
||||
'Enable policy-based tool confirmation via message bus (/settings)...',
|
||||
'Enable smart-edit tool for more precise editing (/settings)...',
|
||||
'Enable write_todos_list tool to generate task lists (/settings)...',
|
||||
'Enable model routing based on complexity (/settings)...',
|
||||
'Enable experimental subagents for task delegation (/settings)...',
|
||||
//Settings tips end here
|
||||
// Keyboard shortcut tips start here
|
||||
'Close dialogs and suggestions with Esc...',
|
||||
'Cancel a request with Ctrl+C, or press twice to exit...',
|
||||
'Exit the app with Ctrl+D on an empty line...',
|
||||
'Clear your screen at any time with Ctrl+L...',
|
||||
'Toggle the debug console display with Ctrl+O...',
|
||||
'See full, untruncated responses with Ctrl+S...',
|
||||
'Show or hide tool descriptions with Ctrl+T...',
|
||||
'Toggle auto-approval (YOLO mode) for all tools with Ctrl+Y...',
|
||||
'Toggle shell mode by typing ! in an empty prompt...',
|
||||
'Insert a newline with a backslash (\\) followed by Enter...',
|
||||
'Navigate your prompt history with the Up and Down arrows...',
|
||||
'You can also use Ctrl+P (up) and Ctrl+N (down) for history...',
|
||||
'Submit your prompt to Gemini with Enter...',
|
||||
'Accept an autocomplete suggestion with Tab or Enter...',
|
||||
'Move to the start of the line with Ctrl+A or Home...',
|
||||
'Move to the end of the line with Ctrl+E or End...',
|
||||
'Move one character left or right with Ctrl+B/F or the arrow keys...',
|
||||
'Move one word left or right with Ctrl+Left/Right Arrow...',
|
||||
'Delete the character to the left with Ctrl+H or Backspace...',
|
||||
'Delete the character to the right with Ctrl+D or Delete...',
|
||||
'Delete the word to the left of the cursor with Ctrl+W...',
|
||||
'Delete the word to the right of the cursor with Ctrl+Delete...',
|
||||
'Delete from the cursor to the start of the line with Ctrl+U...',
|
||||
'Delete from the cursor to the end of the line with Ctrl+K...',
|
||||
'Clear the entire input prompt with a double-press of Esc...',
|
||||
'Paste from your clipboard with Ctrl+V...',
|
||||
'Open the current prompt in an external editor with Ctrl+X...',
|
||||
'In menus, move up/down with k/j or the arrow keys...',
|
||||
'In menus, select an item by typing its number...',
|
||||
"If you're using an IDE, see the context with Ctrl+G...",
|
||||
// Keyboard shortcut tips end here
|
||||
// Command tips start here
|
||||
'Show version info with /about...',
|
||||
'Change your authentication method with /auth...',
|
||||
'File a bug report directly with /bug...',
|
||||
'List your saved chat checkpoints with /chat list...',
|
||||
'Save your current conversation with /chat save <tag>...',
|
||||
'Resume a saved conversation with /chat resume <tag>...',
|
||||
'Delete a conversation checkpoint with /chat delete <tag>...',
|
||||
'Share your conversation to a file with /chat share <file>...',
|
||||
'Clear the screen and history with /clear...',
|
||||
'Save tokens by summarizing the context with /compress...',
|
||||
'Copy the last response to your clipboard with /copy...',
|
||||
'Open the full documentation in your browser with /docs...',
|
||||
'Add directories to your workspace with /directory add <path>...',
|
||||
'Show all directories in your workspace with /directory show...',
|
||||
'Set your preferred external editor with /editor...',
|
||||
'List all active extensions with /extensions list...',
|
||||
'Update all or specific extensions with /extensions update...',
|
||||
'Get help on commands with /help...',
|
||||
'Manage IDE integration with /ide...',
|
||||
'Create a project-specific GEMINI.md file with /init...',
|
||||
'List configured MCP servers and tools with /mcp list...',
|
||||
'Authenticate with an OAuth-enabled MCP server with /mcp auth...',
|
||||
'Restart MCP servers with /mcp refresh...',
|
||||
'See the current instructional context with /memory show...',
|
||||
'Add content to the instructional memory with /memory add...',
|
||||
'Reload instructional context from GEMINI.md files with /memory refresh...',
|
||||
'List the paths of the GEMINI.md files in use with /memory list...',
|
||||
'Display the privacy notice with /privacy...',
|
||||
'Exit the CLI with /quit or /exit...',
|
||||
'Check model-specific usage stats with /stats model...',
|
||||
'Check tool-specific usage stats with /stats tools...',
|
||||
"Change the CLI's color theme with /theme...",
|
||||
'List all available tools with /tools...',
|
||||
'View and edit settings with the /settings editor...',
|
||||
'Toggle Vim keybindings on and off with /vim...',
|
||||
'Set up GitHub Actions with /setup-github...',
|
||||
'Configure terminal keybindings for multiline input with /terminal-setup...',
|
||||
'Find relevant documentation with /find-docs...',
|
||||
'Review a pull request with /oncall:pr-review...',
|
||||
'Go back to main and clean up the branch with /github:cleanup-back-to-main...',
|
||||
'Execute any shell command with !<command>...',
|
||||
// Command tips end here
|
||||
];
|
||||
|
||||
export const PHRASE_CHANGE_INTERVAL_MS = 15000;
|
||||
|
||||
/**
|
||||
@@ -173,16 +315,26 @@ export const usePhraseCycler = (
|
||||
if (phraseIntervalRef.current) {
|
||||
clearInterval(phraseIntervalRef.current);
|
||||
}
|
||||
|
||||
const setRandomPhrase = () => {
|
||||
if (customPhrases && customPhrases.length > 0) {
|
||||
const randomIndex = Math.floor(Math.random() * customPhrases.length);
|
||||
setCurrentLoadingPhrase(customPhrases[randomIndex]);
|
||||
} else {
|
||||
// Roughly 1 in 6 chance to show a tip.
|
||||
const showTip = Math.random() < 1 / 6;
|
||||
const phraseList = showTip ? INFORMATIVE_TIPS : WITTY_LOADING_PHRASES;
|
||||
const randomIndex = Math.floor(Math.random() * phraseList.length);
|
||||
setCurrentLoadingPhrase(phraseList[randomIndex]);
|
||||
}
|
||||
};
|
||||
|
||||
// Select an initial random phrase
|
||||
const initialRandomIndex = Math.floor(
|
||||
Math.random() * loadingPhrases.length,
|
||||
);
|
||||
setCurrentLoadingPhrase(loadingPhrases[initialRandomIndex]);
|
||||
setRandomPhrase();
|
||||
|
||||
phraseIntervalRef.current = setInterval(() => {
|
||||
// Select a new random phrase
|
||||
const randomIndex = Math.floor(Math.random() * loadingPhrases.length);
|
||||
setCurrentLoadingPhrase(loadingPhrases[randomIndex]);
|
||||
setRandomPhrase();
|
||||
}, PHRASE_CHANGE_INTERVAL_MS);
|
||||
} else {
|
||||
// Idle or other states, clear the phrase interval
|
||||
@@ -200,7 +352,7 @@ export const usePhraseCycler = (
|
||||
phraseIntervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [isActive, isWaiting, loadingPhrases]);
|
||||
}, [isActive, isWaiting, customPhrases, loadingPhrases]);
|
||||
|
||||
return currentLoadingPhrase;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user