summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/hooks
diff options
context:
space:
mode:
authorAllen Hutchison <[email protected]>2025-06-03 10:12:47 -0700
committerGitHub <[email protected]>2025-06-03 10:12:47 -0700
commit72f5ec3725a71ca6b69956faba07d5e9d626bb6c (patch)
tree19726063dea6f0f725b091a5c2dfeec6beadb529 /packages/cli/src/ui/hooks
parentd9677528339aaf8b559368e4ee1b8c9d7793c293 (diff)
feat(cli): randomize and expand witty loading phrases (#704)
Diffstat (limited to 'packages/cli/src/ui/hooks')
-rw-r--r--packages/cli/src/ui/hooks/useLoadingIndicator.test.ts19
-rw-r--r--packages/cli/src/ui/hooks/usePhraseCycler.test.ts50
-rw-r--r--packages/cli/src/ui/hooks/usePhraseCycler.ts123
3 files changed, 159 insertions, 33 deletions
diff --git a/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts b/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts
index c20ded88..92ae81a2 100644
--- a/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts
+++ b/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { act, renderHook } from '@testing-library/react';
import { useLoadingIndicator } from './useLoadingIndicator.js';
import { StreamingState } from '../types.js';
@@ -39,15 +39,18 @@ describe('useLoadingIndicator', () => {
// Initial state before timers advance
expect(result.current.elapsedTime).toBe(0);
- expect(result.current.currentLoadingPhrase).toBe(WITTY_LOADING_PHRASES[0]);
+ expect(WITTY_LOADING_PHRASES).toContain(
+ result.current.currentLoadingPhrase,
+ );
+ const _initialPhrase = result.current.currentLoadingPhrase;
act(() => {
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
});
// Phrase should cycle if PHRASE_CHANGE_INTERVAL_MS has passed
- // This depends on the actual implementation of usePhraseCycler
- // For simplicity, we'll check it's one of the witty phrases
- expect(result.current.currentLoadingPhrase).toBe(WITTY_LOADING_PHRASES[1]);
+ expect(WITTY_LOADING_PHRASES).toContain(
+ result.current.currentLoadingPhrase,
+ );
});
it('should show waiting phrase and retain elapsedTime when WaitingForConfirmation', () => {
@@ -75,7 +78,7 @@ describe('useLoadingIndicator', () => {
expect(result.current.elapsedTime).toBe(60);
});
- it('should reset elapsedTime and use initial phrase when transitioning from WaitingForConfirmation to Responding', () => {
+ it('should reset elapsedTime and use a witty phrase when transitioning from WaitingForConfirmation to Responding', () => {
const { result, rerender } = renderHook(
({ streamingState }) => useLoadingIndicator(streamingState),
{ initialProps: { streamingState: StreamingState.Responding } },
@@ -94,7 +97,9 @@ describe('useLoadingIndicator', () => {
rerender({ streamingState: StreamingState.Responding });
expect(result.current.elapsedTime).toBe(0); // Should reset
- expect(result.current.currentLoadingPhrase).toBe(WITTY_LOADING_PHRASES[0]);
+ expect(WITTY_LOADING_PHRASES).toContain(
+ result.current.currentLoadingPhrase,
+ );
act(() => {
vi.advanceTimersByTime(1000);
diff --git a/packages/cli/src/ui/hooks/usePhraseCycler.test.ts b/packages/cli/src/ui/hooks/usePhraseCycler.test.ts
index f5be12e9..fca5970f 100644
--- a/packages/cli/src/ui/hooks/usePhraseCycler.test.ts
+++ b/packages/cli/src/ui/hooks/usePhraseCycler.test.ts
@@ -45,40 +45,50 @@ describe('usePhraseCycler', () => {
it('should cycle through witty phrases when isActive is true and not waiting', () => {
const { result } = renderHook(() => usePhraseCycler(true, false));
- expect(result.current).toBe(WITTY_LOADING_PHRASES[0]);
+ // Initial phrase should be one of the witty phrases
+ expect(WITTY_LOADING_PHRASES).toContain(result.current);
+ const _initialPhrase = result.current;
act(() => {
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
});
- expect(result.current).toBe(WITTY_LOADING_PHRASES[1]);
+ // Phrase should change and be one of the witty phrases
+ expect(WITTY_LOADING_PHRASES).toContain(result.current);
+ const _secondPhrase = result.current;
act(() => {
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
});
- expect(result.current).toBe(
- WITTY_LOADING_PHRASES[2 % WITTY_LOADING_PHRASES.length],
- );
+ expect(WITTY_LOADING_PHRASES).toContain(result.current);
});
- it('should reset to the first phrase when isActive becomes true after being false (and not waiting)', () => {
+ it('should reset to a witty phrase when isActive becomes true after being false (and not waiting)', () => {
const { result, rerender } = renderHook(
({ isActive, isWaiting }) => usePhraseCycler(isActive, isWaiting),
{ initialProps: { isActive: false, isWaiting: false } },
);
- // Cycle to a different phrase
+
+ // Activate
rerender({ isActive: true, isWaiting: false });
+ const firstActivePhrase = result.current;
+ expect(WITTY_LOADING_PHRASES).toContain(firstActivePhrase);
+
act(() => {
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
});
- expect(result.current).not.toBe(WITTY_LOADING_PHRASES[0]);
+ // Phrase should change if enough phrases and interval passed
+ if (WITTY_LOADING_PHRASES.length > 1) {
+ expect(result.current).not.toBe(firstActivePhrase);
+ }
+ expect(WITTY_LOADING_PHRASES).toContain(result.current);
- // Set to inactive
+ // Set to inactive - should reset to the default initial phrase
rerender({ isActive: false, isWaiting: false });
- expect(result.current).toBe(WITTY_LOADING_PHRASES[0]); // Should reset to first phrase
+ expect(result.current).toBe(WITTY_LOADING_PHRASES[0]);
- // Set back to active
+ // Set back to active - should pick a random witty phrase
rerender({ isActive: true, isWaiting: false });
- expect(result.current).toBe(WITTY_LOADING_PHRASES[0]); // Should start with the first phrase
+ expect(WITTY_LOADING_PHRASES).toContain(result.current);
});
it('should clear phrase interval on unmount when active', () => {
@@ -88,24 +98,30 @@ describe('usePhraseCycler', () => {
expect(clearIntervalSpy).toHaveBeenCalledOnce();
});
- it('should reset to the first witty phrase when transitioning from waiting to active', () => {
+ it('should reset to a witty phrase when transitioning from waiting to active', () => {
const { result, rerender } = renderHook(
({ isActive, isWaiting }) => usePhraseCycler(isActive, isWaiting),
{ initialProps: { isActive: true, isWaiting: false } },
);
- // Cycle to a different phrase
+ const _initialPhrase = result.current;
+ expect(WITTY_LOADING_PHRASES).toContain(_initialPhrase);
+
+ // Cycle to a different phrase (potentially)
act(() => {
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
});
- expect(result.current).toBe(WITTY_LOADING_PHRASES[1]);
+ if (WITTY_LOADING_PHRASES.length > 1) {
+ // This check is probabilistic with random selection
+ }
+ expect(WITTY_LOADING_PHRASES).toContain(result.current);
// Go to waiting state
rerender({ isActive: false, isWaiting: true });
expect(result.current).toBe('Waiting for user confirmation...');
- // Go back to active cycling
+ // Go back to active cycling - should pick a random witty phrase
rerender({ isActive: true, isWaiting: false });
- expect(result.current).toBe(WITTY_LOADING_PHRASES[0]);
+ expect(WITTY_LOADING_PHRASES).toContain(result.current);
});
});
diff --git a/packages/cli/src/ui/hooks/usePhraseCycler.ts b/packages/cli/src/ui/hooks/usePhraseCycler.ts
index 96bad676..6b9450c9 100644
--- a/packages/cli/src/ui/hooks/usePhraseCycler.ts
+++ b/packages/cli/src/ui/hooks/usePhraseCycler.ts
@@ -22,6 +22,111 @@ export const WITTY_LOADING_PHRASES = [
'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)...',
+ 'Running sudo make me a sandwich...',
+ 'Optimizing for ludicrous speed',
+ "Swapping bits... don't tell the bytes...",
+ 'Garbage collecting... be right back...',
+ 'Assembling the interwebs...',
+ 'Converting coffee into code...',
+ 'Pushing to production (and hoping for the best)...',
+ '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...',
+ "I'm Giving Her all she's got Captain!",
+ "Don't panic...",
+ 'Following the white rabbit...',
+ 'The truth is in here... somewhere...',
+ 'Blowing on the cartridge...',
+ 'Looking for the princess in another castle...',
+ '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...',
+ '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 he used up all his 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.)',
];
export const PHRASE_CHANGE_INTERVAL_MS = 15000;
@@ -37,7 +142,6 @@ export const usePhraseCycler = (isActive: boolean, isWaiting: boolean) => {
WITTY_LOADING_PHRASES[0],
);
const phraseIntervalRef = useRef<NodeJS.Timeout | null>(null);
- const currentPhraseIndexRef = useRef<number>(0);
useEffect(() => {
if (isWaiting) {
@@ -50,16 +154,18 @@ export const usePhraseCycler = (isActive: boolean, isWaiting: boolean) => {
if (phraseIntervalRef.current) {
clearInterval(phraseIntervalRef.current);
}
- // Reset to the first witty phrase when starting to respond
- currentPhraseIndexRef.current = 0;
- setCurrentLoadingPhrase(WITTY_LOADING_PHRASES[0]);
+ // Select an initial random phrase
+ const initialRandomIndex = Math.floor(
+ Math.random() * WITTY_LOADING_PHRASES.length,
+ );
+ setCurrentLoadingPhrase(WITTY_LOADING_PHRASES[initialRandomIndex]);
phraseIntervalRef.current = setInterval(() => {
- currentPhraseIndexRef.current =
- (currentPhraseIndexRef.current + 1) % WITTY_LOADING_PHRASES.length;
- setCurrentLoadingPhrase(
- WITTY_LOADING_PHRASES[currentPhraseIndexRef.current],
+ // Select a new random phrase
+ const randomIndex = Math.floor(
+ Math.random() * WITTY_LOADING_PHRASES.length,
);
+ setCurrentLoadingPhrase(WITTY_LOADING_PHRASES[randomIndex]);
}, PHRASE_CHANGE_INTERVAL_MS);
} else {
// Idle or other states, clear the phrase interval
@@ -69,7 +175,6 @@ export const usePhraseCycler = (isActive: boolean, isWaiting: boolean) => {
phraseIntervalRef.current = null;
}
setCurrentLoadingPhrase(WITTY_LOADING_PHRASES[0]);
- currentPhraseIndexRef.current = 0;
}
return () => {