diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index a2ed7cdc..39cbaf47 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -10,6 +10,7 @@ import { StreamingState, type HistoryItem } from './types.js'; import { useGeminiStream } from './hooks/useGeminiStream.js'; import { useLoadingIndicator } from './hooks/useLoadingIndicator.js'; import { useThemeCommand } from './hooks/useThemeCommand.js'; +import { useSlashCommandProcessor } from './hooks/slashCommandProcessor.js'; import { Header } from './components/Header.js'; import { LoadingIndicator } from './components/LoadingIndicator.js'; import { EditorState, InputPrompt } from './components/InputPrompt.js'; @@ -36,36 +37,40 @@ interface AppProps { export const App = ({ config, settings, cliVersion }: AppProps) => { const { history, addItem, clearItems } = useHistory(); + const [staticKey, setStaticKey] = useState(0); + const refreshStatic = useCallback(() => { + setStaticKey((prev) => prev + 1); + }, [setStaticKey]); + const [startupWarnings, setStartupWarnings] = useState([]); + const [debugMessage, setDebugMessage] = useState(''); const [showHelp, setShowHelp] = useState(false); const [themeError, setThemeError] = useState(null); + const { isThemeDialogOpen, openThemeDialog, handleThemeSelect, handleThemeHighlight, } = useThemeCommand(settings, setThemeError); - - const [staticKey, setStaticKey] = useState(0); - const refreshStatic = useCallback(() => { - setStaticKey((prev) => prev + 1); - }, [setStaticKey]); - - const { - streamingState, - submitQuery, - initError, - debugMessage, - slashCommands, - pendingHistoryItem, - } = useGeminiStream( + const { handleSlashCommand, slashCommands } = useSlashCommandProcessor( addItem, clearItems, refreshStatic, setShowHelp, - config, + setDebugMessage, openThemeDialog, ); + + const { streamingState, submitQuery, initError, pendingHistoryItem } = + useGeminiStream( + addItem, + refreshStatic, + setShowHelp, + config, + setDebugMessage, + handleSlashCommand, + ); const { elapsedTime, currentLoadingPhrase } = useLoadingIndicator(streamingState); diff --git a/packages/cli/src/ui/hooks/atCommandProcessor.ts b/packages/cli/src/ui/hooks/atCommandProcessor.ts index a13a7d36..e0cf65c5 100644 --- a/packages/cli/src/ui/hooks/atCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/atCommandProcessor.ts @@ -24,7 +24,7 @@ interface HandleAtCommandParams { query: string; config: Config; addItem: UseHistoryManagerReturn['addItem']; - setDebugMessage: React.Dispatch>; + onDebugMessage: (message: string) => void; messageId: number; signal: AbortSignal; } @@ -89,7 +89,7 @@ export async function handleAtCommand({ query, config, addItem, - setDebugMessage, + onDebugMessage, messageId: userMessageTimestamp, signal, }: HandleAtCommandParams): Promise { @@ -109,7 +109,7 @@ export async function handleAtCommand({ // If the atPath is just "@", pass the original query to the LLM if (atPath === '@') { - setDebugMessage('Lone @ detected, passing directly to LLM.'); + onDebugMessage('Lone @ detected, passing directly to LLM.'); return { processedQuery: [{ text: query }], shouldProceed: true }; } @@ -144,18 +144,18 @@ export async function handleAtCommand({ const stats = await fs.stat(absolutePath); if (stats.isDirectory()) { pathSpec = pathPart.endsWith('/') ? `${pathPart}**` : `${pathPart}/**`; - setDebugMessage(`Path resolved to directory, using glob: ${pathSpec}`); + onDebugMessage(`Path resolved to directory, using glob: ${pathSpec}`); } else { - setDebugMessage(`Path resolved to file: ${pathSpec}`); + onDebugMessage(`Path resolved to file: ${pathSpec}`); } } catch (error) { // If stat fails (e.g., not found), proceed with original path. // The tool itself will handle the error during execution. if (isNodeError(error) && error.code === 'ENOENT') { - setDebugMessage(`Path not found, proceeding with original: ${pathSpec}`); + onDebugMessage(`Path not found, proceeding with original: ${pathSpec}`); } else { console.error(`Error stating path ${pathPart}:`, error); - setDebugMessage( + onDebugMessage( `Error stating path, proceeding with original: ${pathSpec}`, ); } diff --git a/packages/cli/src/ui/hooks/shellCommandProcessor.ts b/packages/cli/src/ui/hooks/shellCommandProcessor.ts index 16106bb0..d0615ce5 100644 --- a/packages/cli/src/ui/hooks/shellCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/shellCommandProcessor.ts @@ -19,7 +19,7 @@ import { UseHistoryManagerReturn } from './useHistoryManager.js'; export const useShellCommandProcessor = ( addItemToHistory: UseHistoryManagerReturn['addItem'], setStreamingState: React.Dispatch>, - setDebugMessage: React.Dispatch>, + onDebugMessage: (message: string) => void, config: Config, ) => { /** @@ -50,7 +50,7 @@ export const useShellCommandProcessor = ( } const targetDir = config.getTargetDir(); - setDebugMessage( + onDebugMessage( `Executing shell command in ${targetDir}: ${commandToExecute}`, ); const execOptions = { @@ -80,7 +80,7 @@ export const useShellCommandProcessor = ( return true; // Command was initiated }, - [config, setDebugMessage, addItemToHistory, setStreamingState], + [config, onDebugMessage, addItemToHistory, setStreamingState], ); return { handleShellCommand }; diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.ts index 0a0a5fc5..aa7323ca 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.ts @@ -24,7 +24,7 @@ export const useSlashCommandProcessor = ( clearItems: UseHistoryManagerReturn['clearItems'], refreshStatic: () => void, setShowHelp: React.Dispatch>, - setDebugMessage: React.Dispatch>, + onDebugMessage: (message: string) => void, openThemeDialog: () => void, ) => { const slashCommands: SlashCommand[] = useMemo( @@ -34,7 +34,7 @@ export const useSlashCommandProcessor = ( altName: '?', description: 'for help on gemini-code', action: (_value: PartListUnion) => { - setDebugMessage('Opening help.'); + onDebugMessage('Opening help.'); setShowHelp(true); }, }, @@ -42,7 +42,7 @@ export const useSlashCommandProcessor = ( name: 'clear', description: 'clear the screen', action: (_value: PartListUnion) => { - setDebugMessage('Clearing terminal.'); + onDebugMessage('Clearing terminal.'); clearItems(); refreshStatic(); }, @@ -59,12 +59,12 @@ export const useSlashCommandProcessor = ( altName: 'exit', description: '', action: (_value: PartListUnion) => { - setDebugMessage('Quitting. Good-bye.'); + onDebugMessage('Quitting. Good-bye.'); process.exit(0); }, }, ], - [setDebugMessage, setShowHelp, refreshStatic, openThemeDialog, clearItems], + [onDebugMessage, setShowHelp, refreshStatic, openThemeDialog, clearItems], ); /** diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts index 4fcc503b..15239bb1 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.ts +++ b/packages/cli/src/ui/hooks/useGeminiStream.ts @@ -28,7 +28,6 @@ import { HistoryItemWithoutId, } from '../types.js'; import { isAtCommand } from '../utils/commandUtils.js'; -import { useSlashCommandProcessor } from './slashCommandProcessor.js'; import { useShellCommandProcessor } from './shellCommandProcessor.js'; import { handleAtCommand } from './atCommandProcessor.js'; import { findLastSafeSplitPoint } from '../utils/markdownUtilities.js'; @@ -41,17 +40,16 @@ import { UseHistoryManagerReturn } from './useHistoryManager.js'; */ export const useGeminiStream = ( addItem: UseHistoryManagerReturn['addItem'], - clearItems: UseHistoryManagerReturn['clearItems'], refreshStatic: () => void, setShowHelp: React.Dispatch>, config: Config, - openThemeDialog: () => void, + onDebugMessage: (message: string) => void, + handleSlashCommand: (cmd: PartListUnion) => boolean, ) => { const toolRegistry = config.getToolRegistry(); const [streamingState, setStreamingState] = useState( StreamingState.Idle, ); - const [debugMessage, setDebugMessage] = useState(''); const [initError, setInitError] = useState(null); const abortControllerRef = useRef(null); const chatSessionRef = useRef(null); @@ -59,19 +57,10 @@ export const useGeminiStream = ( const [pendingHistoryItemRef, setPendingHistoryItem] = useStateAndRef(null); - const { handleSlashCommand, slashCommands } = useSlashCommandProcessor( - addItem, - clearItems, - refreshStatic, - setShowHelp, - setDebugMessage, - openThemeDialog, - ); - const { handleShellCommand } = useShellCommandProcessor( addItem, setStreamingState, - setDebugMessage, + onDebugMessage, config, ); @@ -109,7 +98,7 @@ export const useGeminiStream = ( if (typeof query === 'string') { const trimmedQuery = query.trim(); - setDebugMessage(`User query: '${trimmedQuery}'`); + onDebugMessage(`User query: '${trimmedQuery}'`); // Handle UI-only commands first if (handleSlashCommand(trimmedQuery)) return; @@ -121,7 +110,7 @@ export const useGeminiStream = ( query: trimmedQuery, config, addItem, - setDebugMessage, + onDebugMessage, messageId: userMessageTimestamp, signal, }); @@ -138,7 +127,7 @@ export const useGeminiStream = ( } if (queryToSendToGemini === null) { - setDebugMessage( + onDebugMessage( 'Query processing resulted in null, not sending to Gemini.', ); return; @@ -558,6 +547,7 @@ export const useGeminiStream = ( setPendingHistoryItem, toolRegistry, refreshStatic, + onDebugMessage, ], ); @@ -565,8 +555,6 @@ export const useGeminiStream = ( streamingState, submitQuery, initError, - debugMessage, - slashCommands, // Normally we would be concerned that the ref would not be up-to-date, but // this isn't a concern as the ref is updated whenever the corresponding // state is updated.