From 817a8c5165115aef879f27290b96477500c85711 Mon Sep 17 00:00:00 2001 From: samtiz Date: Sun, 13 Jul 2025 02:26:46 +0900 Subject: [PATCH] settingsService facade --- aec | 2 +- src/bridge/featureBridge.js | 53 +- src/bridge/windowBridge.js | 60 -- .../common/services/modelStateService.js | 85 ++- src/features/common/services/ollamaService.js | 20 + .../common/services/whisperService.js | 4 +- src/features/settings/settingsService.js | 71 +- src/index.js | 6 +- src/ui/settings/SettingsView.js | 651 ++++++++++++------ src/window/windowManager.js | 8 + 10 files changed, 596 insertions(+), 364 deletions(-) diff --git a/aec b/aec index 9e11f4f..f00bb1f 160000 --- a/aec +++ b/aec @@ -1 +1 @@ -Subproject commit 9e11f4f95707714464194bdfc9db0222ec5c6163 +Subproject commit f00bb1fb948053c752b916adfee19f90644a0b2f diff --git a/src/bridge/featureBridge.js b/src/bridge/featureBridge.js index 3cce84b..1ec2a0f 100644 --- a/src/bridge/featureBridge.js +++ b/src/bridge/featureBridge.js @@ -5,61 +5,26 @@ 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 () => { + console.log('[FeatureBridge] settings:getPresets 호출됨'); 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 () => { + console.log('[FeatureBridge] settings:get-auto-update 호출됨'); return await settingsService.getAutoUpdateSetting(); }); ipcMain.handle('settings:set-auto-update', async (event, isEnabled) => { - console.log('[SettingsService] Setting auto update setting:', isEnabled); + console.log('[FeatureBridge] settings:set-auto-update 호출됨', isEnabled); return await settingsService.setAutoUpdateSetting(isEnabled); }); + + // New IPC handler for loadInitialData + ipcMain.handle('settings:loadInitialData', async () => { + console.log('[FeatureBridge] settings:loadInitialData called'); + return await settingsService.loadInitialData(); + }); console.log('[FeatureBridge] Initialized with settings handlers.'); }, diff --git a/src/bridge/windowBridge.js b/src/bridge/windowBridge.js index f20ff14..2b0ae06 100644 --- a/src/bridge/windowBridge.js +++ b/src/bridge/windowBridge.js @@ -5,66 +5,6 @@ const { windowPool, settingsHideTimer, app, shell } = require('../window/windowM 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로 상태를 전송 diff --git a/src/features/common/services/modelStateService.js b/src/features/common/services/modelStateService.js index b5bed8b..14866d2 100644 --- a/src/features/common/services/modelStateService.js +++ b/src/features/common/services/modelStateService.js @@ -6,8 +6,11 @@ const encryptionService = require('./encryptionService'); const providerSettingsRepository = require('../repositories/providerSettings'); const userModelSelectionsRepository = require('../repositories/userModelSelections'); +// Import authService directly (singleton) +const authService = require('./authService'); + class ModelStateService { - constructor(authService) { + constructor() { this.authService = authService; this.store = new Store({ name: 'pickle-glass-model-state' }); this.state = {}; @@ -506,6 +509,45 @@ class ModelStateService { } } + getProviderConfig() { + const serializableProviders = {}; + for (const key in PROVIDERS) { + const { handler, ...rest } = PROVIDERS[key]; + serializableProviders[key] = rest; + } + return serializableProviders; + } + + async handleValidateKey(provider, key) { + const result = await this.validateApiKey(provider, key); + if (result.success) { + // Use 'local' as placeholder for local services + const finalKey = (provider === 'ollama' || provider === 'whisper') ? 'local' : key; + this.setApiKey(provider, finalKey); + // After setting the key, auto-select models + this._autoSelectAvailableModels(); + this._saveState(); // Ensure state is saved after model selection + } + return result; + } + + async handleRemoveApiKey(provider) { + const success = this.removeApiKey(provider); + if (success) { + const selectedModels = this.getSelectedModels(); + if (!selectedModels.llm || !selectedModels.stt) { + webContents.getAllWebContents().forEach(wc => { + wc.send('force-show-apikey-header'); + }); + } + } + return success; + } + + async handleSetSelectedModel(type, modelId) { + return this.setSelectedModel(type, modelId); + } + /** * * @param {('llm' | 'stt')} type @@ -528,18 +570,7 @@ class ModelStateService { } setupIpcHandlers() { - ipcMain.handle('model:validate-key', async (e, { provider, key }) => { - const result = await this.validateApiKey(provider, key); - if (result.success) { - // Use 'local' as placeholder for local services - const finalKey = (provider === 'ollama' || provider === 'whisper') ? 'local' : key; - this.setApiKey(provider, finalKey); - // After setting the key, auto-select models - this._autoSelectAvailableModels(); - this._saveState(); // Ensure state is saved after model selection - } - return result; - }); + ipcMain.handle('model:validate-key', async (e, { provider, key }) => this.handleValidateKey(provider, key)); ipcMain.handle('model:get-all-keys', () => this.getAllApiKeys()); ipcMain.handle('model:set-api-key', async (e, { provider, key }) => { const success = this.setApiKey(provider, key); @@ -549,33 +580,17 @@ class ModelStateService { } return success; }); - ipcMain.handle('model:remove-api-key', async (e, { provider }) => { - const success = this.removeApiKey(provider); - if (success) { - const selectedModels = this.getSelectedModels(); - if (!selectedModels.llm || !selectedModels.stt) { - webContents.getAllWebContents().forEach(wc => { - wc.send('force-show-apikey-header'); - }); - } - } - return success; - }); + ipcMain.handle('model:remove-api-key', async (e, { provider }) => this.handleRemoveApiKey(provider)); ipcMain.handle('model:get-selected-models', () => this.getSelectedModels()); - ipcMain.handle('model:set-selected-model', async (e, { type, modelId }) => this.setSelectedModel(type, modelId)); + ipcMain.handle('model:set-selected-model', async (e, { type, modelId }) => this.handleSetSelectedModel(type, modelId)); ipcMain.handle('model:get-available-models', (e, { type }) => this.getAvailableModels(type)); ipcMain.handle('model:are-providers-configured', () => this.areProvidersConfigured()); ipcMain.handle('model:get-current-model-info', (e, { type }) => this.getCurrentModelInfo(type)); - ipcMain.handle('model:get-provider-config', () => { - const serializableProviders = {}; - for (const key in PROVIDERS) { - const { handler, ...rest } = PROVIDERS[key]; - serializableProviders[key] = rest; - } - return serializableProviders; - }); + ipcMain.handle('model:get-provider-config', () => this.getProviderConfig()); } } -module.exports = ModelStateService; \ No newline at end of file +// Export singleton instance +const modelStateService = new ModelStateService(); +module.exports = modelStateService; \ No newline at end of file diff --git a/src/features/common/services/ollamaService.js b/src/features/common/services/ollamaService.js index 13186d5..7fb6b1c 100644 --- a/src/features/common/services/ollamaService.js +++ b/src/features/common/services/ollamaService.js @@ -39,6 +39,26 @@ class OllamaService extends LocalAIServiceBase { this._startHealthMonitoring(); } + async getStatus() { + try { + const installed = await this.isInstalled(); + if (!installed) { + return { success: true, installed: false, running: false, models: [] }; + } + + const running = await this.isServiceRunning(); + if (!running) { + return { success: true, installed: true, running: false, models: [] }; + } + + const models = await this.getInstalledModels(); + return { success: true, installed: true, running: true, models }; + } catch (error) { + console.error('[OllamaService] Error getting status:', error); + return { success: false, error: error.message, installed: false, running: false, models: [] }; + } + } + getOllamaCliPath() { if (this.getPlatform() === 'darwin') { return '/Applications/Ollama.app/Contents/Resources/ollama'; diff --git a/src/features/common/services/whisperService.js b/src/features/common/services/whisperService.js index 684c055..1b70ca6 100644 --- a/src/features/common/services/whisperService.js +++ b/src/features/common/services/whisperService.js @@ -448,4 +448,6 @@ class WhisperService extends LocalAIServiceBase { } } -module.exports = { WhisperService }; \ No newline at end of file +// Export singleton instance +const whisperService = new WhisperService(); +module.exports = whisperService; \ No newline at end of file diff --git a/src/features/settings/settingsService.js b/src/features/settings/settingsService.js index f36c385..3d1a6f7 100644 --- a/src/features/settings/settingsService.js +++ b/src/features/settings/settingsService.js @@ -4,6 +4,11 @@ const authService = require('../common/services/authService'); const settingsRepository = require('./repositories'); const { getStoredApiKey, getStoredProvider, windowPool } = require('../../window/windowManager'); +// New imports for common services +const modelStateService = require('../common/services/modelStateService'); +const ollamaService = require('../common/services/ollamaService'); +const whisperService = require('../common/services/whisperService'); + const store = new Store({ name: 'pickle-glass-settings', defaults: { @@ -19,6 +24,51 @@ const NOTIFICATION_CONFIG = { RETRY_BASE_DELAY: 1000, // exponential backoff base (ms) }; +// New facade functions for model state management +async function getModelSettings() { + try { + const [config, storedKeys, availableLlm, availableStt, selectedModels] = await Promise.all([ + modelStateService.getProviderConfig(), + modelStateService.getAllApiKeys(), + modelStateService.getAvailableModels('llm'), + modelStateService.getAvailableModels('stt'), + modelStateService.getSelectedModels(), + ]); + return { success: true, data: { config, storedKeys, availableLlm, availableStt, selectedModels } }; + } catch (error) { + console.error('[SettingsService] Error getting model settings:', error); + return { success: false, error: error.message }; + } +} + +async function validateAndSaveKey(provider, key) { + return modelStateService.handleValidateKey(provider, key); +} + +async function clearApiKey(provider) { + const success = await modelStateService.handleRemoveApiKey(provider); + return { success }; +} + +async function setSelectedModel(type, modelId) { + const success = await modelStateService.handleSetSelectedModel(type, modelId); + return { success }; +} + +// Ollama facade functions +async function getOllamaStatus() { + return ollamaService.getStatus(); +} + +async function ensureOllamaReady() { + return ollamaService.ensureReady(); +} + +async function shutdownOllama() { + return ollamaService.shutdown(false); // false for graceful shutdown +} + + // window targeting system class WindowNotificationManager { constructor() { @@ -373,7 +423,16 @@ function initialize() { // cleanup windowNotificationManager.cleanup(); - // IPC handlers 제거 (featureBridge로 이동) + // IPC handlers for model settings + ipcMain.handle('settings:get-model-settings', getModelSettings); + ipcMain.handle('settings:validate-and-save-key', (e, { provider, key }) => validateAndSaveKey(provider, key)); + ipcMain.handle('settings:clear-api-key', (e, { provider }) => clearApiKey(provider)); + ipcMain.handle('settings:set-selected-model', (e, { type, modelId }) => setSelectedModel(type, modelId)); + + // IPC handlers for Ollama management + ipcMain.handle('settings:get-ollama-status', getOllamaStatus); + ipcMain.handle('settings:ensure-ollama-ready', ensureOllamaReady); + ipcMain.handle('settings:shutdown-ollama', shutdownOllama); console.log('[SettingsService] Initialized and ready.'); } @@ -406,4 +465,14 @@ module.exports = { removeApiKey, updateContentProtection, getAutoUpdateSetting, + setAutoUpdateSetting, + // Model settings facade + getModelSettings, + validateAndSaveKey, + clearApiKey, + setSelectedModel, + // Ollama facade + getOllamaStatus, + ensureOllamaReady, + shutdownOllama }; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 4cdc439..975edc4 100644 --- a/src/index.js +++ b/src/index.js @@ -25,7 +25,7 @@ const { EventEmitter } = require('events'); const askService = require('./features/ask/askService'); const settingsService = require('./features/settings/settingsService'); const sessionRepository = require('./features/common/repositories/session'); -const ModelStateService = require('./features/common/services/modelStateService'); +const modelStateService = require('./features/common/services/modelStateService'); const sqliteClient = require('./features/common/services/sqliteClient'); const featureBridge = require('./bridge/featureBridge'); @@ -39,7 +39,6 @@ const listenService = new ListenService(); global.listenService = listenService; //////// after_modelStateService //////// -const modelStateService = new ModelStateService(authService); global.modelStateService = modelStateService; //////// after_modelStateService //////// @@ -331,8 +330,7 @@ app.on('activate', () => { }); function setupWhisperIpcHandlers() { - const { WhisperService } = require('./features/common/services/whisperService'); - const whisperService = new WhisperService(); + const whisperService = require('./features/common/services/whisperService'); // Forward download progress events to renderer whisperService.on('downloadProgress', (data) => { diff --git a/src/ui/settings/SettingsView.js b/src/ui/settings/SettingsView.js index 4eb43d1..65f330d 100644 --- a/src/ui/settings/SettingsView.js +++ b/src/ui/settings/SettingsView.js @@ -543,9 +543,11 @@ export class SettingsView extends LitElement { } async loadAutoUpdateSetting() { + if (!window.require) return; + const { ipcRenderer } = window.require('electron'); this.autoUpdateLoading = true; try { - const enabled = await window.api.feature.settings.getAutoUpdate(); + const enabled = await ipcRenderer.invoke('settings:get-auto-update'); this.autoUpdateEnabled = enabled; console.log('Auto-update setting loaded:', enabled); } catch (e) { @@ -557,12 +559,13 @@ export class SettingsView extends LitElement { } async handleToggleAutoUpdate() { - if (this.autoUpdateLoading) return; + if (!window.require || this.autoUpdateLoading) return; + const { ipcRenderer } = window.require('electron'); this.autoUpdateLoading = true; this.requestUpdate(); try { const newValue = !this.autoUpdateEnabled; - const result = await window.api.feature.settings.setAutoUpdate(newValue); + const result = await ipcRenderer.invoke('settings:set-auto-update', newValue); if (result && result.success) { this.autoUpdateEnabled = newValue; } else { @@ -577,29 +580,32 @@ 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([ - 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() + const [userState, modelSettings, presets, contentProtection, shortcuts, ollamaStatus, whisperModelsResult] = await Promise.all([ + ipcRenderer.invoke('get-current-user'), + ipcRenderer.invoke('settings:get-model-settings'), // Facade call + ipcRenderer.invoke('settings:getPresets'), + ipcRenderer.invoke('get-content-protection-status'), + ipcRenderer.invoke('get-current-shortcuts'), + ipcRenderer.invoke('settings:get-ollama-status'), + ipcRenderer.invoke('whisper:get-installed-models') ]); if (userState && userState.isLoggedIn) this.firebaseUser = userState; - this.providerConfig = config; - this.apiKeys = storedKeys; - this.availableLlmModels = availableLlm; - this.availableSttModels = availableStt; - this.selectedLlm = selectedModels.llm; - this.selectedStt = selectedModels.stt; + + if (modelSettings.success) { + const { config, storedKeys, availableLlm, availableStt, selectedModels } = modelSettings.data; + this.providerConfig = config; + this.apiKeys = storedKeys; + this.availableLlmModels = availableLlm; + this.availableSttModels = availableStt; + this.selectedLlm = selectedModels.llm; + this.selectedStt = selectedModels.stt; + } + this.presets = presets || []; this.isContentProtectionOn = contentProtection; this.shortcuts = shortcuts || {}; @@ -631,6 +637,7 @@ export class SettingsView extends LitElement { } } + async handleSaveKey(provider) { const input = this.shadowRoot.querySelector(`#key-input-${provider}`); if (!input) return; @@ -639,9 +646,10 @@ 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 window.api.feature.settings.ensureOllamaReady(); + const ensureResult = await ipcRenderer.invoke('settings:ensure-ollama-ready'); if (!ensureResult.success) { alert(`Failed to setup Ollama: ${ensureResult.error}`); this.saving = false; @@ -649,10 +657,9 @@ export class SettingsView extends LitElement { } // Now validate (which will check if service is running) - const result = await window.api.feature.settings.validateKey({ provider, key: 'local' }); + const result = await ipcRenderer.invoke('settings:validate-and-save-key', { provider, key: 'local' }); if (result.success) { - this.apiKeys = { ...this.apiKeys, [provider]: 'local' }; await this.refreshModelData(); await this.refreshOllamaStatus(); } else { @@ -665,10 +672,10 @@ export class SettingsView extends LitElement { // For Whisper, just enable it if (provider === 'whisper') { this.saving = true; - const result = await window.api.feature.settings.validateKey({ provider, key: 'local' }); + const { ipcRenderer } = window.require('electron'); + const result = await ipcRenderer.invoke('settings:validate-and-save-key', { provider, key: 'local' }); if (result.success) { - this.apiKeys = { ...this.apiKeys, [provider]: 'local' }; await this.refreshModelData(); } else { alert(`Failed to enable Whisper: ${result.error}`); @@ -679,10 +686,10 @@ export class SettingsView extends LitElement { // For other providers, use the normal flow this.saving = true; - const result = await window.api.feature.settings.validateKey({ provider, key }); + const { ipcRenderer } = window.require('electron'); + const result = await ipcRenderer.invoke('settings:validate-and-save-key', { provider, key }); if (result.success) { - this.apiKeys = { ...this.apiKeys, [provider]: key }; await this.refreshModelData(); } else { alert(`Failed to save ${provider} key: ${result.error}`); @@ -693,24 +700,23 @@ export class SettingsView extends LitElement { async handleClearKey(provider) { this.saving = true; - await window.api.feature.settings.removeApiKey({ provider }); - this.apiKeys = { ...this.apiKeys, [provider]: '' }; + const { ipcRenderer } = window.require('electron'); + await ipcRenderer.invoke('settings:clear-api-key', { provider }); await this.refreshModelData(); this.saving = false; } async refreshModelData() { - const [availableLlm, availableStt, selected, storedKeys] = await Promise.all([ - 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; - this.selectedLlm = selected.llm; - this.selectedStt = selected.stt; - this.apiKeys = storedKeys; + const { ipcRenderer } = window.require('electron'); + const result = await ipcRenderer.invoke('settings:get-model-settings'); + if (result.success) { + const { availableLlm, availableStt, selectedModels, storedKeys } = result.data; + this.availableLlmModels = availableLlm; + this.availableSttModels = availableStt; + this.selectedLlm = selectedModels.llm; + this.selectedStt = selectedModels.stt; + this.apiKeys = storedKeys; + } this.requestUpdate(); } @@ -755,7 +761,8 @@ export class SettingsView extends LitElement { } this.saving = true; - await window.api.feature.settings.setSelectedModel({ type, modelId }); + const { ipcRenderer } = window.require('electron'); + await ipcRenderer.invoke('settings:set-selected-model', { type, modelId }); if (type === 'llm') this.selectedLlm = modelId; if (type === 'stt') this.selectedStt = modelId; this.isLlmListVisible = false; @@ -765,7 +772,8 @@ export class SettingsView extends LitElement { } async refreshOllamaStatus() { - const ollamaStatus = await window.api.feature.settings.getOllamaStatus(); + const { ipcRenderer } = window.require('electron'); + const ollamaStatus = await ipcRenderer.invoke('settings:get-ollama-status'); if (ollamaStatus?.success) { this.ollamaStatus = { installed: ollamaStatus.installed, running: ollamaStatus.running }; this.ollamaModels = ollamaStatus.models || []; @@ -809,6 +817,8 @@ 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) { @@ -817,10 +827,10 @@ export class SettingsView extends LitElement { } }; - window.api.feature.settings.onWhisperDownloadProgress(progressHandler); + ipcRenderer.on('whisper:download-progress', progressHandler); // Start download - const result = await window.api.feature.settings.downloadWhisperModel(modelId); + const result = await ipcRenderer.invoke('whisper:download-model', modelId); if (result.success) { // Auto-select the model after download @@ -830,7 +840,7 @@ export class SettingsView extends LitElement { } // Cleanup - window.api.feature.settings.removeOnWhisperDownloadProgress(progressHandler); + ipcRenderer.removeListener('whisper:download-progress', progressHandler); } catch (error) { console.error(`[SettingsView] Error downloading Whisper model ${modelId}:`, error); alert(`Error downloading ${modelId}: ${error.message}`); @@ -862,12 +872,17 @@ export class SettingsView extends LitElement { if (this.wasJustDragged) return console.log("Requesting Firebase authentication from main process...") - window.api.feature.settings.startFirebaseAuth(); - } + if (window.require) { + window.require("electron").ipcRenderer.invoke("start-firebase-auth") + } + } //////// after_modelStateService //////// openShortcutEditor() { - window.api.feature.settings.openShortcutEditor(); + if (window.require) { + const { ipcRenderer } = window.require('electron'); + ipcRenderer.invoke('open-shortcut-editor'); + } } connectedCallback() { @@ -905,6 +920,10 @@ 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) { @@ -926,7 +945,7 @@ export class SettingsView extends LitElement { this._presetsUpdatedListener = async (event) => { console.log('[SettingsView] Received presets-updated, refreshing presets'); try { - const presets = await window.api.feature.settings.getPresets(); + const presets = await ipcRenderer.invoke('settings:getPresets'); this.presets = presets || []; // 현재 선택된 프리셋이 삭제되었는지 확인 (사용자 프리셋만 고려) @@ -945,24 +964,28 @@ export class SettingsView extends LitElement { this.shortcuts = keybinds; }; - 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); + 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); } cleanupIpcListeners() { + if (!window.require) return; + + const { ipcRenderer } = window.require('electron'); + if (this._userStateListener) { - window.api.feature.settings.removeOnUserStateChanged(this._userStateListener); + ipcRenderer.removeListener('user-state-changed', this._userStateListener); } if (this._settingsUpdatedListener) { - window.api.feature.settings.removeOnSettingsUpdated(this._settingsUpdatedListener); + ipcRenderer.removeListener('settings-updated', this._settingsUpdatedListener); } if (this._presetsUpdatedListener) { - window.api.feature.settings.removeOnPresetsUpdated(this._presetsUpdatedListener); + ipcRenderer.removeListener('presets-updated', this._presetsUpdatedListener); } if (this._shortcutListener) { - window.api.feature.settings.removeOnShortcutsUpdated(this._shortcutListener); + ipcRenderer.removeListener('shortcuts-updated', this._shortcutListener); } } @@ -996,11 +1019,17 @@ export class SettingsView extends LitElement { } handleMouseEnter = () => { - window.api.window.cancelHideSettingsWindow(); + if (window.require) { + const { ipcRenderer } = window.require('electron'); + ipcRenderer.send('cancel-hide-settings-window'); + } } handleMouseLeave = () => { - window.api.window.hideSettingsWindow(); + if (window.require) { + const { ipcRenderer } = window.require('electron'); + ipcRenderer.send('hide-settings-window'); + } } // getMainShortcuts() { @@ -1050,74 +1079,70 @@ export class SettingsView extends LitElement { handleMoveLeft() { console.log('Move Left clicked'); - window.api.feature.settings.moveWindowStep('left'); + if (window.require) { + const { ipcRenderer } = window.require('electron'); + ipcRenderer.invoke('move-window-step', 'left'); + } } handleMoveRight() { console.log('Move Right clicked'); - window.api.feature.settings.moveWindowStep('right'); + if (window.require) { + const { ipcRenderer } = window.require('electron'); + ipcRenderer.invoke('move-window-step', 'right'); + } } async handlePersonalize() { console.log('Personalize clicked'); - try { - await window.api.window.openLoginPage(); - } catch (error) { - console.error('Failed to open personalize page:', error); + 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); + } } } async handleToggleInvisibility() { console.log('Toggle Invisibility clicked'); - this.isContentProtectionOn = await window.api.window.toggleContentProtection(); - this.requestUpdate(); - } - - async handleSaveApiKey() { - const input = this.shadowRoot.getElementById('api-key-input'); - if (!input || !input.value) return; - - const newApiKey = input.value; - 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); + if (window.require) { + const { ipcRenderer } = window.require('electron'); + this.isContentProtectionOn = await ipcRenderer.invoke('toggle-content-protection'); + this.requestUpdate(); } } - async handleClearApiKey() { - console.log('Clear API Key clicked'); - await window.api.feature.settings.removeApiKey(); - this.apiKey = null; - this.requestUpdate(); - } - handleQuit() { console.log('Quit clicked'); - window.api.window.quitApplication(); + if (window.require) { + const { ipcRenderer } = window.require('electron'); + ipcRenderer.invoke('quit-application'); + } } handleFirebaseLogout() { console.log('Firebase Logout clicked'); - window.api.window.firebaseLogout(); + if (window.require) { + const { ipcRenderer } = window.require('electron'); + ipcRenderer.invoke('firebase-logout'); + } } 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 window.api.feature.settings.shutdownOllama(false); // Graceful shutdown + const result = await ipcRenderer.invoke('settings:shutdown-ollama'); // Graceful shutdown if (result.success) { console.log('[SettingsView] Ollama shut down successfully'); @@ -1135,139 +1160,329 @@ export class SettingsView extends LitElement { } } + //////// after_modelStateService //////// + render() { + if (this.isLoading) { + return html` +
+
+
+ Loading... +
+
+ `; + } - //////// before_modelStateService //////// - // render() { - // if (this.isLoading) { - // return html` - //
- //
- //
- // Loading... - //
- //
- // `; - // } + const loggedIn = !!this.firebaseUser; - // const loggedIn = !!this.firebaseUser; + const apiKeyManagementHTML = html` +
+ ${Object.entries(this.providerConfig) + .filter(([id, config]) => !id.includes('-glass')) + .map(([id, config]) => { + if (id === 'ollama') { + // Special UI for Ollama + return html` +
+ + ${this.ollamaStatus.installed && this.ollamaStatus.running ? html` +
+ ✓ Ollama is running +
+ + ` : this.ollamaStatus.installed ? html` +
+ ⚠ Ollama installed but not running +
+ + ` : html` +
+ ✗ Ollama not installed +
+ + `} +
+ `; + } + + if (id === 'whisper') { + // Special UI for Whisper with model selection + const whisperModels = config.sttModels || []; + const selectedWhisperModel = this.selectedStt && this.getProviderForModel('stt', this.selectedStt) === 'whisper' + ? this.selectedStt + : null; + + return html` +
+ + ${this.apiKeys[id] === 'local' ? html` +
+ ✓ Whisper is enabled +
+ + + + + + ${Object.entries(this.installingModels).map(([modelId, progress]) => { + if (modelId.startsWith('whisper-') && progress !== undefined) { + return html` +
+
+ Downloading ${modelId}... +
+
+
+
+
+ `; + } + return null; + })} + + + ` : html` + + `} +
+ `; + } + + // Regular providers + return html` +
+ + +
+ + +
+
+ `; + })} +
+ `; + + const getModelName = (type, id) => { + const models = type === 'llm' ? this.availableLlmModels : this.availableSttModels; + const model = models.find(m => m.id === id); + return model ? model.name : id; + } - // return html` - //
- //
- //
- //

Pickle Glass

- // - //
- //
- // - // - // - //
- //
+ const modelSelectionHTML = html` +
+
+ + + ${this.isLlmListVisible ? html` +
+ ${this.availableLlmModels.map(model => { + const isOllama = this.getProviderForModel('llm', model.id) === 'ollama'; + const ollamaModel = isOllama ? this.ollamaModels.find(m => m.name === model.id) : null; + const isInstalling = this.installingModels[model.id] !== undefined; + const installProgress = this.installingModels[model.id] || 0; + + return html` +
this.selectModel('llm', model.id)}> + ${model.name} + ${isOllama ? html` + ${isInstalling ? html` +
+
+
+ ` : ollamaModel?.installed ? html` + ✓ Installed + ` : html` + Click to install + `} + ` : ''} +
+ `; + })} +
+ ` : ''} +
+
+ + + ${this.isSttListVisible ? html` +
+ ${this.availableSttModels.map(model => { + const isWhisper = this.getProviderForModel('stt', model.id) === 'whisper'; + const isInstalling = this.installingModels[model.id] !== undefined; + const installProgress = this.installingModels[model.id] || 0; + + return html` +
this.selectModel('stt', model.id)}> + ${model.name} + ${isWhisper && isInstalling ? html` +
+
+
+ ` : ''} +
+ `; + })} +
+ ` : ''} +
+
+ `; - //
- // - // - //
+ return html` +
+
+
+

Pickle Glass

+ +
+
+ + + +
+
- //
- // ${this.getMainShortcuts().map(shortcut => html` - //
- // ${shortcut.name} - //
- // - // ${shortcut.key} - //
- //
- // `)} - //
+ ${apiKeyManagementHTML} + ${modelSelectionHTML} - // - //
- //
- // - // My Presets - // (${this.presets.filter(p => p.is_default === 0).length}) - // - // - // ${this.showPresets ? '▼' : '▶'} - // - //
+
+ +
+ + +
+ ${this.getMainShortcuts().map(shortcut => html` +
+ ${shortcut.name} +
+ ${this.renderShortcutKeys(shortcut.accelerator)} +
+
+ `)} +
+ +
+
+ + My Presets + (${this.presets.filter(p => p.is_default === 0).length}) + + + ${this.showPresets ? '▼' : '▶'} + +
- //
- // ${this.presets.filter(p => p.is_default === 0).length === 0 ? html` - //
- // No custom presets yet.
- // - // Create your first preset - // - //
- // ` : this.presets.filter(p => p.is_default === 0).map(preset => html` - //
this.handlePresetSelect(preset)}> - // ${preset.title} - // ${this.selectedPreset?.id === preset.id ? html`Selected` : ''} - //
- // `)} - //
- //
+
+ ${this.presets.filter(p => p.is_default === 0).length === 0 ? html` +
+ No custom presets yet.
+ + Create your first preset + +
+ ` : this.presets.filter(p => p.is_default === 0).map(preset => html` +
this.handlePresetSelect(preset)}> + ${preset.title} + ${this.selectedPreset?.id === preset.id ? html`Selected` : ''} +
+ `)} +
+
- //
- // +
+ + - //
- // - // - //
+
+ + +
- // + - //
- // ${this.firebaseUser - // ? html` - // - // ` - // : html` - // - // ` - // } - // - //
- //
- //
- // `; - // } - //////// before_modelStateService //////// - +
+ ${this.firebaseUser + ? html` + + ` + : html` + + ` + } + +
+
+
+ `; + } //////// after_modelStateService //////// } diff --git a/src/window/windowManager.js b/src/window/windowManager.js index 9fa53a4..ac43c55 100644 --- a/src/window/windowManager.js +++ b/src/window/windowManager.js @@ -295,6 +295,10 @@ function createFeatureWindows(header, namesToCreate) { }); } windowPool.set('settings', settings); + + if (!app.isPackaged) { + settings.webContents.openDevTools({ mode: 'detach' }); + } break; } @@ -1051,6 +1055,7 @@ function setupIpcHandlers(movementManager) { const win = windowPool.get('settings'); if (win && !win.isDestroyed()) { + console.log('[WindowManager] Showing settings window'); if (settingsHideTimer) { clearTimeout(settingsHideTimer); settingsHideTimer = null; @@ -1077,6 +1082,9 @@ function setupIpcHandlers(movementManager) { win.show(); win.moveTop(); win.setAlwaysOnTop(true); + console.log('[WindowManager] Settings window shown'); + } else { + console.log('[WindowManager] Settings window not found'); } });