From 115ae0a91411ce2c6c427bdee14dfc6edc2061bc Mon Sep 17 00:00:00 2001 From: Zane Staggs Date: Mon, 28 Jul 2025 16:39:38 -0700 Subject: [PATCH 1/2] change power save blocker to automatically stop and cleanup after 15 minutes of inactivity and have per window ids for better cleanup --- ui/desktop/src/hooks/useChatEngine.ts | 62 ++++++++++++++++--- ui/desktop/src/main.ts | 87 ++++++++++++++++++++++----- 2 files changed, 125 insertions(+), 24 deletions(-) diff --git a/ui/desktop/src/hooks/useChatEngine.ts b/ui/desktop/src/hooks/useChatEngine.ts index 512bffd091c4..9642169a6696 100644 --- a/ui/desktop/src/hooks/useChatEngine.ts +++ b/ui/desktop/src/hooks/useChatEngine.ts @@ -49,6 +49,7 @@ export const useChatEngine = ({ const [sessionOutputTokens, setSessionOutputTokens] = useState(0); const [localInputTokens, setLocalInputTokens] = useState(0); const [localOutputTokens, setLocalOutputTokens] = useState(0); + const [powerSaveTimeoutId, setPowerSaveTimeoutId] = useState(null); // Store message in global history when it's added (if enabled) const storeMessageInHistory = useCallback( @@ -63,6 +64,20 @@ export const useChatEngine = ({ [enableLocalStorage] ); + const stopPowerSaveBlocker = useCallback(() => { + try { + window.electron.stopPowerSaveBlocker(); + } catch (error) { + console.error('Failed to stop power save blocker:', error); + } + + // Clear timeout if it exists + if (powerSaveTimeoutId) { + window.clearTimeout(powerSaveTimeoutId); + setPowerSaveTimeoutId(null); + } + }, [powerSaveTimeoutId]); + const { messages, append: originalAppend, @@ -84,7 +99,7 @@ export const useChatEngine = ({ initialMessages: chat.messages, body: { session_id: chat.id, session_working_dir: window.appConfig.get('GOOSE_WORKING_DIR') }, onFinish: async (_message, _reason) => { - window.electron.stopPowerSaveBlocker(); + stopPowerSaveBlocker(); const timeSinceLastInteraction = Date.now() - lastInteractionTime; window.electron.logInfo('last interaction:' + lastInteractionTime); @@ -110,6 +125,8 @@ export const useChatEngine = ({ onMessageStreamFinish?.(); }, onError: (error) => { + stopPowerSaveBlocker(); + console.log( 'CHAT ENGINE RECEIVED ERROR FROM MESSAGE STREAM:', JSON.stringify( @@ -206,40 +223,67 @@ export const useChatEngine = ({ } }, [sessionMetadata]); + useEffect(() => { + return () => { + if (powerSaveTimeoutId) { + window.clearTimeout(powerSaveTimeoutId); + } + try { + window.electron.stopPowerSaveBlocker(); + } catch (error) { + console.error('Failed to stop power save blocker during cleanup:', error); + } + }; + }, [powerSaveTimeoutId]); + // Handle submit const handleSubmit = useCallback( (combinedTextFromInput: string, onSummaryReset?: () => void) => { if (combinedTextFromInput.trim()) { - window.electron.startPowerSaveBlocker(); + try { + window.electron.startPowerSaveBlocker(); + } catch (error) { + console.error('Failed to start power save blocker:', error); + } + setLastInteractionTime(Date.now()); + // Set a timeout to automatically stop the power save blocker after 15 minutes + const timeoutId = window.setTimeout( + () => { + console.warn('Power save blocker timeout - stopping automatically after 15 minutes'); + stopPowerSaveBlocker(); + }, + 15 * 60 * 1000 + ); // 15 minutes + + setPowerSaveTimeoutId(timeoutId); + const userMessage = createUserMessage(combinedTextFromInput.trim()); if (onSummaryReset) { onSummaryReset(); - setTimeout(() => { + window.setTimeout(() => { append(userMessage); - // Call onMessageSent after the message is sent onMessageSent?.(); }, 150); } else { append(userMessage); - // Call onMessageSent after the message is sent onMessageSent?.(); } } else { // If nothing was actually submitted (e.g. empty input and no images pasted) - window.electron.stopPowerSaveBlocker(); + stopPowerSaveBlocker(); } }, - [append, onMessageSent] + [append, onMessageSent, stopPowerSaveBlocker] ); // Handle stopping the message stream const onStopGoose = useCallback(() => { stop(); setLastInteractionTime(Date.now()); - window.electron.stopPowerSaveBlocker(); + stopPowerSaveBlocker(); // Handle stopping the message stream const lastMessage = messages[messages.length - 1]; @@ -325,7 +369,7 @@ export const useChatEngine = ({ setMessages([...messages, responseMessage]); } } - }, [stop, messages, _setInput, setMessages]); + }, [stop, messages, _setInput, setMessages, stopPowerSaveBlocker]); const filteredMessages = useMemo(() => { return [...ancestorMessages, ...messages].filter((message) => message.display ?? true); diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 1eb396c4d345..425c64fe799b 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -506,8 +506,8 @@ let appConfig = { let windowCounter = 0; const windowMap = new Map(); -// Track power save blocker ID globally -let powerSaveBlockerId: number | null = null; +// Track power save blockers per window +const windowPowerSaveBlockers = new Map(); // windowId -> blockerId const createChat = async ( app: App, @@ -822,6 +822,23 @@ const createChat = async ( // Handle window closure mainWindow.on('closed', () => { windowMap.delete(windowId); + + if (windowPowerSaveBlockers.has(windowId)) { + const blockerId = windowPowerSaveBlockers.get(windowId)!; + try { + powerSaveBlocker.stop(blockerId); + console.log( + `[Main] Stopped power save blocker ${blockerId} for closing window ${windowId}` + ); + } catch (error) { + console.error( + `[Main] Failed to stop power save blocker ${blockerId} for window ${windowId}:`, + error + ); + } + windowPowerSaveBlockers.delete(windowId); + } + if (goosedProcess && typeof goosedProcess === 'object' && 'kill' in goosedProcess) { goosedProcess.kill(); } @@ -1201,10 +1218,22 @@ ipcMain.handle('set-wakelock', async (_event, enable: boolean) => { settings.enableWakelock = enable; saveSettings(settings); - // Stop any existing power save blocker when disabling the setting - if (!enable && powerSaveBlockerId !== null) { - powerSaveBlocker.stop(powerSaveBlockerId); - powerSaveBlockerId = null; + // Stop all existing power save blockers when disabling the setting + if (!enable) { + for (const [windowId, blockerId] of windowPowerSaveBlockers.entries()) { + try { + powerSaveBlocker.stop(blockerId); + console.log( + `[Main] Stopped power save blocker ${blockerId} for window ${windowId} due to wakelock setting disabled` + ); + } catch (error) { + console.error( + `[Main] Failed to stop power save blocker ${blockerId} for window ${windowId}:`, + error + ); + } + } + windowPowerSaveBlockers.clear(); } return true; @@ -2027,21 +2056,36 @@ app.whenReady().then(async () => { } }); - ipcMain.handle('start-power-save-blocker', () => { - if (powerSaveBlockerId === null) { - powerSaveBlockerId = powerSaveBlocker.start('prevent-app-suspension'); + ipcMain.handle('start-power-save-blocker', (event) => { + const window = BrowserWindow.fromWebContents(event.sender); + const windowId = window?.id; + + if (windowId && !windowPowerSaveBlockers.has(windowId)) { + const blockerId = powerSaveBlocker.start('prevent-app-suspension'); + windowPowerSaveBlockers.set(windowId, blockerId); + console.log(`[Main] Started power save blocker ${blockerId} for window ${windowId}`); return true; } + if (windowId && windowPowerSaveBlockers.has(windowId)) { + console.log(`[Main] Power save blocker already active for window ${windowId}`); + } + return false; }); - ipcMain.handle('stop-power-save-blocker', () => { - if (powerSaveBlockerId !== null) { - powerSaveBlocker.stop(powerSaveBlockerId); - powerSaveBlockerId = null; + ipcMain.handle('stop-power-save-blocker', (event) => { + const window = BrowserWindow.fromWebContents(event.sender); + const windowId = window?.id; + + if (windowId && windowPowerSaveBlockers.has(windowId)) { + const blockerId = windowPowerSaveBlockers.get(windowId)!; + powerSaveBlocker.stop(blockerId); + windowPowerSaveBlockers.delete(windowId); + console.log(`[Main] Stopped power save blocker ${blockerId} for window ${windowId}`); return true; } + return false; }); @@ -2154,11 +2198,24 @@ async function getAllowList(): Promise { } app.on('will-quit', async () => { + for (const [windowId, blockerId] of windowPowerSaveBlockers.entries()) { + try { + powerSaveBlocker.stop(blockerId); + console.log( + `[Main] Stopped power save blocker ${blockerId} for window ${windowId} during app quit` + ); + } catch (error) { + console.error( + `[Main] Failed to stop power save blocker ${blockerId} for window ${windowId}:`, + error + ); + } + } + windowPowerSaveBlockers.clear(); + // Unregister all shortcuts when quitting globalShortcut.unregisterAll(); - // Clean up the temp directory on app quit - console.log('[Main] App "will-quit". Cleaning up temporary image directory...'); try { await fs.access(gooseTempDir); // Check if directory exists to avoid error on fs.rm if it doesn't From f11b312af12beeb06e50bc42f4d3990a58d1e32f Mon Sep 17 00:00:00 2001 From: Zane <75694352+zanesq@users.noreply.github.com> Date: Wed, 30 Jul 2025 10:49:08 -0700 Subject: [PATCH 2/2] Remove unneeded comment --- ui/desktop/src/hooks/useChatEngine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/desktop/src/hooks/useChatEngine.ts b/ui/desktop/src/hooks/useChatEngine.ts index 9642169a6696..60751a1bb5e6 100644 --- a/ui/desktop/src/hooks/useChatEngine.ts +++ b/ui/desktop/src/hooks/useChatEngine.ts @@ -255,7 +255,7 @@ export const useChatEngine = ({ stopPowerSaveBlocker(); }, 15 * 60 * 1000 - ); // 15 minutes + ); setPowerSaveTimeoutId(timeoutId);