11import { NetworkError , RETRYABLE_ERROR_CODES } from '@codebuff/sdk'
2- import { useCallback , useMemo , useRef , useState } from 'react'
2+ import { useCallback , useEffect , useMemo , useRef , useState } from 'react'
33import { useShallow } from 'zustand/react/shallow'
44
55import { Chat } from './chat'
@@ -14,9 +14,10 @@ import { useTerminalDimensions } from './hooks/use-terminal-dimensions'
1414import { useTerminalFocus } from './hooks/use-terminal-focus'
1515import { useTheme } from './hooks/use-theme'
1616import { getProjectRoot } from './project-files'
17- import { useChatStore } from './state/chat-store'
17+ import { useChatStore , type TopBannerType } from './state/chat-store'
1818import { openFileAtPath } from './utils/open-file'
1919import { formatCwd } from './utils/path-helpers'
20+ import { findGitRoot } from './utils/git'
2021import { getLogoBlockColor , getLogoAccentColor } from './utils/theme-system'
2122
2223import type { MultilineInputHandle } from './components/multiline-input'
@@ -73,11 +74,21 @@ export const App = ({
7374 } )
7475
7576 const inputRef = useRef < MultilineInputHandle | null > ( null )
76- const { setInputFocused, setIsFocusSupported, resetChatStore } = useChatStore (
77+ const {
78+ setInputFocused,
79+ setIsFocusSupported,
80+ resetChatStore,
81+ activeTopBanner,
82+ setActiveTopBanner,
83+ closeTopBanner,
84+ } = useChatStore (
7785 useShallow ( ( store ) => ( {
7886 setInputFocused : store . setInputFocused ,
7987 setIsFocusSupported : store . setIsFocusSupported ,
8088 resetChatStore : store . reset ,
89+ activeTopBanner : store . activeTopBanner ,
90+ setActiveTopBanner : store . setActiveTopBanner ,
91+ closeTopBanner : store . closeTopBanner ,
8192 } ) ) ,
8293 )
8394
@@ -110,6 +121,53 @@ export const App = ({
110121 } )
111122
112123 const projectRoot = getProjectRoot ( )
124+ const gitRoot = useMemo (
125+ ( ) => findGitRoot ( { cwd : projectRoot } ) ,
126+ [ projectRoot ] ,
127+ )
128+ const showGitRootBanner = Boolean ( gitRoot && gitRoot !== projectRoot )
129+ const [ gitRootBannerDismissed , setGitRootBannerDismissed ] = useState ( false )
130+ const prevTopBannerRef = useRef < TopBannerType | null > ( null )
131+
132+ useEffect ( ( ) => {
133+ setGitRootBannerDismissed ( false )
134+ } , [ projectRoot ] )
135+
136+ useEffect ( ( ) => {
137+ const prevBanner = prevTopBannerRef . current
138+ if (
139+ prevBanner === 'gitRoot' &&
140+ activeTopBanner === null &&
141+ showGitRootBanner
142+ ) {
143+ setGitRootBannerDismissed ( true )
144+ }
145+ prevTopBannerRef . current = activeTopBanner
146+ } , [ activeTopBanner , showGitRootBanner ] )
147+
148+ useEffect ( ( ) => {
149+ if ( ! showGitRootBanner ) {
150+ if ( activeTopBanner === 'gitRoot' ) {
151+ closeTopBanner ( )
152+ }
153+ return
154+ }
155+ if ( ! gitRootBannerDismissed && activeTopBanner === null ) {
156+ setActiveTopBanner ( 'gitRoot' )
157+ }
158+ } , [
159+ activeTopBanner ,
160+ closeTopBanner ,
161+ gitRootBannerDismissed ,
162+ setActiveTopBanner ,
163+ showGitRootBanner ,
164+ ] )
165+
166+ const handleSwitchToGitRoot = useCallback ( ( ) => {
167+ if ( gitRoot ) {
168+ onProjectChange ( gitRoot )
169+ }
170+ } , [ gitRoot , onProjectChange ] )
113171
114172 const headerContent = useMemo ( ( ) => {
115173 const displayPath = formatCwd ( projectRoot )
@@ -211,6 +269,8 @@ export const App = ({
211269 continueChatId = { continueChatId }
212270 authStatus = { authStatus }
213271 initialMode = { initialMode }
272+ gitRoot = { gitRoot }
273+ onSwitchToGitRoot = { handleSwitchToGitRoot }
214274 />
215275 )
216276}
0 commit comments