2025-04-18 17:44:24 -07:00
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
2025-05-15 00:19:41 -07:00
import { useCallback , useEffect , useMemo , useState , useRef } from 'react' ;
2025-05-22 10:36:44 -07:00
import {
Box ,
DOMElement ,
measureElement ,
Static ,
Text ,
2025-06-13 09:59:09 -07:00
useStdin ,
2025-06-21 11:11:42 -07:00
useStdout ,
2025-05-22 10:36:44 -07:00
useInput ,
type Key as InkKeyType ,
} from 'ink' ;
2025-05-23 22:51:47 -07:00
import { StreamingState , type HistoryItem , MessageType } from './types.js' ;
2025-05-15 22:56:03 -07:00
import { useTerminalSize } from './hooks/useTerminalSize.js' ;
2025-04-15 21:41:08 -07:00
import { useGeminiStream } from './hooks/useGeminiStream.js' ;
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js' ;
2025-04-22 18:57:47 -07:00
import { useThemeCommand } from './hooks/useThemeCommand.js' ;
2025-06-19 16:52:22 -07:00
import { useAuthCommand } from './hooks/useAuthCommand.js' ;
2025-06-12 02:21:54 +01:00
import { useEditorSettings } from './hooks/useEditorSettings.js' ;
2025-05-13 23:55:49 +00:00
import { useSlashCommandProcessor } from './hooks/slashCommandProcessor.js' ;
2025-05-17 21:25:28 -07:00
import { useAutoAcceptIndicator } from './hooks/useAutoAcceptIndicator.js' ;
2025-05-23 22:51:47 -07:00
import { useConsoleMessages } from './hooks/useConsoleMessages.js' ;
2025-04-18 19:09:41 -04:00
import { Header } from './components/Header.js' ;
import { LoadingIndicator } from './components/LoadingIndicator.js' ;
2025-05-17 21:25:28 -07:00
import { AutoAcceptIndicator } from './components/AutoAcceptIndicator.js' ;
2025-05-18 22:16:57 -07:00
import { ShellModeIndicator } from './components/ShellModeIndicator.js' ;
2025-05-20 16:50:32 -07:00
import { InputPrompt } from './components/InputPrompt.js' ;
2025-04-18 19:09:41 -04:00
import { Footer } from './components/Footer.js' ;
2025-04-22 18:57:47 -07:00
import { ThemeDialog } from './components/ThemeDialog.js' ;
2025-06-19 16:52:22 -07:00
import { AuthDialog } from './components/AuthDialog.js' ;
2025-06-20 10:46:41 -07:00
import { AuthInProgress } from './components/AuthInProgress.js' ;
2025-06-12 02:21:54 +01:00
import { EditorSettingsDialog } from './components/EditorSettingsDialog.js' ;
2025-07-27 02:00:26 -04:00
import { ShellConfirmationDialog } from './components/ShellConfirmationDialog.js' ;
2025-04-19 12:38:09 -04:00
import { Colors } from './colors.js' ;
2025-07-28 11:13:46 -07:00
import { loadHierarchicalGeminiMemory } from '../config/config.js' ;
import { LoadedSettings } from '../config/settings.js' ;
2025-04-22 18:57:47 -07:00
import { Tips } from './components/Tips.js' ;
2025-07-12 15:42:47 -07:00
import { ConsolePatcher } from './utils/ConsolePatcher.js' ;
import { registerCleanup } from '../utils/cleanup.js' ;
2025-05-22 10:36:44 -07:00
import { DetailedMessagesDisplay } from './components/DetailedMessagesDisplay.js' ;
2025-04-25 17:11:08 -07:00
import { HistoryItemDisplay } from './components/HistoryItemDisplay.js' ;
2025-06-01 15:48:48 -07:00
import { ContextSummaryDisplay } from './components/ContextSummaryDisplay.js' ;
2025-07-25 14:50:34 +00:00
import { IDEContextDetailDisplay } from './components/IDEContextDetailDisplay.js' ;
2025-05-06 16:20:28 -07:00
import { useHistory } from './hooks/useHistoryManager.js' ;
2025-05-21 07:36:22 +00:00
import process from 'node:process' ;
2025-05-31 12:49:28 -07:00
import {
getErrorMessage ,
type Config ,
2025-06-13 09:19:08 -07:00
getAllGeminiMdFilenames ,
2025-06-02 22:05:45 +02:00
ApprovalMode ,
2025-06-12 02:21:54 +01:00
isEditorAvailable ,
EditorType ,
2025-07-12 02:40:25 +05:30
FlashFallbackEvent ,
logFlashFallback ,
2025-07-15 21:13:30 -07:00
AuthType ,
2025-07-28 11:03:22 -04:00
type IdeContext ,
2025-07-18 18:14:46 -04:00
ideContext ,
2025-06-25 05:41:11 -07:00
} from '@google/gemini-cli-core' ;
2025-06-19 16:52:22 -07:00
import { validateAuthMethod } from '../config/auth.js' ;
2025-05-21 13:31:18 -07:00
import { useLogger } from './hooks/useLogger.js' ;
2025-05-28 19:46:08 +00:00
import { StreamingContext } from './contexts/StreamingContext.js' ;
2025-06-15 11:15:53 -07:00
import {
SessionStatsProvider ,
useSessionStats ,
} from './contexts/SessionContext.js' ;
2025-05-28 23:30:05 +00:00
import { useGitBranchName } from './hooks/useGitBranchName.js' ;
2025-07-17 20:45:42 -04:00
import { useFocus } from './hooks/useFocus.js' ;
2025-06-27 10:57:32 -07:00
import { useBracketedPaste } from './hooks/useBracketedPaste.js' ;
2025-06-13 09:59:09 -07:00
import { useTextBuffer } from './components/shared/text-buffer.js' ;
2025-07-25 15:36:42 -07:00
import { useVimMode , VimModeProvider } from './contexts/VimModeContext.js' ;
import { useVim } from './hooks/vim.js' ;
2025-06-13 09:59:09 -07:00
import * as fs from 'fs' ;
2025-06-17 08:24:07 -07:00
import { UpdateNotification } from './components/UpdateNotification.js' ;
2025-07-09 10:18:15 -04:00
import {
isProQuotaExceededError ,
isGenericQuotaExceededError ,
2025-07-09 13:55:56 -04:00
UserTierId ,
2025-07-09 10:18:15 -04:00
} from '@google/gemini-cli-core' ;
2025-07-28 17:56:52 -07:00
import { UpdateObject } from './utils/updateCheck.js' ;
2025-06-21 11:11:42 -07:00
import ansiEscapes from 'ansi-escapes' ;
2025-06-22 00:54:10 +00:00
import { OverflowProvider } from './contexts/OverflowContext.js' ;
import { ShowMoreLines } from './components/ShowMoreLines.js' ;
2025-06-27 12:07:38 -07:00
import { PrivacyNotice } from './privacy/PrivacyNotice.js' ;
2025-07-28 17:56:52 -07:00
import { setUpdateHandler } from '../utils/handleAutoUpdate.js' ;
2025-07-25 17:35:26 -07:00
import { appEvents , AppEvent } from '../utils/events.js' ;
2025-04-18 21:55:02 +01:00
2025-06-13 09:59:09 -07:00
const CTRL_EXIT_PROMPT_DURATION_MS = 1000 ;
2025-05-30 19:36:52 -07:00
2025-04-15 21:41:08 -07:00
interface AppProps {
2025-04-19 19:45:42 +01:00
config : Config ;
2025-05-01 10:34:07 -07:00
settings : LoadedSettings ;
2025-05-14 00:12:04 +00:00
startupWarnings? : string [ ] ;
2025-07-11 13:43:57 -07:00
version : string ;
2025-04-15 21:41:08 -07:00
}
2025-06-08 18:01:02 -04:00
export const AppWrapper = ( props : AppProps ) = > (
2025-06-09 20:25:37 -04:00
< SessionStatsProvider >
2025-07-25 15:36:42 -07:00
< VimModeProvider settings = { props . settings } >
< App { ...props } / >
< / VimModeProvider >
2025-06-09 20:25:37 -04:00
< / SessionStatsProvider >
2025-06-08 18:01:02 -04:00
) ;
2025-07-28 11:13:46 -07:00
const App = ( { config , settings , startupWarnings = [ ] , version } : AppProps ) = > {
2025-07-17 20:45:42 -04:00
const isFocused = useFocus ( ) ;
2025-06-27 10:57:32 -07:00
useBracketedPaste ( ) ;
2025-07-28 17:56:52 -07:00
const [ updateInfo , setUpdateInfo ] = useState < UpdateObject | null > ( null ) ;
2025-06-21 11:11:42 -07:00
const { stdout } = useStdout ( ) ;
2025-07-28 11:13:46 -07:00
const nightly = version . includes ( 'nightly' ) ;
2025-07-28 17:56:52 -07:00
const { history , addItem , clearItems , loadHistory } = useHistory ( ) ;
2025-06-17 08:24:07 -07:00
useEffect ( ( ) = > {
2025-07-28 17:56:52 -07:00
const cleanup = setUpdateHandler ( addItem , setUpdateInfo ) ;
return cleanup ;
} , [ addItem ] ) ;
2025-06-17 08:24:07 -07:00
2025-05-23 22:51:47 -07:00
const {
consoleMessages ,
handleNewMessage ,
clearConsoleMessages : clearConsoleMessagesState ,
} = useConsoleMessages ( ) ;
2025-07-12 15:42:47 -07:00
useEffect ( ( ) = > {
const consolePatcher = new ConsolePatcher ( {
onNewMessage : handleNewMessage ,
debugMode : config.getDebugMode ( ) ,
} ) ;
consolePatcher . patch ( ) ;
registerCleanup ( consolePatcher . cleanup ) ;
} , [ handleNewMessage , config ] ) ;
2025-06-15 11:15:53 -07:00
const { stats : sessionStats } = useSessionStats ( ) ;
2025-05-15 00:19:41 -07:00
const [ staticNeedsRefresh , setStaticNeedsRefresh ] = useState ( false ) ;
2025-05-13 23:55:49 +00:00
const [ staticKey , setStaticKey ] = useState ( 0 ) ;
const refreshStatic = useCallback ( ( ) = > {
2025-06-21 11:11:42 -07:00
stdout . write ( ansiEscapes . clearTerminal ) ;
2025-05-13 23:55:49 +00:00
setStaticKey ( ( prev ) = > prev + 1 ) ;
2025-06-21 11:11:42 -07:00
} , [ setStaticKey , stdout ] ) ;
2025-05-13 23:55:49 +00:00
2025-05-21 13:31:18 -07:00
const [ geminiMdFileCount , setGeminiMdFileCount ] = useState < number > ( 0 ) ;
2025-05-13 23:55:49 +00:00
const [ debugMessage , setDebugMessage ] = useState < string > ( '' ) ;
2025-05-08 20:56:46 -07:00
const [ themeError , setThemeError ] = useState < string | null > ( null ) ;
2025-06-19 16:52:22 -07:00
const [ authError , setAuthError ] = useState < string | null > ( null ) ;
2025-06-12 02:21:54 +01:00
const [ editorError , setEditorError ] = useState < string | null > ( null ) ;
2025-05-15 22:56:03 -07:00
const [ footerHeight , setFooterHeight ] = useState < number > ( 0 ) ;
2025-05-17 21:57:27 -07:00
const [ corgiMode , setCorgiMode ] = useState ( false ) ;
2025-06-24 18:48:55 -04:00
const [ currentModel , setCurrentModel ] = useState ( config . getModel ( ) ) ;
2025-05-18 01:18:32 -07:00
const [ shellModeActive , setShellModeActive ] = useState ( false ) ;
2025-05-22 10:36:44 -07:00
const [ showErrorDetails , setShowErrorDetails ] = useState < boolean > ( false ) ;
2025-06-07 18:30:56 -04:00
const [ showToolDescriptions , setShowToolDescriptions ] =
useState < boolean > ( false ) ;
2025-07-25 14:50:34 +00:00
const [ showIDEContextDetail , setShowIDEContextDetail ] =
useState < boolean > ( false ) ;
2025-05-30 19:36:52 -07:00
const [ ctrlCPressedOnce , setCtrlCPressedOnce ] = useState ( false ) ;
2025-06-11 20:08:32 -04:00
const [ quittingMessages , setQuittingMessages ] = useState <
HistoryItem [ ] | null
> ( null ) ;
2025-05-30 19:36:52 -07:00
const ctrlCTimerRef = useRef < NodeJS.Timeout | null > ( null ) ;
2025-06-13 09:59:09 -07:00
const [ ctrlDPressedOnce , setCtrlDPressedOnce ] = useState ( false ) ;
const ctrlDTimerRef = useRef < NodeJS.Timeout | null > ( null ) ;
2025-06-19 20:17:23 +00:00
const [ constrainHeight , setConstrainHeight ] = useState < boolean > ( true ) ;
2025-06-27 12:07:38 -07:00
const [ showPrivacyNotice , setShowPrivacyNotice ] = useState < boolean > ( false ) ;
2025-07-09 13:55:56 -04:00
const [ modelSwitchedFromQuotaError , setModelSwitchedFromQuotaError ] =
useState < boolean > ( false ) ;
2025-07-11 11:25:30 -04:00
const [ userTier , setUserTier ] = useState < UserTierId | undefined > ( undefined ) ;
2025-07-28 11:03:22 -04:00
const [ ideContextState , setIdeContextState ] = useState <
IdeContext | undefined
> ( ) ;
2025-07-27 02:00:26 -04:00
const [ isProcessing , setIsProcessing ] = useState < boolean > ( false ) ;
2025-07-18 18:14:46 -04:00
useEffect ( ( ) = > {
2025-07-28 11:03:22 -04:00
const unsubscribe = ideContext . subscribeToIdeContext ( setIdeContextState ) ;
2025-07-18 18:14:46 -04:00
// Set the initial value
2025-07-28 11:03:22 -04:00
setIdeContextState ( ideContext . getIdeContext ( ) ) ;
2025-07-18 18:14:46 -04:00
return unsubscribe ;
} , [ ] ) ;
2025-06-27 12:07:38 -07:00
2025-07-25 17:35:26 -07:00
useEffect ( ( ) = > {
const openDebugConsole = ( ) = > {
setShowErrorDetails ( true ) ;
setConstrainHeight ( false ) ; // Make sure the user sees the full message.
} ;
appEvents . on ( AppEvent . OpenDebugConsole , openDebugConsole ) ;
const logErrorHandler = ( errorMessage : unknown ) = > {
handleNewMessage ( {
type : 'error' ,
content : String ( errorMessage ) ,
count : 1 ,
} ) ;
} ;
appEvents . on ( AppEvent . LogError , logErrorHandler ) ;
return ( ) = > {
appEvents . off ( AppEvent . OpenDebugConsole , openDebugConsole ) ;
appEvents . off ( AppEvent . LogError , logErrorHandler ) ;
} ;
} , [ handleNewMessage ] ) ;
2025-06-27 12:07:38 -07:00
const openPrivacyNotice = useCallback ( ( ) = > {
setShowPrivacyNotice ( true ) ;
} , [ ] ) ;
2025-07-11 16:52:56 -07:00
const initialPromptSubmitted = useRef ( false ) ;
2025-05-22 10:36:44 -07:00
const errorCount = useMemo (
2025-07-25 17:35:26 -07:00
( ) = >
consoleMessages
. filter ( ( msg ) = > msg . type === 'error' )
. reduce ( ( total , msg ) = > total + msg . count , 0 ) ,
2025-05-22 10:36:44 -07:00
[ consoleMessages ] ,
) ;
2025-05-30 19:36:52 -07:00
2025-04-30 22:26:28 +00:00
const {
isThemeDialogOpen ,
openThemeDialog ,
handleThemeSelect ,
handleThemeHighlight ,
2025-06-06 07:55:28 -07:00
} = useThemeCommand ( settings , setThemeError , addItem ) ;
2025-05-14 12:37:17 -07:00
2025-06-19 16:52:22 -07:00
const {
isAuthDialogOpen ,
openAuthDialog ,
handleAuthSelect ,
2025-06-20 10:46:41 -07:00
isAuthenticating ,
cancelAuthentication ,
2025-06-19 16:52:22 -07:00
} = useAuthCommand ( settings , setAuthError , config ) ;
useEffect ( ( ) = > {
2025-08-01 11:49:03 -07:00
if ( settings . merged . selectedAuthType && ! settings . merged . useExternalAuth ) {
2025-06-19 16:52:22 -07:00
const error = validateAuthMethod ( settings . merged . selectedAuthType ) ;
if ( error ) {
setAuthError ( error ) ;
openAuthDialog ( ) ;
}
}
2025-08-01 11:49:03 -07:00
} , [
settings . merged . selectedAuthType ,
settings . merged . useExternalAuth ,
openAuthDialog ,
setAuthError ,
] ) ;
2025-06-19 16:52:22 -07:00
2025-07-11 11:25:30 -04:00
// Sync user tier from config when authentication changes
useEffect ( ( ) = > {
// Only sync when not currently authenticating
if ( ! isAuthenticating ) {
2025-07-21 13:44:43 -07:00
setUserTier ( config . getGeminiClient ( ) ? . getUserTier ( ) ) ;
2025-07-11 11:25:30 -04:00
}
2025-07-21 13:44:43 -07:00
} , [ config , isAuthenticating ] ) ;
2025-07-11 11:25:30 -04:00
2025-06-12 02:21:54 +01:00
const {
isEditorDialogOpen ,
openEditorDialog ,
handleEditorSelect ,
exitEditorDialog ,
} = useEditorSettings ( settings , setEditorError , addItem ) ;
2025-06-07 18:30:56 -04:00
const toggleCorgiMode = useCallback ( ( ) = > {
setCorgiMode ( ( prev ) = > ! prev ) ;
} , [ ] ) ;
2025-05-14 15:19:45 -07:00
2025-05-14 12:37:17 -07:00
const performMemoryRefresh = useCallback ( async ( ) = > {
addItem (
{
type : MessageType . INFO ,
2025-06-21 12:15:43 -07:00
text : 'Refreshing hierarchical memory (GEMINI.md or other context files)...' ,
2025-05-14 12:37:17 -07:00
} ,
Date . now ( ) ,
) ;
try {
2025-05-14 15:19:45 -07:00
const { memoryContent , fileCount } = await loadHierarchicalGeminiMemory (
2025-05-14 12:37:17 -07:00
process . cwd ( ) ,
2025-08-06 02:01:01 +09:00
settings . merged . loadMemoryFromIncludeDirectories
? config . getWorkspaceContext ( ) . getDirectories ( )
: [ ] ,
2025-05-14 12:37:17 -07:00
config . getDebugMode ( ) ,
2025-06-14 10:25:34 -04:00
config . getFileService ( ) ,
2025-07-23 14:48:35 -07:00
settings . merged ,
2025-06-22 16:17:05 -07:00
config . getExtensionContextFilePaths ( ) ,
2025-07-31 22:06:50 +05:30
settings . merged . memoryImportFormat || 'tree' , // Use setting or default to 'tree'
2025-07-20 00:55:33 -07:00
config . getFileFilteringOptions ( ) ,
2025-05-14 12:37:17 -07:00
) ;
2025-07-20 00:55:33 -07:00
2025-05-14 15:19:45 -07:00
config . setUserMemory ( memoryContent ) ;
config . setGeminiMdFileCount ( fileCount ) ;
setGeminiMdFileCount ( fileCount ) ;
2025-05-14 12:37:17 -07:00
addItem (
{
type : MessageType . INFO ,
2025-05-14 15:19:45 -07:00
text : ` Memory refreshed successfully. ${ memoryContent . length > 0 ? ` Loaded ${ memoryContent . length } characters from ${ fileCount } file(s). ` : 'No memory content found.' } ` ,
2025-05-14 12:37:17 -07:00
} ,
Date . now ( ) ,
) ;
if ( config . getDebugMode ( ) ) {
console . log (
2025-05-14 15:19:45 -07:00
` [DEBUG] Refreshed memory content in config: ${ memoryContent . substring ( 0 , 200 ) } ... ` ,
2025-05-14 12:37:17 -07:00
) ;
}
} catch ( error ) {
const errorMessage = getErrorMessage ( error ) ;
addItem (
{
type : MessageType . ERROR ,
text : ` Error refreshing memory: ${ errorMessage } ` ,
} ,
Date . now ( ) ,
) ;
console . error ( 'Error refreshing memory:' , error ) ;
}
2025-07-23 14:48:35 -07:00
} , [ config , addItem , settings . merged ] ) ;
2025-05-14 12:37:17 -07:00
2025-06-24 18:48:55 -04:00
// Watch for model changes (e.g., from Flash fallback)
useEffect ( ( ) = > {
const checkModelChange = ( ) = > {
const configModel = config . getModel ( ) ;
if ( configModel !== currentModel ) {
setCurrentModel ( configModel ) ;
}
} ;
// Check immediately and then periodically
checkModelChange ( ) ;
const interval = setInterval ( checkModelChange , 1000 ) ; // Check every second
return ( ) = > clearInterval ( interval ) ;
} , [ config , currentModel ] ) ;
// Set up Flash fallback handler
useEffect ( ( ) = > {
const flashFallbackHandler = async (
currentModel : string ,
fallbackModel : string ,
2025-07-09 10:18:15 -04:00
error? : unknown ,
2025-06-24 18:48:55 -04:00
) : Promise < boolean > = > {
2025-07-09 10:18:15 -04:00
let message : string ;
2025-07-15 21:13:30 -07:00
if (
config . getContentGeneratorConfig ( ) . authType ===
AuthType . LOGIN_WITH_GOOGLE
) {
2025-07-21 17:54:44 -04:00
// Use actual user tier if available; otherwise, default to FREE tier behavior (safe default)
2025-07-15 21:13:30 -07:00
const isPaidTier =
userTier === UserTierId . LEGACY || userTier === UserTierId . STANDARD ;
2025-07-09 13:55:56 -04:00
2025-07-15 21:13:30 -07:00
// Check if this is a Pro quota exceeded error
if ( error && isProQuotaExceededError ( error ) ) {
if ( isPaidTier ) {
message = ` ⚡ You have reached your daily ${ currentModel } quota limit.
2025-07-09 13:55:56 -04:00
⚡ Automatically switching from ${ currentModel } to ${ fallbackModel } for the remainder of this session.
⚡ To continue accessing the ${ currentModel } model today, consider using /auth to switch to using a paid API key from AI Studio at https://aistudio.google.com/apikey ` ;
2025-07-15 21:13:30 -07:00
} else {
message = ` ⚡ You have reached your daily ${ currentModel } quota limit.
2025-07-09 10:18:15 -04:00
⚡ Automatically switching from ${ currentModel } to ${ fallbackModel } for the remainder of this session.
⚡ To increase your limits, upgrade to a Gemini Code Assist Standard or Enterprise plan with higher limits at https://goo.gle/set-up-gemini-code-assist
⚡ Or you can utilize a Gemini API Key. See: https://goo.gle/gemini-cli-docs-auth#gemini-api-key
⚡ You can switch authentication methods by typing /auth ` ;
2025-07-15 21:13:30 -07:00
}
} else if ( error && isGenericQuotaExceededError ( error ) ) {
if ( isPaidTier ) {
message = ` ⚡ You have reached your daily quota limit.
2025-07-09 13:55:56 -04:00
⚡ Automatically switching from ${ currentModel } to ${ fallbackModel } for the remainder of this session.
⚡ To continue accessing the ${ currentModel } model today, consider using /auth to switch to using a paid API key from AI Studio at https://aistudio.google.com/apikey ` ;
2025-07-15 21:13:30 -07:00
} else {
message = ` ⚡ You have reached your daily quota limit.
2025-07-09 10:18:15 -04:00
⚡ Automatically switching from ${ currentModel } to ${ fallbackModel } for the remainder of this session.
⚡ To increase your limits, upgrade to a Gemini Code Assist Standard or Enterprise plan with higher limits at https://goo.gle/set-up-gemini-code-assist
⚡ Or you can utilize a Gemini API Key. See: https://goo.gle/gemini-cli-docs-auth#gemini-api-key
⚡ You can switch authentication methods by typing /auth ` ;
2025-07-15 21:13:30 -07:00
}
} else {
if ( isPaidTier ) {
// Default fallback message for other cases (like consecutive 429s)
message = ` ⚡ Automatically switching from ${ currentModel } to ${ fallbackModel } for faster responses for the remainder of this session.
2025-07-09 13:55:56 -04:00
⚡ Possible reasons for this are that you have received multiple consecutive capacity errors or you have reached your daily ${ currentModel } quota limit
⚡ To continue accessing the ${ currentModel } model today, consider using /auth to switch to using a paid API key from AI Studio at https://aistudio.google.com/apikey ` ;
2025-07-15 21:13:30 -07:00
} else {
// Default fallback message for other cases (like consecutive 429s)
message = ` ⚡ Automatically switching from ${ currentModel } to ${ fallbackModel } for faster responses for the remainder of this session.
2025-07-09 13:55:56 -04:00
⚡ Possible reasons for this are that you have received multiple consecutive capacity errors or you have reached your daily ${ currentModel } quota limit
⚡ To increase your limits, upgrade to a Gemini Code Assist Standard or Enterprise plan with higher limits at https://goo.gle/set-up-gemini-code-assist
⚡ Or you can utilize a Gemini API Key. See: https://goo.gle/gemini-cli-docs-auth#gemini-api-key
⚡ You can switch authentication methods by typing /auth ` ;
2025-07-15 21:13:30 -07:00
}
2025-07-09 13:55:56 -04:00
}
2025-07-09 10:18:15 -04:00
2025-07-15 21:13:30 -07:00
// Add message to UI history
addItem (
{
type : MessageType . INFO ,
text : message ,
} ,
Date . now ( ) ,
) ;
// Set the flag to prevent tool continuation
setModelSwitchedFromQuotaError ( true ) ;
// Set global quota error flag to prevent Flash model calls
config . setQuotaErrorOccurred ( true ) ;
}
2025-07-09 13:55:56 -04:00
// Switch model for future use but return false to stop current retry
config . setModel ( fallbackModel ) ;
2025-07-28 15:55:50 -04:00
config . setFallbackMode ( true ) ;
2025-07-12 02:40:25 +05:30
logFlashFallback (
config ,
new FlashFallbackEvent ( config . getContentGeneratorConfig ( ) . authType ! ) ,
) ;
2025-07-09 13:55:56 -04:00
return false ; // Don't continue with current prompt
2025-06-24 18:48:55 -04:00
} ;
config . setFlashFallbackHandler ( flashFallbackHandler ) ;
2025-07-11 11:25:30 -04:00
} , [ config , addItem , userTier ] ) ;
2025-06-24 18:48:55 -04:00
2025-07-25 15:36:42 -07:00
// Terminal and UI setup
const { rows : terminalHeight , columns : terminalWidth } = useTerminalSize ( ) ;
const { stdin , setRawMode } = useStdin ( ) ;
const isInitialMount = useRef ( true ) ;
const widthFraction = 0.9 ;
const inputWidth = Math . max (
20 ,
Math . floor ( terminalWidth * widthFraction ) - 3 ,
) ;
const suggestionsWidth = Math . max ( 60 , Math . floor ( terminalWidth * 0.8 ) ) ;
// Utility callbacks
const isValidPath = useCallback ( ( filePath : string ) : boolean = > {
try {
return fs . existsSync ( filePath ) && fs . statSync ( filePath ) . isFile ( ) ;
} catch ( _e ) {
return false ;
}
} , [ ] ) ;
const getPreferredEditor = useCallback ( ( ) = > {
const editorType = settings . merged . preferredEditor ;
const isValidEditor = isEditorAvailable ( editorType ) ;
if ( ! isValidEditor ) {
openEditorDialog ( ) ;
return ;
}
return editorType as EditorType ;
} , [ settings , openEditorDialog ] ) ;
const onAuthError = useCallback ( ( ) = > {
setAuthError ( 'reauth required' ) ;
openAuthDialog ( ) ;
} , [ openAuthDialog , setAuthError ] ) ;
// Core hooks and processors
const {
vimEnabled : vimModeEnabled ,
vimMode ,
toggleVimEnabled ,
} = useVimMode ( ) ;
2025-06-13 21:21:40 -07:00
const {
handleSlashCommand ,
slashCommands ,
pendingHistoryItems : pendingSlashCommandHistoryItems ,
2025-07-07 16:45:44 -04:00
commandContext ,
2025-07-27 02:00:26 -04:00
shellConfirmationRequest ,
2025-06-13 21:21:40 -07:00
} = useSlashCommandProcessor (
2025-05-20 16:50:32 -07:00
config ,
2025-06-21 12:15:43 -07:00
settings ,
2025-05-06 16:20:28 -07:00
addItem ,
clearItems ,
2025-06-11 15:33:09 -04:00
loadHistory ,
2025-05-05 20:48:34 +00:00
refreshStatic ,
2025-05-13 23:55:49 +00:00
setDebugMessage ,
2025-05-05 20:48:34 +00:00
openThemeDialog ,
2025-06-19 16:52:22 -07:00
openAuthDialog ,
2025-06-12 02:21:54 +01:00
openEditorDialog ,
2025-05-17 21:57:27 -07:00
toggleCorgiMode ,
2025-06-11 20:08:32 -04:00
setQuittingMessages ,
2025-06-27 12:07:38 -07:00
openPrivacyNotice ,
2025-07-25 15:36:42 -07:00
toggleVimEnabled ,
2025-07-27 02:00:26 -04:00
setIsProcessing ,
2025-08-06 02:01:01 +09:00
setGeminiMdFileCount ,
2025-06-07 18:30:56 -04:00
) ;
2025-08-06 15:19:10 -04:00
const buffer = useTextBuffer ( {
initialText : '' ,
viewport : { height : 10 , width : inputWidth } ,
stdin ,
setRawMode ,
isValidPath ,
shellModeActive ,
} ) ;
const [ userMessages , setUserMessages ] = useState < string [ ] > ( [ ] ) ;
const handleUserCancel = useCallback ( ( ) = > {
const lastUserMessage = userMessages . at ( - 1 ) ;
if ( lastUserMessage ) {
buffer . setText ( lastUserMessage ) ;
}
} , [ buffer , userMessages ] ) ;
2025-07-25 15:36:42 -07:00
const {
streamingState ,
submitQuery ,
initError ,
pendingHistoryItems : pendingGeminiHistoryItems ,
thought ,
} = useGeminiStream (
config . getGeminiClient ( ) ,
history ,
addItem ,
config ,
setDebugMessage ,
handleSlashCommand ,
shellModeActive ,
getPreferredEditor ,
onAuthError ,
performMemoryRefresh ,
modelSwitchedFromQuotaError ,
setModelSwitchedFromQuotaError ,
2025-08-05 14:55:54 -07:00
refreshStatic ,
2025-08-06 15:19:10 -04:00
handleUserCancel ,
2025-07-25 15:36:42 -07:00
) ;
2025-06-13 09:59:09 -07:00
2025-07-25 15:36:42 -07:00
// Input handling
const handleFinalSubmit = useCallback (
( submittedValue : string ) = > {
const trimmedValue = submittedValue . trim ( ) ;
if ( trimmedValue . length > 0 ) {
submitQuery ( trimmedValue ) ;
}
} ,
[ submitQuery ] ,
2025-06-13 09:59:09 -07:00
) ;
2025-07-25 15:36:42 -07:00
const { handleInput : vimHandleInput } = useVim ( buffer , handleFinalSubmit ) ;
const pendingHistoryItems = [ . . . pendingSlashCommandHistoryItems ] ;
pendingHistoryItems . push ( . . . pendingGeminiHistoryItems ) ;
const { elapsedTime , currentLoadingPhrase } =
useLoadingIndicator ( streamingState ) ;
const showAutoAcceptIndicator = useAutoAcceptIndicator ( { config } ) ;
2025-06-13 09:59:09 -07:00
const handleExit = useCallback (
(
pressedOnce : boolean ,
setPressedOnce : ( value : boolean ) = > void ,
timerRef : React.MutableRefObject < NodeJS.Timeout | null > ,
) = > {
if ( pressedOnce ) {
if ( timerRef . current ) {
clearTimeout ( timerRef . current ) ;
}
2025-07-17 12:07:10 -04:00
// Directly invoke the central command handler.
handleSlashCommand ( '/quit' ) ;
2025-06-13 09:59:09 -07:00
} else {
setPressedOnce ( true ) ;
timerRef . current = setTimeout ( ( ) = > {
setPressedOnce ( false ) ;
timerRef . current = null ;
} , CTRL_EXIT_PROMPT_DURATION_MS ) ;
}
} ,
2025-07-17 12:07:10 -04:00
[ handleSlashCommand ] ,
2025-06-13 09:59:09 -07:00
) ;
2025-06-07 18:30:56 -04:00
useInput ( ( input : string , key : InkKeyType ) = > {
2025-06-23 05:42:20 +00:00
let enteringConstrainHeightMode = false ;
2025-06-22 20:43:36 +00:00
if ( ! constrainHeight ) {
// Automatically re-enter constrain height mode if the user types
// anything. When constrainHeight==false, the user will experience
// significant flickering so it is best to disable it immediately when
// the user starts interacting with the app.
2025-06-23 05:42:20 +00:00
enteringConstrainHeightMode = true ;
2025-06-22 20:43:36 +00:00
setConstrainHeight ( true ) ;
}
2025-06-07 18:30:56 -04:00
if ( key . ctrl && input === 'o' ) {
setShowErrorDetails ( ( prev ) = > ! prev ) ;
} else if ( key . ctrl && input === 't' ) {
const newValue = ! showToolDescriptions ;
setShowToolDescriptions ( newValue ) ;
const mcpServers = config . getMcpServers ( ) ;
if ( Object . keys ( mcpServers || { } ) . length > 0 ) {
handleSlashCommand ( newValue ? '/mcp desc' : '/mcp nodesc' ) ;
}
2025-07-30 22:36:24 +00:00
} else if (
key . ctrl &&
input === 'e' &&
config . getIdeMode ( ) &&
ideContextState
) {
2025-07-25 14:50:34 +00:00
setShowIDEContextDetail ( ( prev ) = > ! prev ) ;
2025-06-07 18:30:56 -04:00
} else if ( key . ctrl && ( input === 'c' || input === 'C' ) ) {
2025-06-13 09:59:09 -07:00
handleExit ( ctrlCPressedOnce , setCtrlCPressedOnce , ctrlCTimerRef ) ;
} else if ( key . ctrl && ( input === 'd' || input === 'D' ) ) {
if ( buffer . text . length > 0 ) {
// Do nothing if there is text in the input.
return ;
2025-06-07 18:30:56 -04:00
}
2025-06-13 09:59:09 -07:00
handleExit ( ctrlDPressedOnce , setCtrlDPressedOnce , ctrlDTimerRef ) ;
2025-06-23 05:42:20 +00:00
} else if ( key . ctrl && input === 's' && ! enteringConstrainHeightMode ) {
2025-06-22 20:43:36 +00:00
setConstrainHeight ( false ) ;
2025-06-07 18:30:56 -04:00
}
} ) ;
useEffect ( ( ) = > {
if ( config ) {
setGeminiMdFileCount ( config . getGeminiMdFileCount ( ) ) ;
}
2025-08-06 02:01:01 +09:00
} , [ config , config . getGeminiMdFileCount ] ) ;
2025-06-07 18:30:56 -04:00
2025-05-21 07:36:22 +00:00
const logger = useLogger ( ) ;
useEffect ( ( ) = > {
const fetchUserMessages = async ( ) = > {
2025-06-03 23:01:26 -07:00
const pastMessagesRaw = ( await logger ? . getPreviousUserMessages ( ) ) || [ ] ; // Newest first
const currentSessionUserMessages = history
. filter (
( item ) : item is HistoryItem & { type : 'user' ; text : string } = >
item . type === 'user' &&
typeof item . text === 'string' &&
item . text . trim ( ) !== '' ,
)
. map ( ( item ) = > item . text )
. reverse ( ) ; // Newest first, to match pastMessagesRaw sorting
// Combine, with current session messages being more recent
const combinedMessages = [
. . . currentSessionUserMessages ,
. . . pastMessagesRaw ,
] ;
// Deduplicate consecutive identical messages from the combined list (still newest first)
const deduplicatedMessages : string [ ] = [ ] ;
if ( combinedMessages . length > 0 ) {
deduplicatedMessages . push ( combinedMessages [ 0 ] ) ; // Add the newest one unconditionally
for ( let i = 1 ; i < combinedMessages . length ; i ++ ) {
if ( combinedMessages [ i ] !== combinedMessages [ i - 1 ] ) {
deduplicatedMessages . push ( combinedMessages [ i ] ) ;
}
}
2025-05-21 07:36:22 +00:00
}
2025-06-03 23:01:26 -07:00
// Reverse to oldest first for useInputHistory
setUserMessages ( deduplicatedMessages . reverse ( ) ) ;
2025-05-21 07:36:22 +00:00
} ;
fetchUserMessages ( ) ;
} , [ history , logger ] ) ;
2025-04-15 21:41:08 -07:00
2025-07-27 02:00:26 -04:00
const isInputActive =
streamingState === StreamingState . Idle && ! initError && ! isProcessing ;
2025-04-19 14:31:59 +01:00
2025-05-14 17:33:37 -07:00
const handleClearScreen = useCallback ( ( ) = > {
clearItems ( ) ;
2025-05-23 22:51:47 -07:00
clearConsoleMessagesState ( ) ;
2025-05-14 22:48:50 -07:00
console . clear ( ) ;
2025-05-14 17:33:37 -07:00
refreshStatic ( ) ;
2025-05-23 22:51:47 -07:00
} , [ clearItems , clearConsoleMessagesState , refreshStatic ] ) ;
2025-05-14 17:33:37 -07:00
2025-05-15 22:56:03 -07:00
const mainControlsRef = useRef < DOMElement > ( null ) ;
2025-05-15 00:19:41 -07:00
const pendingHistoryItemRef = useRef < DOMElement > ( null ) ;
useEffect ( ( ) = > {
2025-05-15 22:56:03 -07:00
if ( mainControlsRef . current ) {
const fullFooterMeasurement = measureElement ( mainControlsRef . current ) ;
setFooterHeight ( fullFooterMeasurement . height ) ;
}
2025-05-22 10:36:44 -07:00
} , [ terminalHeight , consoleMessages , showErrorDetails ] ) ;
2025-05-15 00:19:41 -07:00
2025-06-19 20:17:23 +00:00
const staticExtraHeight = /* margins and padding */ 3 ;
const availableTerminalHeight = useMemo (
( ) = > terminalHeight - footerHeight - staticExtraHeight ,
[ terminalHeight , footerHeight ] ,
) ;
2025-05-15 00:19:41 -07:00
2025-06-21 11:11:42 -07:00
useEffect ( ( ) = > {
// skip refreshing Static during first mount
if ( isInitialMount . current ) {
isInitialMount . current = false ;
return ;
}
// debounce so it doesn't fire up too often during resize
const handler = setTimeout ( ( ) = > {
2025-06-23 14:45:15 -07:00
setStaticNeedsRefresh ( false ) ;
refreshStatic ( ) ;
2025-06-21 11:11:42 -07:00
} , 300 ) ;
return ( ) = > {
clearTimeout ( handler ) ;
} ;
2025-06-23 14:45:15 -07:00
} , [ terminalWidth , terminalHeight , refreshStatic ] ) ;
2025-06-21 11:11:42 -07:00
2025-05-15 00:19:41 -07:00
useEffect ( ( ) = > {
if ( streamingState === StreamingState . Idle && staticNeedsRefresh ) {
setStaticNeedsRefresh ( false ) ;
refreshStatic ( ) ;
}
} , [ streamingState , refreshStatic , staticNeedsRefresh ] ) ;
2025-05-22 10:36:44 -07:00
const filteredConsoleMessages = useMemo ( ( ) = > {
if ( config . getDebugMode ( ) ) {
return consoleMessages ;
}
return consoleMessages . filter ( ( msg ) = > msg . type !== 'debug' ) ;
} , [ consoleMessages , config ] ) ;
2025-05-28 23:30:05 +00:00
const branchName = useGitBranchName ( config . getTargetDir ( ) ) ;
2025-06-13 09:19:08 -07:00
const contextFileNames = useMemo ( ( ) = > {
const fromSettings = settings . merged . contextFileName ;
if ( fromSettings ) {
return Array . isArray ( fromSettings ) ? fromSettings : [ fromSettings ] ;
}
return getAllGeminiMdFilenames ( ) ;
} , [ settings . merged . contextFileName ] ) ;
2025-07-11 16:52:56 -07:00
const initialPrompt = useMemo ( ( ) = > config . getQuestion ( ) , [ config ] ) ;
const geminiClient = config . getGeminiClient ( ) ;
useEffect ( ( ) = > {
if (
initialPrompt &&
! initialPromptSubmitted . current &&
! isAuthenticating &&
! isAuthDialogOpen &&
! isThemeDialogOpen &&
! isEditorDialogOpen &&
! showPrivacyNotice &&
geminiClient ? . isInitialized ? . ( )
) {
submitQuery ( initialPrompt ) ;
initialPromptSubmitted . current = true ;
}
} , [
initialPrompt ,
submitQuery ,
isAuthenticating ,
isAuthDialogOpen ,
isThemeDialogOpen ,
isEditorDialogOpen ,
showPrivacyNotice ,
geminiClient ,
] ) ;
2025-06-11 20:08:32 -04:00
if ( quittingMessages ) {
return (
< Box flexDirection = "column" marginBottom = { 1 } >
{ quittingMessages . map ( ( item ) = > (
< HistoryItemDisplay
key = { item . id }
2025-06-19 20:17:23 +00:00
availableTerminalHeight = {
constrainHeight ? availableTerminalHeight : undefined
}
terminalWidth = { terminalWidth }
2025-06-11 20:08:32 -04:00
item = { item }
isPending = { false }
config = { config }
/ >
) ) }
< / Box >
) ;
}
2025-06-19 20:17:23 +00:00
const mainAreaWidth = Math . floor ( terminalWidth * 0.9 ) ;
2025-06-22 20:43:36 +00:00
const debugConsoleMaxHeight = Math . floor ( Math . max ( terminalHeight * 0.2 , 5 ) ) ;
2025-06-19 20:17:23 +00:00
// Arbitrary threshold to ensure that items in the static area are large
// enough but not too large to make the terminal hard to use.
const staticAreaMaxItemHeight = Math . max ( terminalHeight * 4 , 100 ) ;
2025-07-25 15:36:42 -07:00
const placeholder = vimModeEnabled
? " Press 'i' for INSERT mode and 'Esc' for NORMAL mode."
: ' Type your message or @path/to/file' ;
2025-04-17 18:06:21 -04:00
return (
2025-05-28 19:46:08 +00:00
< StreamingContext.Provider value = { streamingState } >
2025-07-25 17:36:19 -07:00
< Box flexDirection = "column" width = "90%" >
2025-05-24 00:44:17 -07:00
{ /*
* 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.
*/ }
< Static
key = { staticKey }
items = { [
< Box flexDirection = "column" key = "header" >
2025-07-14 10:07:31 +05:30
{ ! settings . merged . hideBanner && (
< Header
terminalWidth = { terminalWidth }
2025-07-28 11:13:46 -07:00
version = { version }
2025-07-14 10:07:31 +05:30
nightly = { nightly }
/ >
) }
2025-06-30 01:56:37 +02:00
{ ! settings . merged . hideTips && < Tips config = { config } / > }
2025-05-24 00:44:17 -07:00
< / Box > ,
. . . history . map ( ( h ) = > (
< HistoryItemDisplay
2025-06-19 20:17:23 +00:00
terminalWidth = { mainAreaWidth }
availableTerminalHeight = { staticAreaMaxItemHeight }
2025-05-24 00:44:17 -07:00
key = { h . id }
item = { h }
isPending = { false }
2025-06-08 18:56:58 +01:00
config = { config }
2025-08-04 09:53:50 -07:00
commands = { slashCommands }
2025-05-24 00:44:17 -07:00
/ >
) ) ,
] }
>
{ ( item ) = > item }
< / Static >
2025-06-22 00:54:10 +00:00
< OverflowProvider >
< Box ref = { pendingHistoryItemRef } flexDirection = "column" >
{ pendingHistoryItems . map ( ( item , i ) = > (
< HistoryItemDisplay
key = { i }
availableTerminalHeight = {
constrainHeight ? availableTerminalHeight : undefined
}
terminalWidth = { mainAreaWidth }
// TODO(taehykim): It seems like references to ids aren't necessary in
// HistoryItemDisplay. Refactor later. Use a fake id for now.
item = { { . . . item , id : 0 } }
isPending = { true }
config = { config }
isFocused = { ! isEditorDialogOpen }
/ >
) ) }
< ShowMoreLines constrainHeight = { constrainHeight } / >
< / Box >
< / OverflowProvider >
2025-05-24 00:44:17 -07:00
< Box flexDirection = "column" ref = { mainControlsRef } >
2025-07-28 17:56:52 -07:00
{ /* Move UpdateNotification to render update notification above input area */ }
{ updateInfo && < UpdateNotification message = { updateInfo . message } / > }
2025-07-28 11:13:46 -07:00
{ startupWarnings . length > 0 && (
2025-05-17 22:36:33 -07:00
< Box
2025-05-24 00:44:17 -07:00
borderStyle = "round"
borderColor = { Colors . AccentYellow }
paddingX = { 1 }
marginY = { 1 }
flexDirection = "column"
2025-05-17 22:36:33 -07:00
>
2025-07-28 11:13:46 -07:00
{ startupWarnings . map ( ( warning , index ) = > (
2025-05-24 00:44:17 -07:00
< Text key = { index } color = { Colors . AccentYellow } >
{ warning }
< / Text >
) ) }
2025-05-17 22:36:33 -07:00
< / Box >
2025-05-24 00:44:17 -07:00
) }
2025-07-27 02:00:26 -04:00
{ shellConfirmationRequest ? (
< ShellConfirmationDialog request = { shellConfirmationRequest } / >
) : isThemeDialogOpen ? (
2025-05-24 00:44:17 -07:00
< Box flexDirection = "column" >
{ themeError && (
< Box marginBottom = { 1 } >
< Text color = { Colors . AccentRed } > { themeError } < / Text >
< / Box >
) }
< ThemeDialog
onSelect = { handleThemeSelect }
onHighlight = { handleThemeHighlight }
settings = { settings }
2025-06-19 20:17:23 +00:00
availableTerminalHeight = {
constrainHeight
? terminalHeight - staticExtraHeight
: undefined
}
terminalWidth = { mainAreaWidth }
2025-05-20 16:50:32 -07:00
/ >
2025-05-24 00:44:17 -07:00
< / Box >
2025-06-20 10:46:41 -07:00
) : isAuthenticating ? (
2025-07-10 18:59:02 -07:00
< >
< AuthInProgress
onTimeout = { ( ) = > {
setAuthError ( 'Authentication timed out. Please try again.' ) ;
cancelAuthentication ( ) ;
openAuthDialog ( ) ;
} }
/ >
{ showErrorDetails && (
< OverflowProvider >
< Box flexDirection = "column" >
< DetailedMessagesDisplay
messages = { filteredConsoleMessages }
maxHeight = {
constrainHeight ? debugConsoleMaxHeight : undefined
}
width = { inputWidth }
/ >
< ShowMoreLines constrainHeight = { constrainHeight } / >
< / Box >
< / OverflowProvider >
) }
< / >
2025-06-19 16:52:22 -07:00
) : isAuthDialogOpen ? (
< Box flexDirection = "column" >
< AuthDialog
onSelect = { handleAuthSelect }
settings = { settings }
initialErrorMessage = { authError }
/ >
< / Box >
2025-06-12 02:21:54 +01:00
) : isEditorDialogOpen ? (
< Box flexDirection = "column" >
{ editorError && (
< Box marginBottom = { 1 } >
< Text color = { Colors . AccentRed } > { editorError } < / Text >
< / Box >
) }
< EditorSettingsDialog
onSelect = { handleEditorSelect }
settings = { settings }
onExit = { exitEditorDialog }
/ >
< / Box >
2025-06-27 12:07:38 -07:00
) : showPrivacyNotice ? (
< PrivacyNotice
onExit = { ( ) = > setShowPrivacyNotice ( false ) }
config = { config }
/ >
2025-05-24 00:44:17 -07:00
) : (
< >
< LoadingIndicator
2025-06-15 11:19:05 -07:00
thought = {
streamingState === StreamingState . WaitingForConfirmation ||
config . getAccessibility ( ) ? . disableLoadingPhrases
? undefined
: thought
}
2025-06-04 00:46:57 -07:00
currentLoadingPhrase = {
config . getAccessibility ( ) ? . disableLoadingPhrases
? undefined
: currentLoadingPhrase
}
2025-05-24 00:44:17 -07:00
elapsedTime = { elapsedTime }
/ >
2025-07-25 14:50:34 +00:00
2025-05-24 00:44:17 -07:00
< Box
marginTop = { 1 }
display = "flex"
justifyContent = "space-between"
width = "100%"
>
< Box >
{ process . env . GEMINI_SYSTEM_MD && (
< Text color = { Colors . AccentRed } > | ⌐ ■ _ ■ | < / Text >
) }
2025-05-30 19:36:52 -07:00
{ ctrlCPressedOnce ? (
< Text color = { Colors . AccentYellow } >
Press Ctrl + C again to exit .
< / Text >
2025-06-13 09:59:09 -07:00
) : ctrlDPressedOnce ? (
< Text color = { Colors . AccentYellow } >
Press Ctrl + D again to exit .
< / Text >
2025-05-30 19:36:52 -07:00
) : (
2025-06-01 15:48:48 -07:00
< ContextSummaryDisplay
2025-07-28 11:03:22 -04:00
ideContext = { ideContextState }
2025-06-01 15:48:48 -07:00
geminiMdFileCount = { geminiMdFileCount }
2025-06-13 09:19:08 -07:00
contextFileNames = { contextFileNames }
2025-06-01 15:48:48 -07:00
mcpServers = { config . getMcpServers ( ) }
2025-07-18 20:45:00 +02:00
blockedMcpServers = { config . getBlockedMcpServers ( ) }
2025-06-07 18:30:56 -04:00
showToolDescriptions = { showToolDescriptions }
2025-06-01 15:48:48 -07:00
/ >
2025-05-24 00:44:17 -07:00
) }
< / Box >
< Box >
2025-06-02 22:05:45 +02:00
{ showAutoAcceptIndicator !== ApprovalMode . DEFAULT &&
! shellModeActive && (
< AutoAcceptIndicator
approvalMode = { showAutoAcceptIndicator }
/ >
) }
2025-05-24 00:44:17 -07:00
{ shellModeActive && < ShellModeIndicator / > }
< / Box >
< / Box >
2025-07-25 14:50:34 +00:00
{ showIDEContextDetail && (
2025-07-30 21:26:31 +00:00
< IDEContextDetailDisplay
ideContext = { ideContextState }
detectedIdeDisplay = { config
. getIdeClient ( )
. getDetectedIdeDisplayName ( ) }
/ >
2025-07-25 14:50:34 +00:00
) }
2025-05-24 00:44:17 -07:00
{ showErrorDetails && (
2025-06-22 00:54:10 +00:00
< OverflowProvider >
2025-07-03 21:42:02 +02:00
< Box flexDirection = "column" >
< DetailedMessagesDisplay
messages = { filteredConsoleMessages }
maxHeight = {
constrainHeight ? debugConsoleMaxHeight : undefined
}
width = { inputWidth }
/ >
< ShowMoreLines constrainHeight = { constrainHeight } / >
< / Box >
2025-06-22 00:54:10 +00:00
< / OverflowProvider >
2025-05-24 00:44:17 -07:00
) }
{ isInputActive && (
< InputPrompt
2025-06-13 09:59:09 -07:00
buffer = { buffer }
inputWidth = { inputWidth }
suggestionsWidth = { suggestionsWidth }
2025-05-24 00:44:17 -07:00
onSubmit = { handleFinalSubmit }
userMessages = { userMessages }
onClearScreen = { handleClearScreen }
config = { config }
slashCommands = { slashCommands }
2025-07-07 16:45:44 -04:00
commandContext = { commandContext }
2025-05-24 00:44:17 -07:00
shellModeActive = { shellModeActive }
setShellModeActive = { setShellModeActive }
2025-07-17 20:45:42 -04:00
focus = { isFocused }
2025-07-25 15:36:42 -07:00
vimHandleInput = { vimHandleInput }
placeholder = { placeholder }
2025-05-24 00:44:17 -07:00
/ >
) }
< / >
) }
{ initError && streamingState !== StreamingState . Responding && (
< Box
borderStyle = "round"
borderColor = { Colors . AccentRed }
paddingX = { 1 }
marginBottom = { 1 }
>
{ history . find (
( item ) = >
item . type === 'error' && item . text ? . includes ( initError ) ,
) ? . text ? (
2025-05-15 00:19:41 -07:00
< Text color = { Colors . AccentRed } >
2025-05-24 00:44:17 -07:00
{
history . find (
( item ) = >
item . type === 'error' && item . text ? . includes ( initError ) ,
) ? . text
}
2025-05-15 00:19:41 -07:00
< / Text >
2025-05-24 00:44:17 -07:00
) : (
< >
< Text color = { Colors . AccentRed } >
Initialization Error : { initError }
< / Text >
< Text color = { Colors . AccentRed } >
{ ' ' }
Please check API key and configuration .
< / Text >
< / >
) }
< / Box >
) }
< Footer
2025-06-24 18:48:55 -04:00
model = { currentModel }
2025-05-28 23:30:05 +00:00
targetDir = { config . getTargetDir ( ) }
2025-05-24 00:44:17 -07:00
debugMode = { config . getDebugMode ( ) }
2025-05-28 23:30:05 +00:00
branchName = { branchName }
2025-05-24 00:44:17 -07:00
debugMessage = { debugMessage }
corgiMode = { corgiMode }
errorCount = { errorCount }
showErrorDetails = { showErrorDetails }
2025-05-30 22:18:01 +00:00
showMemoryUsage = {
config . getDebugMode ( ) || config . getShowMemoryUsage ( )
}
2025-06-29 20:44:33 -04:00
promptTokenCount = { sessionStats . lastPromptTokenCount }
2025-07-11 13:43:57 -07:00
nightly = { nightly }
2025-07-25 15:36:42 -07:00
vimMode = { vimModeEnabled ? vimMode : undefined }
2025-05-24 00:44:17 -07:00
/ >
< / Box >
2025-05-15 00:19:41 -07:00
< / Box >
2025-05-24 00:44:17 -07:00
< / StreamingContext.Provider >
2025-04-17 18:06:21 -04:00
) ;
2025-04-15 21:41:08 -07:00
} ;