mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-28 22:14:52 -07:00
feat: Show tip on first request and refactor phrases (#12952)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
@@ -0,0 +1,168 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
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)...',
|
||||||
|
'Hide the context summary above the input (/settings)...',
|
||||||
|
'Reclaim vertical space by hiding the footer (/settings)...',
|
||||||
|
'Hide individual footer elements like CWD or sandbox status (/settings)...',
|
||||||
|
'Hide the context window percentage in the footer (/settings)...',
|
||||||
|
'Show memory usage for performance monitoring (/settings)...',
|
||||||
|
'Show line numbers in the chat for easier reference (/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)...',
|
||||||
|
'Use alternate screen buffer to preserve shell history (/settings)...',
|
||||||
|
'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)...',
|
||||||
|
'Disable fuzzy search when searching for files (/settings)...',
|
||||||
|
'Run tools in a secure sandbox environment (settings.json)...',
|
||||||
|
'Use an interactive terminal for shell commands (/settings)...',
|
||||||
|
'Show color in shell command output (/settings)...',
|
||||||
|
'Automatically accept safe read-only tool calls (/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)...',
|
||||||
|
'Disable YOLO mode to enforce confirmations (settings.json)...',
|
||||||
|
'Block Git extensions for enhanced security (settings.json)...',
|
||||||
|
'Change your authentication method (/settings)...',
|
||||||
|
'Enforce auth type for enterprise use (settings.json)...',
|
||||||
|
'Let Node.js auto-configure memory (settings.json)...',
|
||||||
|
'Retry on fetch failed errors automatically (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)...',
|
||||||
|
'Enable extension management features (settings.json)...',
|
||||||
|
'Enable extension reloading within the CLI session (settings.json)...',
|
||||||
|
//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 F12...',
|
||||||
|
'Toggle the todo list display with Ctrl+T...',
|
||||||
|
'See full, untruncated responses with Ctrl+S...',
|
||||||
|
'Toggle auto-approval (YOLO mode) for all tools with Ctrl+Y...',
|
||||||
|
'Toggle auto-accepting edits approval mode with Shift+Tab...',
|
||||||
|
'Toggle Markdown rendering (raw markdown mode) with Option+M...',
|
||||||
|
'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...',
|
||||||
|
'Search through command history with Ctrl+R...',
|
||||||
|
'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...',
|
||||||
|
'Undo text edits in the input with Ctrl+Z...',
|
||||||
|
'Redo undone text edits with Ctrl+Shift+Z...',
|
||||||
|
'Open the current prompt in an external editor with Ctrl+X or Meta+Enter...',
|
||||||
|
'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...',
|
||||||
|
'Use /dir as a shortcut for /directory...',
|
||||||
|
'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...',
|
||||||
|
'Choose your Gemini model with /model...',
|
||||||
|
'Display the privacy notice with /privacy...',
|
||||||
|
'Restore project files to a previous state with /restore...',
|
||||||
|
'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
|
||||||
|
];
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const WITTY_LOADING_PHRASES = [
|
||||||
|
"I'm Feeling Lucky",
|
||||||
|
'Shipping awesomeness... ',
|
||||||
|
'Painting the serifs back on...',
|
||||||
|
'Navigating the slime mold...',
|
||||||
|
'Consulting the digital spirits...',
|
||||||
|
'Reticulating splines...',
|
||||||
|
'Warming up the AI hamsters...',
|
||||||
|
'Asking the magic conch shell...',
|
||||||
|
'Generating witty retort...',
|
||||||
|
'Polishing the algorithms...',
|
||||||
|
"Don't rush perfection (or my code)...",
|
||||||
|
'Brewing fresh bytes...',
|
||||||
|
'Counting electrons...',
|
||||||
|
'Engaging cognitive processors...',
|
||||||
|
'Checking for syntax errors in the universe...',
|
||||||
|
'One moment, optimizing humor...',
|
||||||
|
'Shuffling punchlines...',
|
||||||
|
'Untangling neural nets...',
|
||||||
|
'Compiling brilliance...',
|
||||||
|
'Loading wit.exe...',
|
||||||
|
'Summoning the cloud of wisdom...',
|
||||||
|
'Preparing a witty response...',
|
||||||
|
"Just a sec, I'm debugging reality...",
|
||||||
|
'Confuzzling the options...',
|
||||||
|
'Tuning the cosmic frequencies...',
|
||||||
|
'Crafting a response worthy of your patience...',
|
||||||
|
'Compiling the 1s and 0s...',
|
||||||
|
'Resolving dependencies... and existential crises...',
|
||||||
|
'Defragmenting memories... both RAM and personal...',
|
||||||
|
'Rebooting the humor module...',
|
||||||
|
'Caching the essentials (mostly cat memes)...',
|
||||||
|
'Optimizing for ludicrous speed',
|
||||||
|
"Swapping bits... don't tell the bytes...",
|
||||||
|
'Garbage collecting... be right back...',
|
||||||
|
'Assembling the interwebs...',
|
||||||
|
'Converting coffee into code...',
|
||||||
|
'Updating the syntax for reality...',
|
||||||
|
'Rewiring the synapses...',
|
||||||
|
'Looking for a misplaced semicolon...',
|
||||||
|
"Greasin' the cogs of the machine...",
|
||||||
|
'Pre-heating the servers...',
|
||||||
|
'Calibrating the flux capacitor...',
|
||||||
|
'Engaging the improbability drive...',
|
||||||
|
'Channeling the Force...',
|
||||||
|
'Aligning the stars for optimal response...',
|
||||||
|
'So say we all...',
|
||||||
|
'Loading the next great idea...',
|
||||||
|
"Just a moment, I'm in the zone...",
|
||||||
|
'Preparing to dazzle you with brilliance...',
|
||||||
|
"Just a tick, I'm polishing my wit...",
|
||||||
|
"Hold tight, I'm crafting a masterpiece...",
|
||||||
|
"Just a jiffy, I'm debugging the universe...",
|
||||||
|
"Just a moment, I'm aligning the pixels...",
|
||||||
|
"Just a sec, I'm optimizing the humor...",
|
||||||
|
"Just a moment, I'm tuning the algorithms...",
|
||||||
|
'Warp speed engaged...',
|
||||||
|
'Mining for more Dilithium crystals...',
|
||||||
|
"Don't panic...",
|
||||||
|
'Following the white rabbit...',
|
||||||
|
'The truth is in here... somewhere...',
|
||||||
|
'Blowing on the cartridge...',
|
||||||
|
'Loading... Do a barrel roll!',
|
||||||
|
'Waiting for the respawn...',
|
||||||
|
'Finishing the Kessel Run in less than 12 parsecs...',
|
||||||
|
"The cake is not a lie, it's just still loading...",
|
||||||
|
'Fiddling with the character creation screen...',
|
||||||
|
"Just a moment, I'm finding the right meme...",
|
||||||
|
"Pressing 'A' to continue...",
|
||||||
|
'Herding digital cats...',
|
||||||
|
'Polishing the pixels...',
|
||||||
|
'Finding a suitable loading screen pun...',
|
||||||
|
'Distracting you with this witty phrase...',
|
||||||
|
'Almost there... probably...',
|
||||||
|
'Our hamsters are working as fast as they can...',
|
||||||
|
'Giving Cloudy a pat on the head...',
|
||||||
|
'Petting the cat...',
|
||||||
|
'Rickrolling my boss...',
|
||||||
|
'Slapping the bass...',
|
||||||
|
'Tasting the snozberries...',
|
||||||
|
"I'm going the distance, I'm going for speed...",
|
||||||
|
'Is this the real life? Is this just fantasy?...',
|
||||||
|
"I've got a good feeling about this...",
|
||||||
|
'Poking the bear...',
|
||||||
|
'Doing research on the latest memes...',
|
||||||
|
'Figuring out how to make this more witty...',
|
||||||
|
'Hmmm... let me think...',
|
||||||
|
'What do you call a fish with no eyes? A fsh...',
|
||||||
|
'Why did the computer go to therapy? It had too many bytes...',
|
||||||
|
"Why don't programmers like nature? It has too many bugs...",
|
||||||
|
'Why do programmers prefer dark mode? Because light attracts bugs...',
|
||||||
|
'Why did the developer go broke? Because they used up all their cache...',
|
||||||
|
"What can you do with a broken pencil? Nothing, it's pointless...",
|
||||||
|
'Applying percussive maintenance...',
|
||||||
|
'Searching for the correct USB orientation...',
|
||||||
|
'Ensuring the magic smoke stays inside the wires...',
|
||||||
|
'Rewriting in Rust for no particular reason...',
|
||||||
|
'Trying to exit Vim...',
|
||||||
|
'Spinning up the hamster wheel...',
|
||||||
|
"That's not a bug, it's an undocumented feature...",
|
||||||
|
'Engage.',
|
||||||
|
"I'll be back... with an answer.",
|
||||||
|
'My other process is a TARDIS...',
|
||||||
|
'Communing with the machine spirit...',
|
||||||
|
'Letting the thoughts marinate...',
|
||||||
|
'Just remembered where I put my keys...',
|
||||||
|
'Pondering the orb...',
|
||||||
|
"I've seen things you people wouldn't believe... like a user who reads loading messages.",
|
||||||
|
'Initiating thoughtful gaze...',
|
||||||
|
"What's a computer's favorite snack? Microchips.",
|
||||||
|
"Why do Java developers wear glasses? Because they don't C#.",
|
||||||
|
'Charging the laser... pew pew!',
|
||||||
|
'Dividing by zero... just kidding!',
|
||||||
|
'Looking for an adult superviso... I mean, processing.',
|
||||||
|
'Making it go beep boop.',
|
||||||
|
'Buffering... because even AIs need a moment.',
|
||||||
|
'Entangling quantum particles for a faster response...',
|
||||||
|
'Polishing the chrome... on the algorithms.',
|
||||||
|
'Are you not entertained? (Working on it!)',
|
||||||
|
'Summoning the code gremlins... to help, of course.',
|
||||||
|
'Just waiting for the dial-up tone to finish...',
|
||||||
|
'Recalibrating the humor-o-meter.',
|
||||||
|
'My other loading screen is even funnier.',
|
||||||
|
"Pretty sure there's a cat walking on the keyboard somewhere...",
|
||||||
|
'Enhancing... Enhancing... Still loading.',
|
||||||
|
"It's not a bug, it's a feature... of this loading screen.",
|
||||||
|
'Have you tried turning it off and on again? (The loading screen, not me.)',
|
||||||
|
'Constructing additional pylons...',
|
||||||
|
'New line? That’s Ctrl+J.',
|
||||||
|
'Releasing the HypnoDrones...',
|
||||||
|
];
|
||||||
@@ -9,10 +9,8 @@ import { act } from 'react';
|
|||||||
import { render } from '../../test-utils/render.js';
|
import { render } from '../../test-utils/render.js';
|
||||||
import { useLoadingIndicator } from './useLoadingIndicator.js';
|
import { useLoadingIndicator } from './useLoadingIndicator.js';
|
||||||
import { StreamingState } from '../types.js';
|
import { StreamingState } from '../types.js';
|
||||||
import {
|
import { PHRASE_CHANGE_INTERVAL_MS } from './usePhraseCycler.js';
|
||||||
WITTY_LOADING_PHRASES,
|
import { WITTY_LOADING_PHRASES } from '../constants/wittyPhrases.js';
|
||||||
PHRASE_CHANGE_INTERVAL_MS,
|
|
||||||
} from './usePhraseCycler.js';
|
|
||||||
|
|
||||||
describe('useLoadingIndicator', () => {
|
describe('useLoadingIndicator', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -61,20 +59,18 @@ describe('useLoadingIndicator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should reflect values when Responding', async () => {
|
it('should reflect values when Responding', async () => {
|
||||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty for subsequent phrases
|
||||||
const { result } = renderLoadingIndicatorHook(StreamingState.Responding);
|
const { result } = renderLoadingIndicatorHook(StreamingState.Responding);
|
||||||
|
|
||||||
// Initial state before timers advance
|
// Initial phrase on first activation will be a tip, not necessarily from witty phrases
|
||||||
expect(result.current.elapsedTime).toBe(0);
|
expect(result.current.elapsedTime).toBe(0);
|
||||||
expect(WITTY_LOADING_PHRASES).toContain(
|
// On first activation, it may show a tip, so we can't guarantee it's in WITTY_LOADING_PHRASES
|
||||||
result.current.currentLoadingPhrase,
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS + 1);
|
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Phrase should cycle if PHRASE_CHANGE_INTERVAL_MS has passed
|
// Phrase should cycle if PHRASE_CHANGE_INTERVAL_MS has passed, now it should be witty since first activation already happened
|
||||||
expect(WITTY_LOADING_PHRASES).toContain(
|
expect(WITTY_LOADING_PHRASES).toContain(
|
||||||
result.current.currentLoadingPhrase,
|
result.current.currentLoadingPhrase,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ import { render } from '../../test-utils/render.js';
|
|||||||
import { Text } from 'ink';
|
import { Text } from 'ink';
|
||||||
import {
|
import {
|
||||||
usePhraseCycler,
|
usePhraseCycler,
|
||||||
WITTY_LOADING_PHRASES,
|
|
||||||
PHRASE_CHANGE_INTERVAL_MS,
|
PHRASE_CHANGE_INTERVAL_MS,
|
||||||
} from './usePhraseCycler.js';
|
} from './usePhraseCycler.js';
|
||||||
|
import { INFORMATIVE_TIPS } from '../constants/tips.js';
|
||||||
|
import { WITTY_LOADING_PHRASES } from '../constants/wittyPhrases.js';
|
||||||
|
|
||||||
// Test component to consume the hook
|
// Test component to consume the hook
|
||||||
const TestComponent = ({
|
const TestComponent = ({
|
||||||
@@ -67,17 +68,37 @@ describe('usePhraseCycler', () => {
|
|||||||
expect(lastFrame()).toBe(initialPhrase);
|
expect(lastFrame()).toBe(initialPhrase);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cycle through witty phrases when isActive is true and not waiting', async () => {
|
it('should show a tip on first activation, then a witty phrase', async () => {
|
||||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
vi.spyOn(Math, 'random').mockImplementation(() => 0.99); // Subsequent phrases are witty
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<TestComponent isActive={true} isWaiting={false} />,
|
<TestComponent isActive={true} isWaiting={false} />,
|
||||||
);
|
);
|
||||||
// Initial phrase should be one of the witty phrases
|
|
||||||
|
// Initial phrase on first activation should be a tip
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await vi.advanceTimersByTimeAsync(0);
|
await vi.advanceTimersByTimeAsync(0);
|
||||||
});
|
});
|
||||||
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
expect(INFORMATIVE_TIPS).toContain(lastFrame());
|
||||||
|
|
||||||
|
// After the first interval, it should be a witty phrase
|
||||||
|
await act(async () => {
|
||||||
|
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS + 100);
|
||||||
|
});
|
||||||
|
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should cycle through phrases when isActive is true and not waiting', async () => {
|
||||||
|
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty for subsequent phrases
|
||||||
|
const { lastFrame } = render(
|
||||||
|
<TestComponent isActive={true} isWaiting={false} />,
|
||||||
|
);
|
||||||
|
// Initial phrase on first activation will be a tip, not necessarily from witty phrases
|
||||||
|
await act(async () => {
|
||||||
|
await vi.advanceTimersByTimeAsync(0);
|
||||||
|
});
|
||||||
|
// First activation shows a tip, so we can't guarantee it's in WITTY_LOADING_PHRASES
|
||||||
|
|
||||||
|
// After the first interval, it should follow the random pattern (witty phrases due to mock)
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS + 100);
|
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS + 100);
|
||||||
});
|
});
|
||||||
@@ -109,7 +130,7 @@ describe('usePhraseCycler', () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Activate -> callCount 0 -> returns 0 -> 'Phrase A'
|
// Activate -> On first activation will show tip on initial call, then first interval will use first mock value for 'Phrase A'
|
||||||
rerender(
|
rerender(
|
||||||
<TestComponent
|
<TestComponent
|
||||||
isActive={true}
|
isActive={true}
|
||||||
@@ -118,17 +139,17 @@ describe('usePhraseCycler', () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await vi.advanceTimersByTimeAsync(0);
|
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS); // First interval after initial state -> callCount 0 -> 'Phrase A'
|
||||||
});
|
});
|
||||||
expect(lastFrame()).toBe('Phrase A');
|
expect(customPhrases).toContain(lastFrame()); // Should be one of the custom phrases
|
||||||
|
|
||||||
// Interval -> callCount 1 -> returns 0.99 -> 'Phrase B'
|
// Second interval -> callCount 1 -> returns 0.99 -> 'Phrase B'
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS);
|
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS);
|
||||||
});
|
});
|
||||||
expect(lastFrame()).toBe('Phrase B');
|
expect(customPhrases).toContain(lastFrame()); // Should be one of the custom phrases
|
||||||
|
|
||||||
// Deactivate -> resets to customPhrases[0] -> 'Phrase A'
|
// Deactivate -> resets to first phrase in sequence
|
||||||
rerender(
|
rerender(
|
||||||
<TestComponent
|
<TestComponent
|
||||||
isActive={false}
|
isActive={false}
|
||||||
@@ -139,9 +160,10 @@ describe('usePhraseCycler', () => {
|
|||||||
await act(async () => {
|
await act(async () => {
|
||||||
await vi.advanceTimersByTimeAsync(0);
|
await vi.advanceTimersByTimeAsync(0);
|
||||||
});
|
});
|
||||||
expect(lastFrame()).toBe('Phrase A');
|
// The phrase should be the first phrase after reset
|
||||||
|
expect(customPhrases).toContain(lastFrame());
|
||||||
|
|
||||||
// Activate again -> callCount 2 -> returns 0 -> 'Phrase A'
|
// Activate again -> this will show a tip on first activation, then cycle from where mock is
|
||||||
rerender(
|
rerender(
|
||||||
<TestComponent
|
<TestComponent
|
||||||
isActive={true}
|
isActive={true}
|
||||||
@@ -150,9 +172,9 @@ describe('usePhraseCycler', () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await vi.advanceTimersByTimeAsync(0);
|
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS); // First interval after re-activation -> should contain phrase
|
||||||
});
|
});
|
||||||
expect(lastFrame()).toBe('Phrase A');
|
expect(customPhrases).toContain(lastFrame()); // Should be one of the custom phrases
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clear phrase interval on unmount when active', () => {
|
it('should clear phrase interval on unmount when active', () => {
|
||||||
@@ -167,7 +189,6 @@ describe('usePhraseCycler', () => {
|
|||||||
it('should use custom phrases when provided', async () => {
|
it('should use custom phrases when provided', async () => {
|
||||||
const customPhrases = ['Custom Phrase 1', 'Custom Phrase 2'];
|
const customPhrases = ['Custom Phrase 1', 'Custom Phrase 2'];
|
||||||
const randomMock = vi.spyOn(Math, 'random');
|
const randomMock = vi.spyOn(Math, 'random');
|
||||||
randomMock.mockReturnValue(0);
|
|
||||||
|
|
||||||
const { lastFrame, rerender } = render(
|
const { lastFrame, rerender } = render(
|
||||||
<TestComponent
|
<TestComponent
|
||||||
@@ -177,14 +198,29 @@ describe('usePhraseCycler', () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(lastFrame()).toBe('Custom Phrase 1');
|
// After first interval, it should use custom phrases
|
||||||
|
|
||||||
randomMock.mockReturnValue(0.99);
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS + 100);
|
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS + 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(lastFrame()).toBe('Custom Phrase 2');
|
randomMock.mockReturnValue(0);
|
||||||
|
rerender(
|
||||||
|
<TestComponent
|
||||||
|
isActive={true}
|
||||||
|
isWaiting={false}
|
||||||
|
customPhrases={customPhrases}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS + 100);
|
||||||
|
});
|
||||||
|
expect(customPhrases).toContain(lastFrame());
|
||||||
|
|
||||||
|
randomMock.mockReturnValue(0.99);
|
||||||
|
await act(async () => {
|
||||||
|
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS);
|
||||||
|
});
|
||||||
|
expect(customPhrases).toContain(lastFrame());
|
||||||
|
|
||||||
// Test fallback to default phrases.
|
// Test fallback to default phrases.
|
||||||
randomMock.mockRestore();
|
randomMock.mockRestore();
|
||||||
@@ -194,36 +230,39 @@ describe('usePhraseCycler', () => {
|
|||||||
<TestComponent isActive={true} isWaiting={false} customPhrases={[]} />,
|
<TestComponent isActive={true} isWaiting={false} customPhrases={[]} />,
|
||||||
);
|
);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await vi.advanceTimersByTimeAsync(0);
|
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS); // Wait for first cycle
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fall back to witty phrases if custom phrases are an empty array', async () => {
|
it('should fall back to witty phrases if custom phrases are an empty array', async () => {
|
||||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty for subsequent phrases
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<TestComponent isActive={true} isWaiting={false} customPhrases={[]} />,
|
<TestComponent isActive={true} isWaiting={false} customPhrases={[]} />,
|
||||||
);
|
);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await vi.advanceTimersByTimeAsync(0);
|
await vi.advanceTimersByTimeAsync(0); // First activation will be a tip
|
||||||
});
|
});
|
||||||
|
// First activation shows a tip, so we can't guarantee it's in WITTY_LOADING_PHRASES
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS); // Next phrase after tip
|
||||||
|
});
|
||||||
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reset to a witty phrase when transitioning from waiting to active', async () => {
|
it('should reset phrase when transitioning from waiting to active', async () => {
|
||||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty for subsequent phrases
|
||||||
const { lastFrame, rerender } = render(
|
const { lastFrame, rerender } = render(
|
||||||
<TestComponent isActive={true} isWaiting={false} />,
|
<TestComponent isActive={true} isWaiting={false} />,
|
||||||
);
|
);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await vi.advanceTimersByTimeAsync(0);
|
await vi.advanceTimersByTimeAsync(0); // First activation will be a tip
|
||||||
});
|
});
|
||||||
|
// First activation shows a tip, so we can't guarantee it's in WITTY_LOADING_PHRASES
|
||||||
|
|
||||||
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
// Cycle to a different phrase (should be witty due to mock)
|
||||||
|
|
||||||
// Cycle to a different phrase (potentially)
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS);
|
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS);
|
||||||
});
|
});
|
||||||
@@ -236,10 +275,10 @@ describe('usePhraseCycler', () => {
|
|||||||
});
|
});
|
||||||
expect(lastFrame()).toBe('Waiting for user confirmation...');
|
expect(lastFrame()).toBe('Waiting for user confirmation...');
|
||||||
|
|
||||||
// Go back to active cycling - should pick a random witty phrase
|
// Go back to active cycling - should pick a phrase based on the logic (witty due to mock)
|
||||||
rerender(<TestComponent isActive={true} isWaiting={false} />);
|
rerender(<TestComponent isActive={true} isWaiting={false} />);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await vi.advanceTimersByTimeAsync(0);
|
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS); // Skip the tip and get next phrase
|
||||||
});
|
});
|
||||||
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,280 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
import { INFORMATIVE_TIPS } from '../constants/tips.js';
|
||||||
export const WITTY_LOADING_PHRASES = [
|
import { WITTY_LOADING_PHRASES } from '../constants/wittyPhrases.js';
|
||||||
"I'm Feeling Lucky",
|
|
||||||
'Shipping awesomeness... ',
|
|
||||||
'Painting the serifs back on...',
|
|
||||||
'Navigating the slime mold...',
|
|
||||||
'Consulting the digital spirits...',
|
|
||||||
'Reticulating splines...',
|
|
||||||
'Warming up the AI hamsters...',
|
|
||||||
'Asking the magic conch shell...',
|
|
||||||
'Generating witty retort...',
|
|
||||||
'Polishing the algorithms...',
|
|
||||||
"Don't rush perfection (or my code)...",
|
|
||||||
'Brewing fresh bytes...',
|
|
||||||
'Counting electrons...',
|
|
||||||
'Engaging cognitive processors...',
|
|
||||||
'Checking for syntax errors in the universe...',
|
|
||||||
'One moment, optimizing humor...',
|
|
||||||
'Shuffling punchlines...',
|
|
||||||
'Untangling neural nets...',
|
|
||||||
'Compiling brilliance...',
|
|
||||||
'Loading wit.exe...',
|
|
||||||
'Summoning the cloud of wisdom...',
|
|
||||||
'Preparing a witty response...',
|
|
||||||
"Just a sec, I'm debugging reality...",
|
|
||||||
'Confuzzling the options...',
|
|
||||||
'Tuning the cosmic frequencies...',
|
|
||||||
'Crafting a response worthy of your patience...',
|
|
||||||
'Compiling the 1s and 0s...',
|
|
||||||
'Resolving dependencies... and existential crises...',
|
|
||||||
'Defragmenting memories... both RAM and personal...',
|
|
||||||
'Rebooting the humor module...',
|
|
||||||
'Caching the essentials (mostly cat memes)...',
|
|
||||||
'Optimizing for ludicrous speed',
|
|
||||||
"Swapping bits... don't tell the bytes...",
|
|
||||||
'Garbage collecting... be right back...',
|
|
||||||
'Assembling the interwebs...',
|
|
||||||
'Converting coffee into code...',
|
|
||||||
'Updating the syntax for reality...',
|
|
||||||
'Rewiring the synapses...',
|
|
||||||
'Looking for a misplaced semicolon...',
|
|
||||||
"Greasin' the cogs of the machine...",
|
|
||||||
'Pre-heating the servers...',
|
|
||||||
'Calibrating the flux capacitor...',
|
|
||||||
'Engaging the improbability drive...',
|
|
||||||
'Channeling the Force...',
|
|
||||||
'Aligning the stars for optimal response...',
|
|
||||||
'So say we all...',
|
|
||||||
'Loading the next great idea...',
|
|
||||||
"Just a moment, I'm in the zone...",
|
|
||||||
'Preparing to dazzle you with brilliance...',
|
|
||||||
"Just a tick, I'm polishing my wit...",
|
|
||||||
"Hold tight, I'm crafting a masterpiece...",
|
|
||||||
"Just a jiffy, I'm debugging the universe...",
|
|
||||||
"Just a moment, I'm aligning the pixels...",
|
|
||||||
"Just a sec, I'm optimizing the humor...",
|
|
||||||
"Just a moment, I'm tuning the algorithms...",
|
|
||||||
'Warp speed engaged...',
|
|
||||||
'Mining for more Dilithium crystals...',
|
|
||||||
"Don't panic...",
|
|
||||||
'Following the white rabbit...',
|
|
||||||
'The truth is in here... somewhere...',
|
|
||||||
'Blowing on the cartridge...',
|
|
||||||
'Loading... Do a barrel roll!',
|
|
||||||
'Waiting for the respawn...',
|
|
||||||
'Finishing the Kessel Run in less than 12 parsecs...',
|
|
||||||
"The cake is not a lie, it's just still loading...",
|
|
||||||
'Fiddling with the character creation screen...',
|
|
||||||
"Just a moment, I'm finding the right meme...",
|
|
||||||
"Pressing 'A' to continue...",
|
|
||||||
'Herding digital cats...',
|
|
||||||
'Polishing the pixels...',
|
|
||||||
'Finding a suitable loading screen pun...',
|
|
||||||
'Distracting you with this witty phrase...',
|
|
||||||
'Almost there... probably...',
|
|
||||||
'Our hamsters are working as fast as they can...',
|
|
||||||
'Giving Cloudy a pat on the head...',
|
|
||||||
'Petting the cat...',
|
|
||||||
'Rickrolling my boss...',
|
|
||||||
'Never gonna give you up, never gonna let you down...',
|
|
||||||
'Slapping the bass...',
|
|
||||||
'Tasting the snozberries...',
|
|
||||||
"I'm going the distance, I'm going for speed...",
|
|
||||||
'Is this the real life? Is this just fantasy?...',
|
|
||||||
"I've got a good feeling about this...",
|
|
||||||
'Poking the bear...',
|
|
||||||
'Doing research on the latest memes...',
|
|
||||||
'Figuring out how to make this more witty...',
|
|
||||||
'Hmmm... let me think...',
|
|
||||||
'What do you call a fish with no eyes? A fsh...',
|
|
||||||
'Why did the computer go to therapy? It had too many bytes...',
|
|
||||||
"Why don't programmers like nature? It has too many bugs...",
|
|
||||||
'Why do programmers prefer dark mode? Because light attracts bugs...',
|
|
||||||
'Why did the developer go broke? Because they used up all their cache...',
|
|
||||||
"What can you do with a broken pencil? Nothing, it's pointless...",
|
|
||||||
'Applying percussive maintenance...',
|
|
||||||
'Searching for the correct USB orientation...',
|
|
||||||
'Ensuring the magic smoke stays inside the wires...',
|
|
||||||
'Rewriting in Rust for no particular reason...',
|
|
||||||
'Trying to exit Vim...',
|
|
||||||
'Spinning up the hamster wheel...',
|
|
||||||
"That's not a bug, it's an undocumented feature...",
|
|
||||||
'Engage.',
|
|
||||||
"I'll be back... with an answer.",
|
|
||||||
'My other process is a TARDIS...',
|
|
||||||
'Communing with the machine spirit...',
|
|
||||||
'Letting the thoughts marinate...',
|
|
||||||
'Just remembered where I put my keys...',
|
|
||||||
'Pondering the orb...',
|
|
||||||
"I've seen things you people wouldn't believe... like a user who reads loading messages.",
|
|
||||||
'Initiating thoughtful gaze...',
|
|
||||||
"What's a computer's favorite snack? Microchips.",
|
|
||||||
"Why do Java developers wear glasses? Because they don't C#.",
|
|
||||||
'Charging the laser... pew pew!',
|
|
||||||
'Dividing by zero... just kidding!',
|
|
||||||
'Looking for an adult superviso... I mean, processing.',
|
|
||||||
'Making it go beep boop.',
|
|
||||||
'Buffering... because even AIs need a moment.',
|
|
||||||
'Entangling quantum particles for a faster response...',
|
|
||||||
'Polishing the chrome... on the algorithms.',
|
|
||||||
'Are you not entertained? (Working on it!)',
|
|
||||||
'Summoning the code gremlins... to help, of course.',
|
|
||||||
'Just waiting for the dial-up tone to finish...',
|
|
||||||
'Recalibrating the humor-o-meter.',
|
|
||||||
'My other loading screen is even funnier.',
|
|
||||||
"Pretty sure there's a cat walking on the keyboard somewhere...",
|
|
||||||
'Enhancing... Enhancing... Still loading.',
|
|
||||||
"It's not a bug, it's a feature... of this loading screen.",
|
|
||||||
'Have you tried turning it off and on again? (The loading screen, not me.)',
|
|
||||||
'Constructing additional pylons...',
|
|
||||||
'New line? That’s Ctrl+J.',
|
|
||||||
'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 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 F12...',
|
|
||||||
'See full, untruncated responses with Ctrl+S...',
|
|
||||||
'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;
|
export const PHRASE_CHANGE_INTERVAL_MS = 15000;
|
||||||
|
|
||||||
@@ -302,6 +30,7 @@ export const usePhraseCycler = (
|
|||||||
loadingPhrases[0],
|
loadingPhrases[0],
|
||||||
);
|
);
|
||||||
const phraseIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
const phraseIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const hasShownFirstRequestTipRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isWaiting) {
|
if (isWaiting) {
|
||||||
@@ -320,9 +49,17 @@ export const usePhraseCycler = (
|
|||||||
const randomIndex = Math.floor(Math.random() * customPhrases.length);
|
const randomIndex = Math.floor(Math.random() * customPhrases.length);
|
||||||
setCurrentLoadingPhrase(customPhrases[randomIndex]);
|
setCurrentLoadingPhrase(customPhrases[randomIndex]);
|
||||||
} else {
|
} else {
|
||||||
// Roughly 1 in 6 chance to show a tip.
|
let phraseList;
|
||||||
const showTip = Math.random() < 1 / 6;
|
// Show a tip on the first request after startup, then continue with 1/6 chance
|
||||||
const phraseList = showTip ? INFORMATIVE_TIPS : WITTY_LOADING_PHRASES;
|
if (!hasShownFirstRequestTipRef.current) {
|
||||||
|
// Show a tip during the first request
|
||||||
|
phraseList = INFORMATIVE_TIPS;
|
||||||
|
hasShownFirstRequestTipRef.current = true;
|
||||||
|
} else {
|
||||||
|
// Roughly 1 in 6 chance to show a tip after the first request
|
||||||
|
const showTip = Math.random() < 1 / 6;
|
||||||
|
phraseList = showTip ? INFORMATIVE_TIPS : WITTY_LOADING_PHRASES;
|
||||||
|
}
|
||||||
const randomIndex = Math.floor(Math.random() * phraseList.length);
|
const randomIndex = Math.floor(Math.random() * phraseList.length);
|
||||||
setCurrentLoadingPhrase(phraseList[randomIndex]);
|
setCurrentLoadingPhrase(phraseList[randomIndex]);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user