diff --git a/.vscode/launch.json b/.vscode/launch.json index 5a7c6522..b7976fd6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -82,6 +82,46 @@ "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", "skipFiles": ["/**"] + }, + { + "type": "node", + "request": "launch", + "name": "Debug useLoadingIndicator Test (CLI)", + "runtimeExecutable": "npm", + "runtimeArgs": [ + "run", + "test", + "-w", + "packages/cli", + "--", + "--inspect-brk=9229", + "--no-file-parallelism", + "${workspaceFolder}/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts" + ], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "skipFiles": ["/**"] + }, + { + "type": "node", + "request": "launch", + "name": "Debug LoadingIndicator Test (CLI)", + "runtimeExecutable": "npm", + "runtimeArgs": [ + "run", + "test", + "-w", + "packages/cli", + "--", + "--inspect-brk=9229", + "--no-file-parallelism", + "${workspaceFolder}/packages/cli/src/ui/components/LoadingIndicator.test.tsx" + ], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "skipFiles": ["/**"] } ] } diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index 1453487f..5860e36e 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -41,6 +41,10 @@ import { useHistory } from './hooks/useHistoryManager.js'; import process from 'node:process'; import { getErrorMessage, type Config } from '@gemini-code/server'; import { useLogger } from './hooks/useLogger.js'; +import { + StreamingContext, + StreamingContextType, +} from './contexts/StreamingContext.js'; interface AppProps { config: Config; @@ -174,19 +178,15 @@ export const App = ({ handleSlashCommand, shellModeActive, ); - const isPausedForConfirmation = useMemo( - () => - pendingHistoryItems.some( - (item) => - item?.type === 'tool_group' && - item.tools.some((tool) => tool.status === 'Confirming'), - ), - [pendingHistoryItems], - ); - const { elapsedTime, currentLoadingPhrase, shouldShowSpinner } = - useLoadingIndicator(streamingState, isPausedForConfirmation); + const { elapsedTime, currentLoadingPhrase } = + useLoadingIndicator(streamingState); const showAutoAcceptIndicator = useAutoAcceptIndicator({ config }); + const streamingContextValue: StreamingContextType = useMemo( + () => ({ streamingState }), + [streamingState], + ); + const handleFinalSubmit = useCallback( (submittedValue: string) => { const trimmedValue = submittedValue.trim(); @@ -278,177 +278,176 @@ export const App = ({ }, [consoleMessages, config]); return ( - - {/* - * The Static component is an Ink intrinsic in which there can only be 1 per application. - * Because of this restriction we're hacking it slightly by having a 'header' item here to - * ensure that it's statically rendered. - * - * Background on the Static Item: Anything in the Static component is written a single time - * to the console. Think of it like doing a console.log and then never using ANSI codes to - * clear that content ever again. Effectively it has a moving frame that every time new static - * content is set it'll flush content to the terminal and move the area which it's "clearing" - * down a notch. Without Static the area which gets erased and redrawn continuously grows. - */} - -
- - , - ...history.map((h) => ( - - )), - ]} - > - {(item) => item} - - - {pendingHistoryItems.map((item, i) => ( - - ))} - - {showHelp && } - - - {startupWarnings.length > 0 && ( - - {startupWarnings.map((warning, index) => ( - - {warning} - - ))} - - )} - - {isThemeDialogOpen ? ( - - {themeError && ( - - {themeError} - - )} - - - ) : ( - <> - - - - {process.env.GEMINI_SYSTEM_MD && ( - |⌐■_■| - )} - {geminiMdFileCount > 0 && ( - - Using {geminiMdFileCount} GEMINI.md file - {geminiMdFileCount > 1 ? 's' : ''} - - )} - - - {showAutoAcceptIndicator && !shellModeActive && ( - - )} - {shellModeActive && } - - - - {showErrorDetails && ( - - )} - - {isInputActive && ( - + + {/* + * The Static component is an Ink intrinsic in which there can only be 1 per application. + * Because of this restriction we're hacking it slightly by having a 'header' item here to + * ensure that it's statically rendered. + * + * Background on the Static Item: Anything in the Static component is written a single time + * to the console. Think of it like doing a console.log and then never using ANSI codes to + * clear that content ever again. Effectively it has a moving frame that every time new static + * content is set it'll flush content to the terminal and move the area which it's "clearing" + * down a notch. Without Static the area which gets erased and redrawn continuously grows. + */} + +
+ + , + ...history.map((h) => ( + - )} - - )} + )), + ]} + > + {(item) => item} + + + {pendingHistoryItems.map((item, i) => ( + + ))} + + {showHelp && } - {initError && streamingState !== StreamingState.Responding && ( - - {history.find( - (item) => item.type === 'error' && item.text?.includes(initError), - )?.text ? ( - - { - history.find( - (item) => - item.type === 'error' && item.text?.includes(initError), - )?.text - } - - ) : ( - <> - - Initialization Error: {initError} + + {startupWarnings.length > 0 && ( + + {startupWarnings.map((warning, index) => ( + + {warning} - - {' '} - Please check API key and configuration. - - - )} - - )} + ))} + + )} -