11import { useCallback , useEffect , useMemo , useReducer , useRef } from 'react' ;
2+ import { defineMessages , useIntl } from '../i18n' ;
23import { v7 as uuidv7 } from 'uuid' ;
34import { AppEvents } from '../constants/events' ;
45import { ChatState } from '../types/chatState' ;
@@ -227,7 +228,7 @@ function createEventProcessor(
227228 dispatch : React . Dispatch < StreamAction > ,
228229 onFinish : ( error ?: string ) => void ,
229230 sessionId : string ,
230- onReloadNeeded ?: ( ) => void ,
231+ onReloadNeeded ?: ( ) => void
231232) {
232233 let currentMessages = initialMessages ;
233234 const reduceMotion = prefersReducedMotion ( ) ;
@@ -343,11 +344,23 @@ function createEventProcessor(
343344 return processEvent ;
344345}
345346
347+ const i18n = defineMessages ( {
348+ notificationTitle : {
349+ id : 'chat.notification.taskComplete.title' ,
350+ defaultMessage : 'Goose finished the task.' ,
351+ } ,
352+ notificationBody : {
353+ id : 'chat.notification.taskComplete.body' ,
354+ defaultMessage : 'Click here to bring Goose back into focus.' ,
355+ } ,
356+ } ) ;
357+
346358export function useChatStream ( {
347359 sessionId,
348360 onStreamFinish,
349361 onSessionLoaded,
350362} : UseChatStreamProps ) : UseChatStreamReturn {
363+ const intl = useIntl ( ) ;
351364 const [ state , dispatch ] = useReducer ( streamReducer , initialState ) ;
352365
353366 // Long-lived SSE connection for this session
@@ -358,7 +371,6 @@ export function useChatStream({
358371 const activeRequestSessionIdRef = useRef < string | null > ( null ) ;
359372 const activeAbortRef = useRef < AbortController | null > ( null ) ;
360373 const activeUnsubscribeRef = useRef < ( ( ) => void ) | null > ( null ) ;
361- const lastInteractionTimeRef = useRef < number > ( Date . now ( ) ) ;
362374 // When ActiveRequests fires before resumeAgent populates messages (cold mount),
363375 // defer the reattach until the session is loaded so the event processor has
364376 // the full conversation history. Events are buffered in the meantime.
@@ -399,12 +411,21 @@ export function useChatStream({
399411
400412 dispatch ( { type : 'STREAM_FINISH' , payload : error } ) ;
401413
402- const timeSinceLastInteraction = Date . now ( ) - lastInteractionTimeRef . current ;
403- if ( ! error && timeSinceLastInteraction > 60000 ) {
404- window . electron . showNotification ( {
405- title : 'goose finished the task.' ,
406- body : 'Click here to expand.' ,
407- } ) ;
414+ if ( ! error ) {
415+ try {
416+ const [ notificationsEnabled , anyWindowFocused ] = await Promise . all ( [
417+ window . electron . getSetting ( 'enableNotifications' ) ,
418+ window . electron . isAnyWindowFocused ( ) ,
419+ ] ) ;
420+ if ( notificationsEnabled === true && ! anyWindowFocused ) {
421+ window . electron . showNotification ( {
422+ title : intl . formatMessage ( i18n . notificationTitle ) ,
423+ body : intl . formatMessage ( i18n . notificationBody ) ,
424+ } ) ;
425+ }
426+ } catch ( notifyError ) {
427+ console . warn ( 'Failed to show task completion notification:' , notifyError ) ;
428+ }
408429 }
409430
410431 const isNewSession = sessionId && sessionId . match ( / ^ \d { 8 } _ \d { 6 } $ / ) ;
@@ -444,7 +465,7 @@ export function useChatStream({
444465
445466 onStreamFinish ( ) ;
446467 } ,
447- [ onStreamFinish , sessionId ]
468+ [ intl , onStreamFinish , sessionId ]
448469 ) ;
449470
450471 // Reload the full conversation from the server, e.g. after the SSE
@@ -453,14 +474,16 @@ export function useChatStream({
453474 getSession ( {
454475 path : { session_id : sessionId } ,
455476 throwOnError : true ,
456- } ) . then ( ( response ) => {
457- const session = response . data as Session ;
458- if ( session ?. conversation ) {
459- dispatch ( { type : 'SET_MESSAGES' , payload : session . conversation } ) ;
460- }
461- } ) . catch ( ( e ) => {
462- console . warn ( 'Failed to reload conversation after buffer overflow:' , e ) ;
463- } ) ;
477+ } )
478+ . then ( ( response ) => {
479+ const session = response . data as Session ;
480+ if ( session ?. conversation ) {
481+ dispatch ( { type : 'SET_MESSAGES' , payload : session . conversation } ) ;
482+ }
483+ } )
484+ . catch ( ( e ) => {
485+ console . warn ( 'Failed to reload conversation after buffer overflow:' , e ) ;
486+ } ) ;
464487 } , [ sessionId ] ) ;
465488
466489 // Perform the actual reattach: wire up an event processor and listener
@@ -479,7 +502,7 @@ export function useChatStream({
479502 dispatch ,
480503 onFinish ,
481504 sessionId ,
482- reloadConversation ,
505+ reloadConversation
483506 ) ;
484507
485508 // Replay any events that were buffered during cold-mount wait
@@ -523,7 +546,7 @@ export function useChatStream({
523546 } ) ;
524547 activeUnsubscribeRef . current = unsubscribe ;
525548 } ,
526- [ sessionId , addListener , onFinish , reloadConversation ] ,
549+ [ sessionId , addListener , onFinish , reloadConversation ]
527550 ) ;
528551 doReattachRef . current = doReattach ;
529552
@@ -582,7 +605,7 @@ export function useChatStream({
582605 targetSessionId : string ,
583606 userMessage : Message ,
584607 currentMessages : Message [ ] ,
585- overrideConversation ?: Message [ ] ,
608+ overrideConversation ?: Message [ ]
586609 ) => {
587610 const requestId = uuidv7 ( ) ;
588611 const abortController = new AbortController ( ) ;
@@ -596,7 +619,7 @@ export function useChatStream({
596619 dispatch ,
597620 onFinish ,
598621 targetSessionId ,
599- reloadConversation ,
622+ reloadConversation
600623 ) ;
601624
602625 const unsubscribe = addListener ( requestId , ( event ) => {
@@ -801,8 +824,6 @@ export function useChatStream({
801824 return ;
802825 }
803826
804- lastInteractionTimeRef . current = Date . now ( ) ;
805-
806827 // Emit session-created event for first message in a new session
807828 if ( ! hasExistingMessages && hasNewMessage ) {
808829 window . dispatchEvent ( new CustomEvent ( AppEvents . SESSION_CREATED ) ) ;
@@ -876,8 +897,6 @@ export function useChatStream({
876897 return ;
877898 }
878899
879- lastInteractionTimeRef . current = Date . now ( ) ;
880-
881900 const responseMessage = createElicitationResponseMessage ( elicitationId , userData ) ;
882901 const currentMessages = [ ...currentState . messages , responseMessage ] ;
883902
@@ -961,7 +980,6 @@ export function useChatStream({
961980 activeRequestSessionIdRef . current = null ;
962981
963982 dispatch ( { type : 'SET_CHAT_STATE' , payload : ChatState . Idle } ) ;
964- lastInteractionTimeRef . current = Date . now ( ) ;
965983 } , [ ] ) ;
966984
967985 const onMessageUpdate = useCallback (
0 commit comments