@@ -113,11 +113,12 @@ export function useChatComposerState(args: UseChatComposerStateArgs) {
113113 const optionsFor = useCallback ( ( text : string ) : QueuedSendOptions => ( { model : gjcModel , effort : reasoningEffort , sessionSummary : sessionLabel ( selectedSession , text ) } ) , [ gjcModel , reasoningEffort , selectedSession ] ) ;
114114 const upload = useCallback ( async ( files : File [ ] ) => { if ( ! files . length ) return [ ] ; const body = new FormData ( ) ; files . forEach ( ( file ) => body . append ( 'images' , file ) ) ; const response = await authenticatedFetch ( '/api/assets/images' , { method : 'POST' , headers : { } , body } ) ; if ( ! response . ok ) throw new Error ( 'Failed to upload images' ) ; return ( await response . json ( ) ) . images as unknown [ ] ; } , [ ] ) ;
115115 const workspaceTarget = useWorkspaceTarget ( { selectedProject, selectedSession, currentSessionId, input } ) ;
116+ const { resolveForSend } = workspaceTarget ;
116117 // A workspace root (e.g. `~/Projects`) never hosts a session itself: the
117118 // resolved child repo becomes the session's project, and it is what the
118119 // caller (ChatInterface's `establishSession`) registers into the sidebar.
119120 const descend = useCallback ( async ( target : WorkspaceCandidate ) : Promise < Project > => { const response = await authenticatedFetch ( `/api/projects/${ encodeURIComponent ( selectedProject ! . projectId ) } /descend` , { method : 'POST' , body : JSON . stringify ( { path : target . path } ) } ) ; if ( ! response . ok ) { const body = await response . json ( ) . catch ( ( ) => ( { } ) ) as { error ?: string | { message ?: string } } ; const error = body . error ; throw new Error ( typeof error === 'string' ? error : error ?. message ?? `Failed to switch to ${ target . name } (${ response . status } )` ) ; } return ( await response . json ( ) ) ?. data as Project ; } , [ selectedProject ] ) ;
120- const allocate = useCallback ( async ( summary : string | null ) => { let id = selectedSession ? ( selectedSession . __provider === 'gjc' ? selectedSession . id : null ) : currentSessionId ; if ( id ) return id ; const target = workspaceTarget . isWorkspace ? workspaceTarget . target : null ; const project = target ? await descend ( target ) : selectedProject ; const response = await authenticatedFetch ( '/api/providers/sessions' , { method : 'POST' , body : JSON . stringify ( { provider : 'gjc' , projectPath : project ?. fullPath || project ?. path || '' } ) } ) ; if ( ! response . ok ) throw new Error ( `Failed to create session (${ response . status } )` ) ; id = ( await response . json ( ) ) ?. data ?. sessionId || null ; if ( ! id ) throw new Error ( 'no session id returned.' ) ; onSessionEstablished ?.( id , { provider : 'gjc' , project : project ! , summary } ) ; return id ; } , [ currentSessionId , descend , onSessionEstablished , selectedProject , selectedSession , workspaceTarget . isWorkspace , workspaceTarget . target ] ) ;
121+ const allocate = useCallback ( async ( summary : string | null , text : string ) => { let id = selectedSession ? ( selectedSession . __provider === 'gjc' ? selectedSession . id : null ) : currentSessionId ; if ( id ) return id ; const target = await resolveForSend ( text ) ; const project = target ? await descend ( target ) : selectedProject ; const response = await authenticatedFetch ( '/api/providers/sessions' , { method : 'POST' , body : JSON . stringify ( { provider : 'gjc' , projectPath : project ?. fullPath || project ?. path || '' } ) } ) ; if ( ! response . ok ) throw new Error ( `Failed to create session (${ response . status } )` ) ; id = ( await response . json ( ) ) ?. data ?. sessionId || null ; if ( ! id ) throw new Error ( 'no session id returned.' ) ; onSessionEstablished ?.( id , { provider : 'gjc' , project : project ! , summary } ) ; return id ; } , [ currentSessionId , descend , onSessionEstablished , resolveForSend , selectedProject , selectedSession ] ) ;
121122
122123 const handleSubmit = useCallback ( async ( event : FormEvent < HTMLFormElement > | MouseEvent | TouchEvent | KeyboardEvent < HTMLTextAreaElement > ) => {
123124 event . preventDefault ( ) ; const text = inputRef . current ; if ( ! text . trim ( ) || ! selectedProject ) return ;
@@ -126,7 +127,7 @@ export function useChatComposerState(args: UseChatComposerStateArgs) {
126127 const candidate = text . trimEnd ( ) ; const help = candidate . trim ( ) . toLowerCase ( ) === 'help' ;
127128 if ( candidate . startsWith ( '/' ) || help ) { const gap = candidate . indexOf ( ' ' ) ; const name = help ? '/help' : gap > 0 ? candidate . slice ( 0 , gap ) : candidate ; const commandArgs = gap > 0 ? candidate . slice ( gap ) . trim ( ) : '' ; const app = findAppUiCommand ( resolveCommandAlias ( name ) ) ; if ( app && ( app . interceptWithArgs !== false || ! commandArgs ) ) { clearComposer ( ) ; applyAppCommand ( app ) ; return ; } const notice = getLocalCommandNotice ( name , commandArgs ) ; if ( notice ) { clearComposer ( ) ; addMessage ( { type : 'assistant' , content : notice , timestamp : Date . now ( ) } ) ; return ; } if ( ! bypassGate . current ) { const gate = gateForCommand ( resolveCommandAlias ( name ) , commandArgs ) ; if ( gate ) { clearComposer ( ) ; announceGate ( { ...gate , text : candidate } ) ; return ; } } bypassGate . current = false ; const registered = slashCommands . find ( ( item ) => item . name === name ) ; if ( registered && ! isAppUiCommand ( registered ) && registered . type !== 'skill' && registered . type !== 'provider' ) { void executeCommand ( registered , help ? '/help' : candidate ) ; clearComposer ( ) ; return ; } }
128129 let images : unknown [ ] ; try { images = await upload ( attachedImages ) ; } catch ( error ) { const message = error instanceof Error ? error . message : 'Unknown error' ; console . error ( 'Image upload failed:' , error ) ; addMessage ( { type : 'error' , content : `Failed to upload images: ${ message } ` , timestamp : new Date ( ) } ) ; return ; }
129- const summary = sessionLabel ( selectedSession , text ) ; let id : string ; try { id = await allocate ( summary ) ; } catch ( error ) { const message = error instanceof Error ? error . message : 'Unknown error' ; console . error ( 'Session creation failed:' , error ) ; addMessage ( { type : 'error' , content : `Failed to start a new session: ${ message } ` , timestamp : new Date ( ) } ) ; return ; }
130+ const summary = sessionLabel ( selectedSession , text ) ; let id : string ; try { id = await allocate ( summary , text ) ; } catch ( error ) { const message = error instanceof Error ? error . message : 'Unknown error' ; console . error ( 'Session creation failed:' , error ) ; addMessage ( { type : 'error' , content : `Failed to start a new session: ${ message } ` , timestamp : new Date ( ) } ) ; return ; }
130131 addMessage ( { type : 'user' , content : text , images : images as any , timestamp : new Date ( ) } ) ; onSessionProcessing ?.( id , { statusText : null , canInterrupt : true } ) ; setIsUserScrolledUp ( false ) ; setTimeout ( scrollToBottom , 100 ) ; sendMessage ( { type : 'chat.send' , sessionId : id , content : text , options : { ...optionsFor ( text ) , images } } ) ; clearComposer ( ) ; eraseDraft ( id ) ;
131132 } , [ addMessage , allocate , announceGate , applyAppCommand , attachedImages , clearComposer , conversation , eraseDraft , executeCommand , isLoading , login , onSessionProcessing , optionsFor , resetCommandMenuState , scrollToBottom , selectedProject , selectedSession , sendMessage , setIsUserScrolledUp , slashCommands , upload ] ) ;
132133 useEffect ( ( ) => { submitRef . current = handleSubmit ; } , [ handleSubmit ] ) ;
0 commit comments