diff --git a/src/bridge/featureBridge.js b/src/bridge/featureBridge.js index 4dde899..be6d348 100644 --- a/src/bridge/featureBridge.js +++ b/src/bridge/featureBridge.js @@ -29,9 +29,12 @@ module.exports = { ipcMain.handle('settings:shutdown-ollama', async () => await settingsService.shutdownOllama()); // Shortcuts - ipcMain.handle('get-current-shortcuts', async () => await shortcutsService.loadKeybinds()); - ipcMain.handle('get-default-shortcuts', async () => await shortcutsService.handleRestoreDefaults()); - ipcMain.handle('save-shortcuts', async (event, newKeybinds) => await shortcutsService.handleSaveShortcuts(newKeybinds)); + ipcMain.handle('settings:getCurrentShortcuts', async () => await shortcutsService.loadKeybinds()); + ipcMain.handle('shortcut:getDefaultShortcuts', async () => await shortcutsService.handleRestoreDefaults()); + ipcMain.handle('shortcut:closeShortcutSettingsWindow', async () => await shortcutsService.closeShortcutSettingsWindow()); + ipcMain.handle('shortcut:openShortcutSettingsWindow', async () => await shortcutsService.openShortcutSettingsWindow()); + ipcMain.handle('shortcut:saveShortcuts', async (event, newKeybinds) => await shortcutsService.handleSaveShortcuts(newKeybinds)); + ipcMain.handle('shortcut:toggleAllWindowsVisibility', async () => await shortcutsService.toggleAllWindowsVisibility()); // Permissions ipcMain.handle('check-system-permissions', async () => await permissionService.checkSystemPermissions()); diff --git a/src/bridge/windowBridge.js b/src/bridge/windowBridge.js index 6382158..313273c 100644 --- a/src/bridge/windowBridge.js +++ b/src/bridge/windowBridge.js @@ -7,14 +7,13 @@ module.exports = { ipcMain.handle('toggle-content-protection', () => windowManager.toggleContentProtection()); ipcMain.handle('resize-header-window', (event, args) => windowManager.resizeHeaderWindow(args)); ipcMain.handle('get-content-protection-status', () => windowManager.getContentProtectionStatus()); - ipcMain.handle('open-shortcut-editor', () => windowManager.openShortcutEditor()); - ipcMain.on('show-settings-window', (event, bounds) => windowManager.showSettingsWindow(bounds)); + ipcMain.on('show-settings-window', () => windowManager.showSettingsWindow()); ipcMain.on('hide-settings-window', () => windowManager.hideSettingsWindow()); ipcMain.on('cancel-hide-settings-window', () => windowManager.cancelHideSettingsWindow()); + ipcMain.handle('open-login-page', () => windowManager.openLoginPage()); ipcMain.handle('open-personalize-page', () => windowManager.openLoginPage()); ipcMain.handle('move-window-step', (event, direction) => windowManager.moveWindowStep(direction)); - ipcMain.on('close-shortcut-editor', () => windowManager.closeWindow('shortcut-settings')); ipcMain.handle('open-external', (event, url) => shell.openExternal(url)); // Newly moved handlers from windowManager @@ -24,9 +23,6 @@ module.exports = { ipcMain.handle('move-header', (event, newX, newY) => windowManager.moveHeader(newX, newY)); ipcMain.handle('move-header-to', (event, newX, newY) => windowManager.moveHeaderTo(newX, newY)); ipcMain.handle('adjust-window-height', (event, targetHeight) => windowManager.adjustWindowHeight(event.sender, targetHeight)); - ipcMain.handle('toggle-all-windows-visibility', () => windowManager.toggleAllWindowsVisibility()); - // ipcMain.on('animation-finished', (event) => windowManager.handleAnimationFinished(event.sender)); - // ipcMain.handle('ask:closeAskWindow', () => windowManager.closeAskWindow()); }, notifyFocusChange(win, isFocused) { diff --git a/src/features/ask/askService.js b/src/features/ask/askService.js index 78ab69f..4274583 100644 --- a/src/features/ask/askService.js +++ b/src/features/ask/askService.js @@ -3,7 +3,6 @@ const { createStreamingLLM } = require('../common/ai/factory'); // Lazy require helper to avoid circular dependency issues const getWindowManager = () => require('../../window/windowManager'); const internalBridge = require('../../bridge/internalBridge'); -const { EVENTS } = internalBridge; const getWindowPool = () => { try { @@ -162,11 +161,11 @@ class AskService { this._broadcastState(); } else { if (askWindow && askWindow.isVisible()) { - internalBridge.emit('request-window-visibility', { name: 'ask', visible: false }); + internalBridge.emit('window:requestVisibility', { name: 'ask', visible: false }); this.state.isVisible = false; } else { console.log('[AskService] Showing hidden Ask window'); - internalBridge.emit('request-window-visibility', { name: 'ask', visible: true }); + internalBridge.emit('window:requestVisibility', { name: 'ask', visible: true }); this.state.isVisible = true; } if (this.state.isVisible) { @@ -192,7 +191,7 @@ class AskService { }; this._broadcastState(); - internalBridge.emit('request-window-visibility', { name: 'ask', visible: false }); + internalBridge.emit('window:requestVisibility', { name: 'ask', visible: false }); return { success: true }; } @@ -217,7 +216,16 @@ class AskService { * @returns {Promise<{success: boolean, response?: string, error?: string}>} */ async sendMessage(userPrompt, conversationHistoryRaw=[]) { - // ensureAskWindowVisible(); + internalBridge.emit('window:requestVisibility', { name: 'ask', visible: true }); + this.state = { + ...this.state, + isLoading: true, + isStreaming: false, + currentQuestion: userPrompt, + currentResponse: '', + showTextInput: false, + }; + this._broadcastState(); if (this.abortController) { this.abortController.abort('New request received.'); @@ -226,26 +234,10 @@ class AskService { const { signal } = this.abortController; - // if (!userPrompt || userPrompt.trim().length === 0) { - // console.warn('[AskService] Cannot process empty message'); - // return { success: false, error: 'Empty message' }; - // } - - let sessionId; try { console.log(`[AskService] 🤖 Processing message: ${userPrompt.substring(0, 50)}...`); - - this.state = { - ...this.state, - isLoading: true, - isStreaming: false, - currentQuestion: userPrompt, - currentResponse: '', - showTextInput: false, - }; - this._broadcastState(); sessionId = await sessionRepository.getOrCreateActive('ask'); await askRepository.addAiMessage({ sessionId, role: 'user', content: userPrompt.trim() }); diff --git a/src/features/listen/listenService.js b/src/features/listen/listenService.js index 924e9b7..d879d60 100644 --- a/src/features/listen/listenService.js +++ b/src/features/listen/listenService.js @@ -5,7 +5,6 @@ const authService = require('../common/services/authService'); const sessionRepository = require('../common/repositories/session'); const sttRepository = require('./stt/repositories'); const internalBridge = require('../../bridge/internalBridge'); -const { EVENTS } = internalBridge; class ListenService { constructor() { @@ -109,7 +108,7 @@ class ListenService { switch (listenButtonText) { case 'Listen': console.log('[ListenService] changeSession to "Listen"'); - internalBridge.emit('request-window-visibility', { name: 'listen', visible: true }); + internalBridge.emit('window:requestVisibility', { name: 'listen', visible: true }); await this.initializeSession(); listenWindow.webContents.send('session-state-changed', { isActive: true }); break; @@ -122,7 +121,7 @@ class ListenService { case 'Done': console.log('[ListenService] changeSession to "Done"'); - internalBridge.emit('request-window-visibility', { name: 'listen', visible: false }); + internalBridge.emit('window:requestVisibility', { name: 'listen', visible: false }); listenWindow.webContents.send('session-state-changed', { isActive: false }); break; diff --git a/src/features/shortcuts/shortcutsService.js b/src/features/shortcuts/shortcutsService.js index 78e5af2..44ad6e7 100644 --- a/src/features/shortcuts/shortcutsService.js +++ b/src/features/shortcuts/shortcutsService.js @@ -10,6 +10,7 @@ class ShortcutsService { this.mouseEventsIgnored = false; this.movementManager = null; this.windowPool = null; + this.allWindowVisibility = true; } initialize(movementManager, windowPool) { @@ -22,6 +23,41 @@ class ShortcutsService { console.log('[ShortcutsService] Initialized with dependencies and event listener.'); } + async openShortcutSettingsWindow () { + const keybinds = await this.loadKeybinds(); + const shortcutWin = this.windowPool.get('shortcut-settings'); + shortcutWin.webContents.send('shortcut:loadShortcuts', keybinds); + + globalShortcut.unregisterAll(); + internalBridge.emit('window:requestVisibility', { name: 'shortcut-settings', visible: true }); + console.log('[ShortcutsService] Shortcut settings window opened.'); + return { success: true }; + } + + async closeShortcutSettingsWindow () { + await this.registerShortcuts(); + internalBridge.emit('window:requestVisibility', { name: 'shortcut-settings', visible: false }); + console.log('[ShortcutsService] Shortcut settings window closed.'); + return { success: true }; + } + + async handleSaveShortcuts(newKeybinds) { + try { + await this.saveKeybinds(newKeybinds); + await this.closeShortcutSettingsWindow(); + return { success: true }; + } catch (error) { + console.error("Failed to save shortcuts:", error); + await this.closeShortcutSettingsWindow(); + return { success: false, error: error.message }; + } + } + + async handleRestoreDefaults() { + const defaults = this.getDefaultKeybinds(); + return defaults; + } + getDefaultKeybinds() { const isMac = process.platform === 'darwin'; return { @@ -72,32 +108,6 @@ class ShortcutsService { return keybinds; } - async handleSaveShortcuts(newKeybinds) { - try { - await this.saveKeybinds(newKeybinds); - const shortcutEditor = this.windowPool.get('shortcut-settings'); - if (shortcutEditor && !shortcutEditor.isDestroyed()) { - shortcutEditor.close(); // This will trigger re-registration on 'closed' event in windowManager - } else { - // If editor wasn't open, re-register immediately - await this.registerShortcuts(); - } - return { success: true }; - } catch (error) { - console.error("Failed to save shortcuts:", error); - // On failure, re-register old shortcuts to be safe - await this.registerShortcuts(); - return { success: false, error: error.message }; - } - } - - async handleRestoreDefaults() { - const defaults = this.getDefaultKeybinds(); - await this.saveKeybinds(defaults); - await this.registerShortcuts(); - return defaults; - } - async saveKeybinds(newKeybinds) { const keybindsToSave = []; for (const action in newKeybinds) { @@ -112,38 +122,22 @@ class ShortcutsService { console.log(`[Shortcuts] Saved keybinds.`); } - toggleAllWindowsVisibility(windowPool) { - const header = windowPool.get('header'); - if (!header) return; - - if (header.isVisible()) { - this.lastVisibleWindows.clear(); - - windowPool.forEach((win, name) => { - if (win && !win.isDestroyed() && win.isVisible()) { - this.lastVisibleWindows.add(name); - } - }); - - this.lastVisibleWindows.forEach(name => { - if (name === 'header') return; - const win = windowPool.get(name); - if (win && !win.isDestroyed()) win.hide(); - }); - header.hide(); - - return; - } - - this.lastVisibleWindows.forEach(name => { - const win = windowPool.get(name); - if (win && !win.isDestroyed()) { - win.show(); - } + async toggleAllWindowsVisibility() { + const targetVisibility = !this.allWindowVisibility; + internalBridge.emit('window:requestToggleAllWindowsVisibility', { + targetVisibility: targetVisibility }); + + if (this.allWindowVisibility) { + await this.registerShortcuts(true); + } else { + await this.registerShortcuts(); + } + + this.allWindowVisibility = !this.allWindowVisibility; } - async registerShortcuts() { + async registerShortcuts(registerOnlyToggleVisibility = false) { if (!this.movementManager || !this.windowPool) { console.error('[Shortcuts] Service not initialized. Cannot register shortcuts.'); return; @@ -168,6 +162,14 @@ class ShortcutsService { sendToRenderer('shortcuts-updated', keybinds); + if (registerOnlyToggleVisibility) { + if (keybinds.toggleVisibility) { + globalShortcut.register(keybinds.toggleVisibility, () => this.toggleAllWindowsVisibility()); + } + console.log('[Shortcuts] registerOnlyToggleVisibility, only toggleVisibility shortcut is registered.'); + return; + } + // --- Hardcoded shortcuts --- const isMac = process.platform === 'darwin'; const modifier = isMac ? 'Cmd' : 'Ctrl'; @@ -195,7 +197,7 @@ class ShortcutsService { // --- User-configurable shortcuts --- if (header?.currentHeaderState === 'apikey') { if (keybinds.toggleVisibility) { - globalShortcut.register(keybinds.toggleVisibility, () => this.toggleAllWindowsVisibility(this.windowPool)); + globalShortcut.register(keybinds.toggleVisibility, () => this.toggleAllWindowsVisibility()); } console.log('[Shortcuts] ApiKeyHeader is active, only toggleVisibility shortcut is registered.'); return; @@ -208,7 +210,7 @@ class ShortcutsService { let callback; switch(action) { case 'toggleVisibility': - callback = () => this.toggleAllWindowsVisibility(this.windowPool); + callback = () => this.toggleAllWindowsVisibility(); break; case 'nextStep': callback = () => askService.toggleAskButton(true); @@ -282,4 +284,7 @@ class ShortcutsService { } } -module.exports = new ShortcutsService(); \ No newline at end of file + +const shortcutsService = new ShortcutsService(); + +module.exports = shortcutsService; \ No newline at end of file diff --git a/src/preload.js b/src/preload.js index 6121c84..04b1046 100644 --- a/src/preload.js +++ b/src/preload.js @@ -95,11 +95,14 @@ contextBridge.exposeInMainWorld('api', { // Settings Window Management cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'), - showSettingsWindow: (bounds) => ipcRenderer.send('show-settings-window', bounds), + showSettingsWindow: () => ipcRenderer.send('show-settings-window'), hideSettingsWindow: () => ipcRenderer.send('hide-settings-window'), // Generic invoke (for dynamic channel names) - invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args), + // invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args), + sendListenButtonClick: (listenButtonText) => ipcRenderer.invoke('listen:changeSession', listenButtonText), + sendAskButtonClick: () => ipcRenderer.invoke('ask:toggleAskButton'), + sendToggleAllWindowsVisibility: () => ipcRenderer.invoke('shortcut:toggleAllWindowsVisibility'), // Listeners onListenChangeSessionResult: (callback) => ipcRenderer.on('listen:changeSessionResult', callback), @@ -213,8 +216,8 @@ contextBridge.exposeInMainWorld('api', { setAutoUpdate: (isEnabled) => ipcRenderer.invoke('settings:set-auto-update', isEnabled), getContentProtectionStatus: () => ipcRenderer.invoke('get-content-protection-status'), toggleContentProtection: () => ipcRenderer.invoke('toggle-content-protection'), - getCurrentShortcuts: () => ipcRenderer.invoke('get-current-shortcuts'), - openShortcutEditor: () => ipcRenderer.invoke('open-shortcut-editor'), + getCurrentShortcuts: () => ipcRenderer.invoke('settings:getCurrentShortcuts'), + openShortcutSettingsWindow: () => ipcRenderer.invoke('shortcut:openShortcutSettingsWindow'), // Window Management moveWindowStep: (direction) => ipcRenderer.invoke('move-window-step', direction), @@ -245,20 +248,17 @@ contextBridge.exposeInMainWorld('api', { // src/ui/settings/ShortCutSettingsView.js shortcutSettingsView: { // Shortcut Management - saveShortcuts: (shortcuts) => ipcRenderer.invoke('save-shortcuts', shortcuts), - getDefaultShortcuts: () => ipcRenderer.invoke('get-default-shortcuts'), - closeShortcutEditor: () => ipcRenderer.send('close-shortcut-editor'), + saveShortcuts: (shortcuts) => ipcRenderer.invoke('shortcut:saveShortcuts', shortcuts), + getDefaultShortcuts: () => ipcRenderer.invoke('shortcut:getDefaultShortcuts'), + closeShortcutSettingsWindow: () => ipcRenderer.invoke('shortcut:closeShortcutSettingsWindow'), // Listeners - onLoadShortcuts: (callback) => ipcRenderer.on('load-shortcuts', callback), - removeOnLoadShortcuts: (callback) => ipcRenderer.removeListener('load-shortcuts', callback) + onLoadShortcuts: (callback) => ipcRenderer.on('shortcut:loadShortcuts', callback), + removeOnLoadShortcuts: (callback) => ipcRenderer.removeListener('shortcut:loadShortcuts', callback) }, // src/ui/app/content.html inline scripts content: { - // Animation Management - // sendAnimationFinished: () => ipcRenderer.send('animation-finished'), - // Listeners onSettingsWindowHideAnimation: (callback) => ipcRenderer.on('settings-window-hide-animation', callback), removeOnSettingsWindowHideAnimation: (callback) => ipcRenderer.removeListener('settings-window-hide-animation', callback), diff --git a/src/ui/app/MainHeader.js b/src/ui/app/MainHeader.js index 1ace9ea..175c71d 100644 --- a/src/ui/app/MainHeader.js +++ b/src/ui/app/MainHeader.js @@ -2,7 +2,6 @@ import { html, css, LitElement } from '../assets/lit-core-2.7.4.min.js'; export class MainHeader extends LitElement { static properties = { - // isSessionActive: { type: Boolean, state: true }, isTogglingSession: { type: Boolean, state: true }, shortcuts: { type: Object, state: true }, listenSessionStatus: { type: String, state: true }, @@ -515,30 +514,12 @@ export class MainHeader extends LitElement { } } - invoke(channel, ...args) { - if (this.wasJustDragged) return; - if (window.api) { - window.api.mainHeader.invoke(channel, ...args); - } - // return Promise.resolve(); - } - showSettingsWindow(element) { if (this.wasJustDragged) return; if (window.api) { console.log(`[MainHeader] showSettingsWindow called at ${Date.now()}`); - - window.api.mainHeader.cancelHideSettingsWindow(); + window.api.mainHeader.showSettingsWindow(); - if (element) { - const { left, top, width, height } = element.getBoundingClientRect(); - window.api.mainHeader.showSettingsWindow({ - x: left, - y: top, - width, - height, - }); - } } } @@ -559,9 +540,10 @@ export class MainHeader extends LitElement { this.isTogglingSession = true; try { - const channel = 'listen:changeSession'; const listenButtonText = this._getListenButtonText(this.listenSessionStatus); - await this.invoke(channel, listenButtonText); + if (window.api) { + await window.api.mainHeader.sendListenButtonClick(listenButtonText); + } } catch (error) { console.error('IPC invoke for session change failed:', error); this.isTogglingSession = false; @@ -572,13 +554,26 @@ export class MainHeader extends LitElement { if (this.wasJustDragged) return; try { - const channel = 'ask:toggleAskButton'; - await this.invoke(channel); + if (window.api) { + await window.api.mainHeader.sendAskButtonClick(); + } } catch (error) { console.error('IPC invoke for ask button failed:', error); } } + async _handleToggleAllWindowsVisibility() { + if (this.wasJustDragged) return; + + try { + if (window.api) { + await window.api.mainHeader.sendToggleAllWindowsVisibility(); + } + } catch (error) { + console.error('IPC invoke for all windows visibility button failed:', error); + } + } + renderShortcut(accelerator) { if (!accelerator) return html``; @@ -656,7 +651,7 @@ export class MainHeader extends LitElement { -