diff --git a/aec b/aec index 3be088c..f00bb1f 160000 --- a/aec +++ b/aec @@ -1 +1 @@ -Subproject commit 3be088c6cff8021c74eca714150e68e2cc74bee0 +Subproject commit f00bb1fb948053c752b916adfee19f90644a0b2f diff --git a/src/bridge/featureBridge.js b/src/bridge/featureBridge.js new file mode 100644 index 0000000..3cce84b --- /dev/null +++ b/src/bridge/featureBridge.js @@ -0,0 +1,71 @@ +// src/bridge/featureBridge.js +const { ipcMain } = require('electron'); +const settingsService = require('../features/settings/settingsService'); + +module.exports = { + // Renderer로부터의 요청을 수신 + initialize() { + // 기존 ask 핸들러 유지 + ipcMain.handle('feature:ask', (e, query) => { + // 실제로는 여기서 Controller -> Service 로직 수행 + return `"${query}"에 대한 답변입니다.`; + }); + + // settings 관련 핸들러 추가 + ipcMain.handle('settings:getSettings', async () => { + return await settingsService.getSettings(); + }); + + ipcMain.handle('settings:saveSettings', async (event, settings) => { + return await settingsService.saveSettings(settings); + }); + + ipcMain.handle('settings:getPresets', async () => { + return await settingsService.getPresets(); + }); + + ipcMain.handle('settings:getPresetTemplates', async () => { + return await settingsService.getPresetTemplates(); + }); + + ipcMain.handle('settings:createPreset', async (event, title, prompt) => { + return await settingsService.createPreset(title, prompt); + }); + + ipcMain.handle('settings:updatePreset', async (event, id, title, prompt) => { + return await settingsService.updatePreset(id, title, prompt); + }); + + ipcMain.handle('settings:deletePreset', async (event, id) => { + return await settingsService.deletePreset(id); + }); + + ipcMain.handle('settings:saveApiKey', async (event, apiKey, provider) => { + return await settingsService.saveApiKey(apiKey, provider); + }); + + ipcMain.handle('settings:removeApiKey', async () => { + return await settingsService.removeApiKey(); + }); + + ipcMain.handle('settings:updateContentProtection', async (event, enabled) => { + return await settingsService.updateContentProtection(enabled); + }); + + ipcMain.handle('settings:get-auto-update', async () => { + return await settingsService.getAutoUpdateSetting(); + }); + + ipcMain.handle('settings:set-auto-update', async (event, isEnabled) => { + console.log('[SettingsService] Setting auto update setting:', isEnabled); + return await settingsService.setAutoUpdateSetting(isEnabled); + }); + + console.log('[FeatureBridge] Initialized with settings handlers.'); + }, + + // Renderer로 상태를 전송 + sendAskProgress(win, progress) { + win.webContents.send('feature:ask:progress', progress); + }, +}; \ No newline at end of file diff --git a/src/bridge/internalBridge.js b/src/bridge/internalBridge.js new file mode 100644 index 0000000..28c41cf --- /dev/null +++ b/src/bridge/internalBridge.js @@ -0,0 +1,10 @@ +// src/bridge/internalBridge.js +const { EventEmitter } = require('events'); + +// FeatureCore와 WindowCore를 잇는 내부 이벤트 버스 +module.exports = new EventEmitter(); + +// 예시 이벤트 +internalBridge.on('content-protection-changed', (enabled) => { + // windowManager에서 처리 +}); \ No newline at end of file diff --git a/src/bridge/windowBridge.js b/src/bridge/windowBridge.js new file mode 100644 index 0000000..15aa91f --- /dev/null +++ b/src/bridge/windowBridge.js @@ -0,0 +1,74 @@ +// src/bridge/windowBridge.js +const { ipcMain, BrowserWindow } = require('electron'); +const { windowPool, settingsHideTimer, app, shell } = require('../electron/windowManager'); // 필요 변수 require + +module.exports = { + // Renderer로부터의 요청을 수신 + initialize() { + // 기존 + ipcMain.on('window:hide', (e) => BrowserWindow.fromWebContents(e.sender)?.hide()); + + // windowManager 관련 추가 + ipcMain.handle('toggle-content-protection', () => { + // windowManager의 toggle-content-protection 로직 + isContentProtectionOn = !isContentProtectionOn; + windowPool.forEach(win => { + if (win && !win.isDestroyed()) { + win.setContentProtection(isContentProtectionOn); + } + }); + return isContentProtectionOn; + }); + + ipcMain.handle('get-content-protection-status', () => { + return isContentProtectionOn; + }); + + ipcMain.handle('open-shortcut-editor', () => { + // open-shortcut-editor 로직 (windowPool 등 필요시 require) + const header = windowPool.get('header'); + if (!header) return; + globalShortcut.unregisterAll(); + createFeatureWindows(header, 'shortcut-settings'); + }); + + // 다른 관련 핸들러 추가 (quit-application, etc.) + ipcMain.handle('quit-application', () => { + app.quit(); + }); + + // 추가: show-settings-window + ipcMain.on('show-settings-window', (event, bounds) => { + if (!bounds) return; + const win = windowPool.get('settings'); + if (win && !win.isDestroyed()) { + if (settingsHideTimer) clearTimeout(settingsHideTimer); + // 위치 조정 로직 (기존 복사) + const header = windowPool.get('header'); + const headerBounds = header?.getBounds() ?? { x: 0, y: 0 }; + const settingsBounds = win.getBounds(); + const disp = getCurrentDisplay(header); + const { x: waX, y: waY, width: waW, height: waH } = disp.workArea; + let x = Math.round(headerBounds.x + (bounds?.x ?? 0) + (bounds?.width ?? 0) / 2 - settingsBounds.width / 2); + let y = Math.round(headerBounds.y + (bounds?.y ?? 0) + (bounds?.height ?? 0) + 31); + x = Math.max(waX + 10, Math.min(waX + waW - settingsBounds.width - 10, x)); + y = Math.max(waY + 10, Math.min(waY + waH - settingsBounds.height - 10, y)); + win.setBounds({ x, y }); + win.__lockedByButton = true; + win.show(); + win.moveTop(); + win.setAlwaysOnTop(true); + } + }); + + // 추가: hide-settings-window 등 다른 핸들러 복사 + // ... (hide-settings-window, cancel-hide-settings-window, quit-application, open-login-page, firebase-logout, move-window-step 등) + + // 예: ipcMain.handle('open-login-page', () => { shell.openExternal(...); }); + }, + + // Renderer로 상태를 전송 + notifyFocusChange(win, isFocused) { + win.webContents.send('window:focus-change', isFocused); + }, +}; \ No newline at end of file diff --git a/src/electron/windowManager.js b/src/electron/windowManager.js index c4bbd81..57f2eca 100644 --- a/src/electron/windowManager.js +++ b/src/electron/windowManager.js @@ -76,6 +76,7 @@ function updateLayout() { } let movementManager = null; +const windowBridge = require('../bridge/windowBridge'); async function toggleFeature(featureName) { diff --git a/src/features/settings/SettingsView.js b/src/features/settings/SettingsView.js index c0b40ea..a2339ad 100644 --- a/src/features/settings/SettingsView.js +++ b/src/features/settings/SettingsView.js @@ -543,11 +543,9 @@ export class SettingsView extends LitElement { } async loadAutoUpdateSetting() { - if (!window.require) return; - const { ipcRenderer } = window.require('electron'); this.autoUpdateLoading = true; try { - const enabled = await ipcRenderer.invoke('settings:get-auto-update'); + const enabled = await window.api.feature.settings.getAutoUpdate(); this.autoUpdateEnabled = enabled; console.log('Auto-update setting loaded:', enabled); } catch (e) { @@ -559,13 +557,12 @@ export class SettingsView extends LitElement { } async handleToggleAutoUpdate() { - if (!window.require || this.autoUpdateLoading) return; - const { ipcRenderer } = window.require('electron'); + if (this.autoUpdateLoading) return; this.autoUpdateLoading = true; this.requestUpdate(); try { const newValue = !this.autoUpdateEnabled; - const result = await ipcRenderer.invoke('settings:set-auto-update', newValue); + const result = await window.api.feature.settings.setAutoUpdate(newValue); if (result && result.success) { this.autoUpdateEnabled = newValue; } else { @@ -580,22 +577,20 @@ export class SettingsView extends LitElement { //////// after_modelStateService //////// async loadInitialData() { - if (!window.require) return; this.isLoading = true; - const { ipcRenderer } = window.require('electron'); try { const [userState, config, storedKeys, availableLlm, availableStt, selectedModels, presets, contentProtection, shortcuts, ollamaStatus, whisperModelsResult] = await Promise.all([ - ipcRenderer.invoke('get-current-user'), - ipcRenderer.invoke('model:get-provider-config'), // Provider 설정 로드 - ipcRenderer.invoke('model:get-all-keys'), - ipcRenderer.invoke('model:get-available-models', { type: 'llm' }), - ipcRenderer.invoke('model:get-available-models', { type: 'stt' }), - ipcRenderer.invoke('model:get-selected-models'), - ipcRenderer.invoke('settings:getPresets'), - ipcRenderer.invoke('get-content-protection-status'), - ipcRenderer.invoke('get-current-shortcuts'), - ipcRenderer.invoke('ollama:get-status'), - ipcRenderer.invoke('whisper:get-installed-models') + window.api.feature.settings.getCurrentUser(), + window.api.feature.settings.getProviderConfig(), // Provider 설정 로드 + window.api.feature.settings.getAllKeys(), + window.api.feature.settings.getAvailableModels({ type: 'llm' }), + window.api.feature.settings.getAvailableModels({ type: 'stt' }), + window.api.feature.settings.getSelectedModels(), + window.api.feature.settings.getPresets(), + window.api.feature.settings.getContentProtectionStatus(), + window.api.feature.settings.getCurrentShortcuts(), + window.api.feature.settings.getOllamaStatus(), + window.api.feature.settings.getWhisperInstalledModels() ]); if (userState && userState.isLoggedIn) this.firebaseUser = userState; @@ -644,10 +639,9 @@ export class SettingsView extends LitElement { // For Ollama, we need to ensure it's ready first if (provider === 'ollama') { this.saving = true; - const { ipcRenderer } = window.require('electron'); // First ensure Ollama is installed and running - const ensureResult = await ipcRenderer.invoke('ollama:ensure-ready'); + const ensureResult = await window.api.feature.settings.ensureOllamaReady(); if (!ensureResult.success) { alert(`Failed to setup Ollama: ${ensureResult.error}`); this.saving = false; @@ -655,7 +649,7 @@ export class SettingsView extends LitElement { } // Now validate (which will check if service is running) - const result = await ipcRenderer.invoke('model:validate-key', { provider, key: 'local' }); + const result = await window.api.feature.settings.validateKey({ provider, key: 'local' }); if (result.success) { this.apiKeys = { ...this.apiKeys, [provider]: 'local' }; @@ -671,8 +665,7 @@ export class SettingsView extends LitElement { // For Whisper, just enable it if (provider === 'whisper') { this.saving = true; - const { ipcRenderer } = window.require('electron'); - const result = await ipcRenderer.invoke('model:validate-key', { provider, key: 'local' }); + const result = await window.api.feature.settings.validateKey({ provider, key: 'local' }); if (result.success) { this.apiKeys = { ...this.apiKeys, [provider]: 'local' }; @@ -686,8 +679,7 @@ export class SettingsView extends LitElement { // For other providers, use the normal flow this.saving = true; - const { ipcRenderer } = window.require('electron'); - const result = await ipcRenderer.invoke('model:validate-key', { provider, key }); + const result = await window.api.feature.settings.validateKey({ provider, key }); if (result.success) { this.apiKeys = { ...this.apiKeys, [provider]: key }; @@ -701,20 +693,18 @@ export class SettingsView extends LitElement { async handleClearKey(provider) { this.saving = true; - const { ipcRenderer } = window.require('electron'); - await ipcRenderer.invoke('model:remove-api-key', { provider }); + await window.api.feature.settings.removeApiKey({ provider }); this.apiKeys = { ...this.apiKeys, [provider]: '' }; await this.refreshModelData(); this.saving = false; } async refreshModelData() { - const { ipcRenderer } = window.require('electron'); const [availableLlm, availableStt, selected, storedKeys] = await Promise.all([ - ipcRenderer.invoke('model:get-available-models', { type: 'llm' }), - ipcRenderer.invoke('model:get-available-models', { type: 'stt' }), - ipcRenderer.invoke('model:get-selected-models'), - ipcRenderer.invoke('model:get-all-keys') + window.api.feature.settings.getAvailableModels({ type: 'llm' }), + window.api.feature.settings.getAvailableModels({ type: 'stt' }), + window.api.feature.settings.getSelectedModels(), + window.api.feature.settings.getAllKeys() ]); this.availableLlmModels = availableLlm; this.availableSttModels = availableStt; @@ -765,8 +755,7 @@ export class SettingsView extends LitElement { } this.saving = true; - const { ipcRenderer } = window.require('electron'); - await ipcRenderer.invoke('model:set-selected-model', { type, modelId }); + await window.api.feature.settings.setSelectedModel({ type, modelId }); if (type === 'llm') this.selectedLlm = modelId; if (type === 'stt') this.selectedStt = modelId; this.isLlmListVisible = false; @@ -776,8 +765,7 @@ export class SettingsView extends LitElement { } async refreshOllamaStatus() { - const { ipcRenderer } = window.require('electron'); - const ollamaStatus = await ipcRenderer.invoke('ollama:get-status'); + const ollamaStatus = await window.api.feature.settings.getOllamaStatus(); if (ollamaStatus?.success) { this.ollamaStatus = { installed: ollamaStatus.installed, running: ollamaStatus.running }; this.ollamaModels = ollamaStatus.models || []; @@ -821,8 +809,6 @@ export class SettingsView extends LitElement { this.requestUpdate(); try { - const { ipcRenderer } = window.require('electron'); - // Set up progress listener const progressHandler = (event, { modelId: id, progress }) => { if (id === modelId) { @@ -831,10 +817,10 @@ export class SettingsView extends LitElement { } }; - ipcRenderer.on('whisper:download-progress', progressHandler); + window.api.feature.settings.onWhisperDownloadProgress(progressHandler); // Start download - const result = await ipcRenderer.invoke('whisper:download-model', modelId); + const result = await window.api.feature.settings.downloadWhisperModel(modelId); if (result.success) { // Auto-select the model after download @@ -844,7 +830,7 @@ export class SettingsView extends LitElement { } // Cleanup - ipcRenderer.removeListener('whisper:download-progress', progressHandler); + window.api.feature.settings.removeOnWhisperDownloadProgress(progressHandler); } catch (error) { console.error(`[SettingsView] Error downloading Whisper model ${modelId}:`, error); alert(`Error downloading ${modelId}: ${error.message}`); @@ -876,17 +862,12 @@ export class SettingsView extends LitElement { if (this.wasJustDragged) return console.log("Requesting Firebase authentication from main process...") - if (window.require) { - window.require("electron").ipcRenderer.invoke("start-firebase-auth") - } - } + window.api.feature.settings.startFirebaseAuth(); + } //////// after_modelStateService //////// openShortcutEditor() { - if (window.require) { - const { ipcRenderer } = window.require('electron'); - ipcRenderer.invoke('open-shortcut-editor'); - } + window.api.feature.settings.openShortcutEditor(); } connectedCallback() { @@ -924,10 +905,6 @@ export class SettingsView extends LitElement { } setupIpcListeners() { - if (!window.require) return; - - const { ipcRenderer } = window.require('electron'); - this._userStateListener = (event, userState) => { console.log('[SettingsView] Received user-state-changed:', userState); if (userState && userState.isLoggedIn) { @@ -949,7 +926,7 @@ export class SettingsView extends LitElement { this._presetsUpdatedListener = async (event) => { console.log('[SettingsView] Received presets-updated, refreshing presets'); try { - const presets = await ipcRenderer.invoke('settings:getPresets'); + const presets = await window.api.feature.settings.getPresets(); this.presets = presets || []; // 현재 선택된 프리셋이 삭제되었는지 확인 (사용자 프리셋만 고려) @@ -968,28 +945,24 @@ export class SettingsView extends LitElement { this.shortcuts = keybinds; }; - ipcRenderer.on('user-state-changed', this._userStateListener); - ipcRenderer.on('settings-updated', this._settingsUpdatedListener); - ipcRenderer.on('presets-updated', this._presetsUpdatedListener); - ipcRenderer.on('shortcuts-updated', this._shortcutListener); + window.api.feature.settings.onUserStateChanged(this._userStateListener); + window.api.feature.settings.onSettingsUpdated(this._settingsUpdatedListener); + window.api.feature.settings.onPresetsUpdated(this._presetsUpdatedListener); + window.api.feature.settings.onShortcutsUpdated(this._shortcutListener); } cleanupIpcListeners() { - if (!window.require) return; - - const { ipcRenderer } = window.require('electron'); - if (this._userStateListener) { - ipcRenderer.removeListener('user-state-changed', this._userStateListener); + window.api.feature.settings.removeOnUserStateChanged(this._userStateListener); } if (this._settingsUpdatedListener) { - ipcRenderer.removeListener('settings-updated', this._settingsUpdatedListener); + window.api.feature.settings.removeOnSettingsUpdated(this._settingsUpdatedListener); } if (this._presetsUpdatedListener) { - ipcRenderer.removeListener('presets-updated', this._presetsUpdatedListener); + window.api.feature.settings.removeOnPresetsUpdated(this._presetsUpdatedListener); } if (this._shortcutListener) { - ipcRenderer.removeListener('shortcuts-updated', this._shortcutListener); + window.api.feature.settings.removeOnShortcutsUpdated(this._shortcutListener); } } @@ -1023,17 +996,11 @@ export class SettingsView extends LitElement { } handleMouseEnter = () => { - if (window.require) { - const { ipcRenderer } = window.require('electron'); - ipcRenderer.send('cancel-hide-settings-window'); - } + window.api.window.cancelHideSettingsWindow(); } handleMouseLeave = () => { - if (window.require) { - const { ipcRenderer } = window.require('electron'); - ipcRenderer.send('hide-settings-window'); - } + window.api.window.hideSettingsWindow(); } // getMainShortcuts() { @@ -1083,39 +1050,27 @@ export class SettingsView extends LitElement { handleMoveLeft() { console.log('Move Left clicked'); - if (window.require) { - const { ipcRenderer } = window.require('electron'); - ipcRenderer.invoke('move-window-step', 'left'); - } + window.api.feature.settings.moveWindowStep('left'); } handleMoveRight() { console.log('Move Right clicked'); - if (window.require) { - const { ipcRenderer } = window.require('electron'); - ipcRenderer.invoke('move-window-step', 'right'); - } + window.api.feature.settings.moveWindowStep('right'); } async handlePersonalize() { console.log('Personalize clicked'); - if (window.require) { - const { ipcRenderer } = window.require('electron'); - try { - await ipcRenderer.invoke('open-login-page'); - } catch (error) { - console.error('Failed to open personalize page:', error); - } + try { + await window.api.window.openLoginPage(); + } catch (error) { + console.error('Failed to open personalize page:', error); } } async handleToggleInvisibility() { console.log('Toggle Invisibility clicked'); - if (window.require) { - const { ipcRenderer } = window.require('electron'); - this.isContentProtectionOn = await ipcRenderer.invoke('toggle-content-protection'); - this.requestUpdate(); - } + this.isContentProtectionOn = await window.api.window.toggleContentProtection(); + this.requestUpdate(); } async handleSaveApiKey() { @@ -1123,62 +1078,46 @@ export class SettingsView extends LitElement { if (!input || !input.value) return; const newApiKey = input.value; - if (window.require) { - const { ipcRenderer } = window.require('electron'); - try { - const result = await ipcRenderer.invoke('settings:saveApiKey', newApiKey); - if (result.success) { - console.log('API Key saved successfully via IPC.'); - this.apiKey = newApiKey; - this.requestUpdate(); - } else { - console.error('Failed to save API Key via IPC:', result.error); - } - } catch(e) { - console.error('Error invoking save-api-key IPC:', e); + try { + const result = await window.api.feature.settings.saveApiKey(newApiKey); + if (result.success) { + console.log('API Key saved successfully via IPC.'); + this.apiKey = newApiKey; + this.requestUpdate(); + } else { + console.error('Failed to save API Key via IPC:', result.error); } + } catch(e) { + console.error('Error invoking save-api-key IPC:', e); } } async handleClearApiKey() { console.log('Clear API Key clicked'); - if (window.require) { - const { ipcRenderer } = window.require('electron'); - await ipcRenderer.invoke('settings:removeApiKey'); - this.apiKey = null; - this.requestUpdate(); - } + await window.api.feature.settings.removeApiKey(); + this.apiKey = null; + this.requestUpdate(); } handleQuit() { console.log('Quit clicked'); - if (window.require) { - const { ipcRenderer } = window.require('electron'); - ipcRenderer.invoke('quit-application'); - } + window.api.window.quitApplication(); } handleFirebaseLogout() { console.log('Firebase Logout clicked'); - if (window.require) { - const { ipcRenderer } = window.require('electron'); - ipcRenderer.invoke('firebase-logout'); - } + window.api.window.firebaseLogout(); } async handleOllamaShutdown() { console.log('[SettingsView] Shutting down Ollama service...'); - if (!window.require) return; - - const { ipcRenderer } = window.require('electron'); - try { // Show loading state this.ollamaStatus = { ...this.ollamaStatus, running: false }; this.requestUpdate(); - const result = await ipcRenderer.invoke('ollama:shutdown', false); // Graceful shutdown + const result = await window.api.feature.settings.shutdownOllama(false); // Graceful shutdown if (result.success) { console.log('[SettingsView] Ollama shut down successfully'); @@ -1330,329 +1269,6 @@ export class SettingsView extends LitElement { //////// before_modelStateService //////// //////// after_modelStateService //////// - render() { - if (this.isLoading) { - return html` -