11import { useRenderer } from '@opentui/react'
2- import React , { useCallback , useEffect , useMemo , useRef } from 'react'
2+ import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react'
33import { useShallow } from 'zustand/react/shallow'
44
55import { AgentModeToggle } from './components/agent-mode-toggle'
6+ import { LoginScreen } from './components/login-screen'
67import { MultilineInput } from './components/multiline-input'
78import { Separator } from './components/separator'
89import { StatusIndicator , useHasStatus } from './components/status-indicator'
@@ -25,6 +26,9 @@ import { logger } from './utils/logger'
2526import { buildMessageTree } from './utils/message-tree-utils'
2627import { chatThemes , createMarkdownPalette } from './utils/theme-system'
2728
29+ import type { User } from './utils/auth'
30+ import { logoutUser } from './utils/auth'
31+
2832import type { ToolName } from '@codebuff/sdk'
2933import type { InputRenderable , ScrollBoxRenderable } from '@opentui/core'
3034
@@ -78,7 +82,14 @@ export type ChatMessage = {
7882export const App = ( {
7983 initialPrompt,
8084 agentId,
81- } : { initialPrompt ?: string ; agentId ?: string } = { } ) => {
85+ requireAuth,
86+ hasInvalidCredentials,
87+ } : {
88+ initialPrompt : string | null
89+ agentId ?: string
90+ requireAuth : boolean | null
91+ hasInvalidCredentials : boolean | null
92+ } ) => {
8293 const renderer = useRenderer ( )
8394 const scrollRef = useRef < ScrollBoxRenderable | null > ( null )
8495 const inputRef = useRef < InputRenderable | null > ( null )
@@ -87,6 +98,30 @@ export const App = ({
8798 const theme = chatThemes [ themeName ]
8899 const markdownPalette = useMemo ( ( ) => createMarkdownPalette ( theme ) , [ theme ] )
89100
101+ // Track authentication state
102+ const [ isAuthenticated , setIsAuthenticated ] = useState ( ! requireAuth )
103+ const [ user , setUser ] = useState < User | null > ( null )
104+
105+ // Log app initialization
106+ useEffect ( ( ) => {
107+ logger . debug (
108+ {
109+ requireAuth,
110+ hasInvalidCredentials,
111+ hasInitialPrompt : ! ! initialPrompt ,
112+ agentId,
113+ } ,
114+ 'Chat App component mounted' ,
115+ )
116+ } , [ ] )
117+
118+ // Handle successful login
119+ const handleLoginSuccess = useCallback ( ( loggedInUser : User ) => {
120+ setUser ( loggedInUser )
121+ setIsAuthenticated ( true )
122+ logger . info ( { user : loggedInUser . name } , 'User logged in successfully' )
123+ } , [ ] )
124+
90125 const {
91126 inputValue,
92127 setInputValue,
@@ -471,6 +506,44 @@ export const App = ({
471506 const trimmed = inputValue . trim ( )
472507 if ( ! trimmed ) return
473508
509+ const normalized = trimmed . startsWith ( '/' ) ? trimmed . slice ( 1 ) : trimmed
510+ const cmd = normalized . split ( / \s + / ) [ 0 ] . toLowerCase ( )
511+ if ( cmd === 'login' || cmd === 'signin' ) {
512+ const msg = {
513+ id : `sys-${ Date . now ( ) } ` ,
514+ variant : 'ai' as const ,
515+ content : "You're already in the app. Use /logout to switch accounts." ,
516+ timestamp : new Date ( ) . toISOString ( ) ,
517+ }
518+ setMessages ( ( prev ) => [ ...prev , msg ] )
519+ setInputValue ( '' )
520+ return
521+ }
522+ if ( cmd === 'logout' || cmd === 'signout' ) {
523+ ; ( async ( ) => {
524+ try {
525+ await logoutUser ( )
526+ } finally {
527+ abortControllerRef . current ?. abort ( )
528+ stopStreaming ( )
529+ setCanProcessQueue ( false )
530+ const msg = {
531+ id : `sys-${ Date . now ( ) } ` ,
532+ variant : 'ai' as const ,
533+ content : 'Logged out.' ,
534+ timestamp : new Date ( ) . toISOString ( ) ,
535+ }
536+ setMessages ( ( prev ) => [ ...prev , msg ] )
537+ setInputValue ( '' )
538+ setTimeout ( ( ) => {
539+ setUser ( null )
540+ setIsAuthenticated ( false )
541+ } , 300 )
542+ }
543+ } ) ( )
544+ return
545+ }
546+
474547 saveToHistory ( trimmed )
475548 setInputValue ( '' )
476549
@@ -563,6 +636,17 @@ export const App = ({
563636 </ text >
564637 ) : null
565638
639+ // Show login screen if not authenticated
640+ if ( ! isAuthenticated ) {
641+ return (
642+ < LoginScreen
643+ onLoginSuccess = { handleLoginSuccess }
644+ theme = { theme }
645+ hasInvalidCredentials = { hasInvalidCredentials }
646+ />
647+ )
648+ }
649+
566650 return (
567651 < box
568652 style = { {
@@ -629,47 +713,33 @@ export const App = ({
629713 backgroundColor : theme . panelBg ,
630714 } }
631715 >
632- < box
633- style = { {
634- flexDirection : 'row' ,
635- alignItems : 'center' ,
636- justifyContent : 'space-between' ,
637- width : '100%' ,
638- } }
639- >
716+ { ( hasStatus || queuedMessages . length > 0 ) && (
640717 < box
641718 style = { {
642- flexGrow : 1 ,
643719 flexDirection : 'row' ,
644720 alignItems : 'center' ,
721+ width : '100%' ,
645722 } }
646723 >
647- { ( hasStatus || queuedMessages . length > 0 ) && (
648- < text wrap = { false } >
649- < StatusIndicator
650- isProcessing = { isWaitingForResponse }
651- theme = { theme }
652- clipboardMessage = { clipboardMessage }
653- />
654- { hasStatus && queuedMessages . length > 0 && ' ' }
655- { queuedMessages . length > 0 && (
656- < span fg = { theme . statusSecondary } bg = { theme . inputFocusedBg } >
657- { ' ' }
658- { formatQueuedPreview (
659- queuedMessages ,
660- Math . max ( 30 , renderer . width - 25 ) ,
661- ) } { ' ' }
662- </ span >
663- ) }
664- </ text >
665- ) }
724+ < text wrap = { false } >
725+ < StatusIndicator
726+ isProcessing = { isWaitingForResponse }
727+ theme = { theme }
728+ clipboardMessage = { clipboardMessage }
729+ />
730+ { hasStatus && queuedMessages . length > 0 && ' ' }
731+ { queuedMessages . length > 0 && (
732+ < span fg = { theme . statusSecondary } bg = { theme . inputFocusedBg } >
733+ { ' ' }
734+ { formatQueuedPreview (
735+ queuedMessages ,
736+ Math . max ( 30 , renderer . width - 25 ) ,
737+ ) } { ' ' }
738+ </ span >
739+ ) }
740+ </ text >
666741 </ box >
667- < AgentModeToggle
668- mode = { agentMode }
669- theme = { theme }
670- onToggle = { toggleAgentMode }
671- />
672- </ box >
742+ ) }
673743 < Separator theme = { theme } width = { renderer . width } />
674744 { slashContext . active && slashSuggestionItems . length > 0 ? (
675745 < SuggestionMenu
@@ -703,6 +773,21 @@ export const App = ({
703773 onKeyIntercept = { handleSuggestionMenuKey }
704774 />
705775 < Separator theme = { theme } width = { renderer . width } />
776+ < box
777+ style = { {
778+ flexDirection : 'row' ,
779+ alignItems : 'center' ,
780+ justifyContent : 'flex-end' ,
781+ width : '100%' ,
782+ paddingTop : 1 ,
783+ } }
784+ >
785+ < AgentModeToggle
786+ mode = { agentMode }
787+ theme = { theme }
788+ onToggle = { toggleAgentMode }
789+ />
790+ </ box >
706791 </ box >
707792 </ box >
708793 )
0 commit comments