refactor: move nested debugmessage and slashcommand hooks outside of useGeminiStream (#341)

This commit is contained in:
Brandon Keiji 2025-05-13 23:55:49 +00:00 committed by GitHub
parent c4c11f1d65
commit d3303fd3a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 42 additions and 49 deletions

View File

@ -10,6 +10,7 @@ import { StreamingState, type HistoryItem } from './types.js';
import { useGeminiStream } from './hooks/useGeminiStream.js'; import { useGeminiStream } from './hooks/useGeminiStream.js';
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js'; import { useLoadingIndicator } from './hooks/useLoadingIndicator.js';
import { useThemeCommand } from './hooks/useThemeCommand.js'; import { useThemeCommand } from './hooks/useThemeCommand.js';
import { useSlashCommandProcessor } from './hooks/slashCommandProcessor.js';
import { Header } from './components/Header.js'; import { Header } from './components/Header.js';
import { LoadingIndicator } from './components/LoadingIndicator.js'; import { LoadingIndicator } from './components/LoadingIndicator.js';
import { EditorState, InputPrompt } from './components/InputPrompt.js'; import { EditorState, InputPrompt } from './components/InputPrompt.js';
@ -36,36 +37,40 @@ interface AppProps {
export const App = ({ config, settings, cliVersion }: AppProps) => { export const App = ({ config, settings, cliVersion }: AppProps) => {
const { history, addItem, clearItems } = useHistory(); const { history, addItem, clearItems } = useHistory();
const [staticKey, setStaticKey] = useState(0);
const refreshStatic = useCallback(() => {
setStaticKey((prev) => prev + 1);
}, [setStaticKey]);
const [startupWarnings, setStartupWarnings] = useState<string[]>([]); const [startupWarnings, setStartupWarnings] = useState<string[]>([]);
const [debugMessage, setDebugMessage] = useState<string>('');
const [showHelp, setShowHelp] = useState<boolean>(false); const [showHelp, setShowHelp] = useState<boolean>(false);
const [themeError, setThemeError] = useState<string | null>(null); const [themeError, setThemeError] = useState<string | null>(null);
const { const {
isThemeDialogOpen, isThemeDialogOpen,
openThemeDialog, openThemeDialog,
handleThemeSelect, handleThemeSelect,
handleThemeHighlight, handleThemeHighlight,
} = useThemeCommand(settings, setThemeError); } = useThemeCommand(settings, setThemeError);
const { handleSlashCommand, slashCommands } = useSlashCommandProcessor(
const [staticKey, setStaticKey] = useState(0);
const refreshStatic = useCallback(() => {
setStaticKey((prev) => prev + 1);
}, [setStaticKey]);
const {
streamingState,
submitQuery,
initError,
debugMessage,
slashCommands,
pendingHistoryItem,
} = useGeminiStream(
addItem, addItem,
clearItems, clearItems,
refreshStatic, refreshStatic,
setShowHelp, setShowHelp,
config, setDebugMessage,
openThemeDialog, openThemeDialog,
); );
const { streamingState, submitQuery, initError, pendingHistoryItem } =
useGeminiStream(
addItem,
refreshStatic,
setShowHelp,
config,
setDebugMessage,
handleSlashCommand,
);
const { elapsedTime, currentLoadingPhrase } = const { elapsedTime, currentLoadingPhrase } =
useLoadingIndicator(streamingState); useLoadingIndicator(streamingState);

View File

@ -24,7 +24,7 @@ interface HandleAtCommandParams {
query: string; query: string;
config: Config; config: Config;
addItem: UseHistoryManagerReturn['addItem']; addItem: UseHistoryManagerReturn['addItem'];
setDebugMessage: React.Dispatch<React.SetStateAction<string>>; onDebugMessage: (message: string) => void;
messageId: number; messageId: number;
signal: AbortSignal; signal: AbortSignal;
} }
@ -89,7 +89,7 @@ export async function handleAtCommand({
query, query,
config, config,
addItem, addItem,
setDebugMessage, onDebugMessage,
messageId: userMessageTimestamp, messageId: userMessageTimestamp,
signal, signal,
}: HandleAtCommandParams): Promise<HandleAtCommandResult> { }: HandleAtCommandParams): Promise<HandleAtCommandResult> {
@ -109,7 +109,7 @@ export async function handleAtCommand({
// If the atPath is just "@", pass the original query to the LLM // If the atPath is just "@", pass the original query to the LLM
if (atPath === '@') { if (atPath === '@') {
setDebugMessage('Lone @ detected, passing directly to LLM.'); onDebugMessage('Lone @ detected, passing directly to LLM.');
return { processedQuery: [{ text: query }], shouldProceed: true }; return { processedQuery: [{ text: query }], shouldProceed: true };
} }
@ -144,18 +144,18 @@ export async function handleAtCommand({
const stats = await fs.stat(absolutePath); const stats = await fs.stat(absolutePath);
if (stats.isDirectory()) { if (stats.isDirectory()) {
pathSpec = pathPart.endsWith('/') ? `${pathPart}**` : `${pathPart}/**`; pathSpec = pathPart.endsWith('/') ? `${pathPart}**` : `${pathPart}/**`;
setDebugMessage(`Path resolved to directory, using glob: ${pathSpec}`); onDebugMessage(`Path resolved to directory, using glob: ${pathSpec}`);
} else { } else {
setDebugMessage(`Path resolved to file: ${pathSpec}`); onDebugMessage(`Path resolved to file: ${pathSpec}`);
} }
} catch (error) { } catch (error) {
// If stat fails (e.g., not found), proceed with original path. // If stat fails (e.g., not found), proceed with original path.
// The tool itself will handle the error during execution. // The tool itself will handle the error during execution.
if (isNodeError(error) && error.code === 'ENOENT') { if (isNodeError(error) && error.code === 'ENOENT') {
setDebugMessage(`Path not found, proceeding with original: ${pathSpec}`); onDebugMessage(`Path not found, proceeding with original: ${pathSpec}`);
} else { } else {
console.error(`Error stating path ${pathPart}:`, error); console.error(`Error stating path ${pathPart}:`, error);
setDebugMessage( onDebugMessage(
`Error stating path, proceeding with original: ${pathSpec}`, `Error stating path, proceeding with original: ${pathSpec}`,
); );
} }

View File

@ -19,7 +19,7 @@ import { UseHistoryManagerReturn } from './useHistoryManager.js';
export const useShellCommandProcessor = ( export const useShellCommandProcessor = (
addItemToHistory: UseHistoryManagerReturn['addItem'], addItemToHistory: UseHistoryManagerReturn['addItem'],
setStreamingState: React.Dispatch<React.SetStateAction<StreamingState>>, setStreamingState: React.Dispatch<React.SetStateAction<StreamingState>>,
setDebugMessage: React.Dispatch<React.SetStateAction<string>>, onDebugMessage: (message: string) => void,
config: Config, config: Config,
) => { ) => {
/** /**
@ -50,7 +50,7 @@ export const useShellCommandProcessor = (
} }
const targetDir = config.getTargetDir(); const targetDir = config.getTargetDir();
setDebugMessage( onDebugMessage(
`Executing shell command in ${targetDir}: ${commandToExecute}`, `Executing shell command in ${targetDir}: ${commandToExecute}`,
); );
const execOptions = { const execOptions = {
@ -80,7 +80,7 @@ export const useShellCommandProcessor = (
return true; // Command was initiated return true; // Command was initiated
}, },
[config, setDebugMessage, addItemToHistory, setStreamingState], [config, onDebugMessage, addItemToHistory, setStreamingState],
); );
return { handleShellCommand }; return { handleShellCommand };

View File

@ -24,7 +24,7 @@ export const useSlashCommandProcessor = (
clearItems: UseHistoryManagerReturn['clearItems'], clearItems: UseHistoryManagerReturn['clearItems'],
refreshStatic: () => void, refreshStatic: () => void,
setShowHelp: React.Dispatch<React.SetStateAction<boolean>>, setShowHelp: React.Dispatch<React.SetStateAction<boolean>>,
setDebugMessage: React.Dispatch<React.SetStateAction<string>>, onDebugMessage: (message: string) => void,
openThemeDialog: () => void, openThemeDialog: () => void,
) => { ) => {
const slashCommands: SlashCommand[] = useMemo( const slashCommands: SlashCommand[] = useMemo(
@ -34,7 +34,7 @@ export const useSlashCommandProcessor = (
altName: '?', altName: '?',
description: 'for help on gemini-code', description: 'for help on gemini-code',
action: (_value: PartListUnion) => { action: (_value: PartListUnion) => {
setDebugMessage('Opening help.'); onDebugMessage('Opening help.');
setShowHelp(true); setShowHelp(true);
}, },
}, },
@ -42,7 +42,7 @@ export const useSlashCommandProcessor = (
name: 'clear', name: 'clear',
description: 'clear the screen', description: 'clear the screen',
action: (_value: PartListUnion) => { action: (_value: PartListUnion) => {
setDebugMessage('Clearing terminal.'); onDebugMessage('Clearing terminal.');
clearItems(); clearItems();
refreshStatic(); refreshStatic();
}, },
@ -59,12 +59,12 @@ export const useSlashCommandProcessor = (
altName: 'exit', altName: 'exit',
description: '', description: '',
action: (_value: PartListUnion) => { action: (_value: PartListUnion) => {
setDebugMessage('Quitting. Good-bye.'); onDebugMessage('Quitting. Good-bye.');
process.exit(0); process.exit(0);
}, },
}, },
], ],
[setDebugMessage, setShowHelp, refreshStatic, openThemeDialog, clearItems], [onDebugMessage, setShowHelp, refreshStatic, openThemeDialog, clearItems],
); );
/** /**

View File

@ -28,7 +28,6 @@ import {
HistoryItemWithoutId, HistoryItemWithoutId,
} from '../types.js'; } from '../types.js';
import { isAtCommand } from '../utils/commandUtils.js'; import { isAtCommand } from '../utils/commandUtils.js';
import { useSlashCommandProcessor } from './slashCommandProcessor.js';
import { useShellCommandProcessor } from './shellCommandProcessor.js'; import { useShellCommandProcessor } from './shellCommandProcessor.js';
import { handleAtCommand } from './atCommandProcessor.js'; import { handleAtCommand } from './atCommandProcessor.js';
import { findLastSafeSplitPoint } from '../utils/markdownUtilities.js'; import { findLastSafeSplitPoint } from '../utils/markdownUtilities.js';
@ -41,17 +40,16 @@ import { UseHistoryManagerReturn } from './useHistoryManager.js';
*/ */
export const useGeminiStream = ( export const useGeminiStream = (
addItem: UseHistoryManagerReturn['addItem'], addItem: UseHistoryManagerReturn['addItem'],
clearItems: UseHistoryManagerReturn['clearItems'],
refreshStatic: () => void, refreshStatic: () => void,
setShowHelp: React.Dispatch<React.SetStateAction<boolean>>, setShowHelp: React.Dispatch<React.SetStateAction<boolean>>,
config: Config, config: Config,
openThemeDialog: () => void, onDebugMessage: (message: string) => void,
handleSlashCommand: (cmd: PartListUnion) => boolean,
) => { ) => {
const toolRegistry = config.getToolRegistry(); const toolRegistry = config.getToolRegistry();
const [streamingState, setStreamingState] = useState<StreamingState>( const [streamingState, setStreamingState] = useState<StreamingState>(
StreamingState.Idle, StreamingState.Idle,
); );
const [debugMessage, setDebugMessage] = useState<string>('');
const [initError, setInitError] = useState<string | null>(null); const [initError, setInitError] = useState<string | null>(null);
const abortControllerRef = useRef<AbortController | null>(null); const abortControllerRef = useRef<AbortController | null>(null);
const chatSessionRef = useRef<Chat | null>(null); const chatSessionRef = useRef<Chat | null>(null);
@ -59,19 +57,10 @@ export const useGeminiStream = (
const [pendingHistoryItemRef, setPendingHistoryItem] = const [pendingHistoryItemRef, setPendingHistoryItem] =
useStateAndRef<HistoryItemWithoutId | null>(null); useStateAndRef<HistoryItemWithoutId | null>(null);
const { handleSlashCommand, slashCommands } = useSlashCommandProcessor(
addItem,
clearItems,
refreshStatic,
setShowHelp,
setDebugMessage,
openThemeDialog,
);
const { handleShellCommand } = useShellCommandProcessor( const { handleShellCommand } = useShellCommandProcessor(
addItem, addItem,
setStreamingState, setStreamingState,
setDebugMessage, onDebugMessage,
config, config,
); );
@ -109,7 +98,7 @@ export const useGeminiStream = (
if (typeof query === 'string') { if (typeof query === 'string') {
const trimmedQuery = query.trim(); const trimmedQuery = query.trim();
setDebugMessage(`User query: '${trimmedQuery}'`); onDebugMessage(`User query: '${trimmedQuery}'`);
// Handle UI-only commands first // Handle UI-only commands first
if (handleSlashCommand(trimmedQuery)) return; if (handleSlashCommand(trimmedQuery)) return;
@ -121,7 +110,7 @@ export const useGeminiStream = (
query: trimmedQuery, query: trimmedQuery,
config, config,
addItem, addItem,
setDebugMessage, onDebugMessage,
messageId: userMessageTimestamp, messageId: userMessageTimestamp,
signal, signal,
}); });
@ -138,7 +127,7 @@ export const useGeminiStream = (
} }
if (queryToSendToGemini === null) { if (queryToSendToGemini === null) {
setDebugMessage( onDebugMessage(
'Query processing resulted in null, not sending to Gemini.', 'Query processing resulted in null, not sending to Gemini.',
); );
return; return;
@ -558,6 +547,7 @@ export const useGeminiStream = (
setPendingHistoryItem, setPendingHistoryItem,
toolRegistry, toolRegistry,
refreshStatic, refreshStatic,
onDebugMessage,
], ],
); );
@ -565,8 +555,6 @@ export const useGeminiStream = (
streamingState, streamingState,
submitQuery, submitQuery,
initError, initError,
debugMessage,
slashCommands,
// Normally we would be concerned that the ref would not be up-to-date, but // 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 // this isn't a concern as the ref is updated whenever the corresponding
// state is updated. // state is updated.