From 1e4c843bafce6be993dd8444c3c855cb820fe7d3 Mon Sep 17 00:00:00 2001 From: JayadityaGit Date: Wed, 3 Sep 2025 16:18:55 +0530 Subject: [PATCH 1/2] custom witty message --- packages/cli/src/config/settingsSchema.ts | 9 +++ packages/cli/src/ui/App.test.tsx | 4 +- packages/cli/src/ui/App.tsx | 6 +- .../src/ui/hooks/useLoadingIndicator.test.ts | 8 ++- .../cli/src/ui/hooks/useLoadingIndicator.ts | 6 +- .../cli/src/ui/hooks/usePhraseCycler.test.ts | 59 +++++++++++++++++-- packages/cli/src/ui/hooks/usePhraseCycler.ts | 27 +++++---- 7 files changed, 98 insertions(+), 21 deletions(-) diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 238f18179ae..e1a01f0d926 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -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', diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx index e0d5fc9922e..401485d5523 100644 --- a/packages/cli/src/ui/App.test.tsx +++ b/packages/cli/src/ui/App.test.tsx @@ -1040,7 +1040,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 () => { @@ -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'); expect(lastFrame()).not.toContain('Select Theme'); }); }); diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index 4bf0529c154..3b5bb225f6b 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -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( diff --git a/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts b/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts index 039b1bff6c8..734a92606dd 100644 --- a/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts +++ b/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts @@ -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 () => { diff --git a/packages/cli/src/ui/hooks/useLoadingIndicator.ts b/packages/cli/src/ui/hooks/useLoadingIndicator.ts index 46754ac1b55..d69df1706d3 100644 --- a/packages/cli/src/ui/hooks/useLoadingIndicator.ts +++ b/packages/cli/src/ui/hooks/useLoadingIndicator.ts @@ -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); diff --git a/packages/cli/src/ui/hooks/usePhraseCycler.test.ts b/packages/cli/src/ui/hooks/usePhraseCycler.test.ts index ec04aca605a..88eed68c472 100644 --- a/packages/cli/src/ui/hooks/usePhraseCycler.test.ts +++ b/packages/cli/src/ui/hooks/usePhraseCycler.test.ts @@ -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), diff --git a/packages/cli/src/ui/hooks/usePhraseCycler.ts b/packages/cli/src/ui/hooks/usePhraseCycler.ts index 0bf2c2a2408..57b83c68f1d 100644 --- a/packages/cli/src/ui/hooks/usePhraseCycler.ts +++ b/packages/cli/src/ui/hooks/usePhraseCycler.ts @@ -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(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; }; From c24d2f40e58685a9c85d3d8fb8fc89963c3f59ea Mon Sep 17 00:00:00 2001 From: JayadityaGit Date: Wed, 3 Sep 2025 16:41:41 +0530 Subject: [PATCH 2/2] Re-trigger CLA check