settingsService facade

This commit is contained in:
samtiz 2025-07-13 02:26:46 +09:00
parent 27f6f0e68e
commit 817a8c5165
10 changed files with 596 additions and 364 deletions

2
aec

@ -1 +1 @@
Subproject commit 9e11f4f95707714464194bdfc9db0222ec5c6163 Subproject commit f00bb1fb948053c752b916adfee19f90644a0b2f

View File

@ -5,61 +5,26 @@ const settingsService = require('../features/settings/settingsService');
module.exports = { module.exports = {
// Renderer로부터의 요청을 수신 // Renderer로부터의 요청을 수신
initialize() { 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 () => { ipcMain.handle('settings:getPresets', async () => {
console.log('[FeatureBridge] settings:getPresets 호출됨');
return await settingsService.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 () => { ipcMain.handle('settings:get-auto-update', async () => {
console.log('[FeatureBridge] settings:get-auto-update 호출됨');
return await settingsService.getAutoUpdateSetting(); return await settingsService.getAutoUpdateSetting();
}); });
ipcMain.handle('settings:set-auto-update', async (event, isEnabled) => { 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); 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.'); console.log('[FeatureBridge] Initialized with settings handlers.');
}, },

View File

@ -5,66 +5,6 @@ const { windowPool, settingsHideTimer, app, shell } = require('../window/windowM
module.exports = { module.exports = {
// Renderer로부터의 요청을 수신 // Renderer로부터의 요청을 수신
initialize() { 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로 상태를 전송 // Renderer로 상태를 전송

View File

@ -6,8 +6,11 @@ const encryptionService = require('./encryptionService');
const providerSettingsRepository = require('../repositories/providerSettings'); const providerSettingsRepository = require('../repositories/providerSettings');
const userModelSelectionsRepository = require('../repositories/userModelSelections'); const userModelSelectionsRepository = require('../repositories/userModelSelections');
// Import authService directly (singleton)
const authService = require('./authService');
class ModelStateService { class ModelStateService {
constructor(authService) { constructor() {
this.authService = authService; this.authService = authService;
this.store = new Store({ name: 'pickle-glass-model-state' }); this.store = new Store({ name: 'pickle-glass-model-state' });
this.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 * @param {('llm' | 'stt')} type
@ -528,18 +570,7 @@ class ModelStateService {
} }
setupIpcHandlers() { setupIpcHandlers() {
ipcMain.handle('model:validate-key', async (e, { provider, key }) => { ipcMain.handle('model:validate-key', async (e, { provider, key }) => this.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;
});
ipcMain.handle('model:get-all-keys', () => this.getAllApiKeys()); ipcMain.handle('model:get-all-keys', () => this.getAllApiKeys());
ipcMain.handle('model:set-api-key', async (e, { provider, key }) => { ipcMain.handle('model:set-api-key', async (e, { provider, key }) => {
const success = this.setApiKey(provider, key); const success = this.setApiKey(provider, key);
@ -549,33 +580,17 @@ class ModelStateService {
} }
return success; return success;
}); });
ipcMain.handle('model:remove-api-key', async (e, { provider }) => { ipcMain.handle('model:remove-api-key', async (e, { provider }) => this.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;
});
ipcMain.handle('model:get-selected-models', () => this.getSelectedModels()); 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:get-available-models', (e, { type }) => this.getAvailableModels(type));
ipcMain.handle('model:are-providers-configured', () => this.areProvidersConfigured()); ipcMain.handle('model:are-providers-configured', () => this.areProvidersConfigured());
ipcMain.handle('model:get-current-model-info', (e, { type }) => this.getCurrentModelInfo(type)); ipcMain.handle('model:get-current-model-info', (e, { type }) => this.getCurrentModelInfo(type));
ipcMain.handle('model:get-provider-config', () => { ipcMain.handle('model:get-provider-config', () => this.getProviderConfig());
const serializableProviders = {};
for (const key in PROVIDERS) {
const { handler, ...rest } = PROVIDERS[key];
serializableProviders[key] = rest;
}
return serializableProviders;
});
} }
} }
module.exports = ModelStateService; // Export singleton instance
const modelStateService = new ModelStateService();
module.exports = modelStateService;

View File

@ -39,6 +39,26 @@ class OllamaService extends LocalAIServiceBase {
this._startHealthMonitoring(); 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() { getOllamaCliPath() {
if (this.getPlatform() === 'darwin') { if (this.getPlatform() === 'darwin') {
return '/Applications/Ollama.app/Contents/Resources/ollama'; return '/Applications/Ollama.app/Contents/Resources/ollama';

View File

@ -448,4 +448,6 @@ class WhisperService extends LocalAIServiceBase {
} }
} }
module.exports = { WhisperService }; // Export singleton instance
const whisperService = new WhisperService();
module.exports = whisperService;

View File

@ -4,6 +4,11 @@ const authService = require('../common/services/authService');
const settingsRepository = require('./repositories'); const settingsRepository = require('./repositories');
const { getStoredApiKey, getStoredProvider, windowPool } = require('../../window/windowManager'); 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({ const store = new Store({
name: 'pickle-glass-settings', name: 'pickle-glass-settings',
defaults: { defaults: {
@ -19,6 +24,51 @@ const NOTIFICATION_CONFIG = {
RETRY_BASE_DELAY: 1000, // exponential backoff base (ms) 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 // window targeting system
class WindowNotificationManager { class WindowNotificationManager {
constructor() { constructor() {
@ -373,7 +423,16 @@ function initialize() {
// cleanup // cleanup
windowNotificationManager.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.'); console.log('[SettingsService] Initialized and ready.');
} }
@ -406,4 +465,14 @@ module.exports = {
removeApiKey, removeApiKey,
updateContentProtection, updateContentProtection,
getAutoUpdateSetting, getAutoUpdateSetting,
setAutoUpdateSetting,
// Model settings facade
getModelSettings,
validateAndSaveKey,
clearApiKey,
setSelectedModel,
// Ollama facade
getOllamaStatus,
ensureOllamaReady,
shutdownOllama
}; };

View File

@ -25,7 +25,7 @@ const { EventEmitter } = require('events');
const askService = require('./features/ask/askService'); const askService = require('./features/ask/askService');
const settingsService = require('./features/settings/settingsService'); const settingsService = require('./features/settings/settingsService');
const sessionRepository = require('./features/common/repositories/session'); 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 sqliteClient = require('./features/common/services/sqliteClient');
const featureBridge = require('./bridge/featureBridge'); const featureBridge = require('./bridge/featureBridge');
@ -39,7 +39,6 @@ const listenService = new ListenService();
global.listenService = listenService; global.listenService = listenService;
//////// after_modelStateService //////// //////// after_modelStateService ////////
const modelStateService = new ModelStateService(authService);
global.modelStateService = modelStateService; global.modelStateService = modelStateService;
//////// after_modelStateService //////// //////// after_modelStateService ////////
@ -331,8 +330,7 @@ app.on('activate', () => {
}); });
function setupWhisperIpcHandlers() { function setupWhisperIpcHandlers() {
const { WhisperService } = require('./features/common/services/whisperService'); const whisperService = require('./features/common/services/whisperService');
const whisperService = new WhisperService();
// Forward download progress events to renderer // Forward download progress events to renderer
whisperService.on('downloadProgress', (data) => { whisperService.on('downloadProgress', (data) => {

View File

@ -543,9 +543,11 @@ export class SettingsView extends LitElement {
} }
async loadAutoUpdateSetting() { async loadAutoUpdateSetting() {
if (!window.require) return;
const { ipcRenderer } = window.require('electron');
this.autoUpdateLoading = true; this.autoUpdateLoading = true;
try { try {
const enabled = await window.api.feature.settings.getAutoUpdate(); const enabled = await ipcRenderer.invoke('settings:get-auto-update');
this.autoUpdateEnabled = enabled; this.autoUpdateEnabled = enabled;
console.log('Auto-update setting loaded:', enabled); console.log('Auto-update setting loaded:', enabled);
} catch (e) { } catch (e) {
@ -557,12 +559,13 @@ export class SettingsView extends LitElement {
} }
async handleToggleAutoUpdate() { async handleToggleAutoUpdate() {
if (this.autoUpdateLoading) return; if (!window.require || this.autoUpdateLoading) return;
const { ipcRenderer } = window.require('electron');
this.autoUpdateLoading = true; this.autoUpdateLoading = true;
this.requestUpdate(); this.requestUpdate();
try { try {
const newValue = !this.autoUpdateEnabled; 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) { if (result && result.success) {
this.autoUpdateEnabled = newValue; this.autoUpdateEnabled = newValue;
} else { } else {
@ -577,29 +580,32 @@ export class SettingsView extends LitElement {
//////// after_modelStateService //////// //////// after_modelStateService ////////
async loadInitialData() { async loadInitialData() {
if (!window.require) return;
this.isLoading = true; this.isLoading = true;
const { ipcRenderer } = window.require('electron');
try { try {
const [userState, config, storedKeys, availableLlm, availableStt, selectedModels, presets, contentProtection, shortcuts, ollamaStatus, whisperModelsResult] = await Promise.all([ const [userState, modelSettings, presets, contentProtection, shortcuts, ollamaStatus, whisperModelsResult] = await Promise.all([
window.api.feature.settings.getCurrentUser(), ipcRenderer.invoke('get-current-user'),
window.api.feature.settings.getProviderConfig(), // Provider 설정 로드 ipcRenderer.invoke('settings:get-model-settings'), // Facade call
window.api.feature.settings.getAllKeys(), ipcRenderer.invoke('settings:getPresets'),
window.api.feature.settings.getAvailableModels({ type: 'llm' }), ipcRenderer.invoke('get-content-protection-status'),
window.api.feature.settings.getAvailableModels({ type: 'stt' }), ipcRenderer.invoke('get-current-shortcuts'),
window.api.feature.settings.getSelectedModels(), ipcRenderer.invoke('settings:get-ollama-status'),
window.api.feature.settings.getPresets(), ipcRenderer.invoke('whisper:get-installed-models')
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; if (userState && userState.isLoggedIn) this.firebaseUser = userState;
this.providerConfig = config;
this.apiKeys = storedKeys; if (modelSettings.success) {
this.availableLlmModels = availableLlm; const { config, storedKeys, availableLlm, availableStt, selectedModels } = modelSettings.data;
this.availableSttModels = availableStt; this.providerConfig = config;
this.selectedLlm = selectedModels.llm; this.apiKeys = storedKeys;
this.selectedStt = selectedModels.stt; this.availableLlmModels = availableLlm;
this.availableSttModels = availableStt;
this.selectedLlm = selectedModels.llm;
this.selectedStt = selectedModels.stt;
}
this.presets = presets || []; this.presets = presets || [];
this.isContentProtectionOn = contentProtection; this.isContentProtectionOn = contentProtection;
this.shortcuts = shortcuts || {}; this.shortcuts = shortcuts || {};
@ -631,6 +637,7 @@ export class SettingsView extends LitElement {
} }
} }
async handleSaveKey(provider) { async handleSaveKey(provider) {
const input = this.shadowRoot.querySelector(`#key-input-${provider}`); const input = this.shadowRoot.querySelector(`#key-input-${provider}`);
if (!input) return; if (!input) return;
@ -639,9 +646,10 @@ export class SettingsView extends LitElement {
// For Ollama, we need to ensure it's ready first // For Ollama, we need to ensure it's ready first
if (provider === 'ollama') { if (provider === 'ollama') {
this.saving = true; this.saving = true;
const { ipcRenderer } = window.require('electron');
// First ensure Ollama is installed and running // 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) { if (!ensureResult.success) {
alert(`Failed to setup Ollama: ${ensureResult.error}`); alert(`Failed to setup Ollama: ${ensureResult.error}`);
this.saving = false; this.saving = false;
@ -649,10 +657,9 @@ export class SettingsView extends LitElement {
} }
// Now validate (which will check if service is running) // 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) { if (result.success) {
this.apiKeys = { ...this.apiKeys, [provider]: 'local' };
await this.refreshModelData(); await this.refreshModelData();
await this.refreshOllamaStatus(); await this.refreshOllamaStatus();
} else { } else {
@ -665,10 +672,10 @@ export class SettingsView extends LitElement {
// For Whisper, just enable it // For Whisper, just enable it
if (provider === 'whisper') { if (provider === 'whisper') {
this.saving = true; 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) { if (result.success) {
this.apiKeys = { ...this.apiKeys, [provider]: 'local' };
await this.refreshModelData(); await this.refreshModelData();
} else { } else {
alert(`Failed to enable Whisper: ${result.error}`); alert(`Failed to enable Whisper: ${result.error}`);
@ -679,10 +686,10 @@ export class SettingsView extends LitElement {
// For other providers, use the normal flow // For other providers, use the normal flow
this.saving = true; 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) { if (result.success) {
this.apiKeys = { ...this.apiKeys, [provider]: key };
await this.refreshModelData(); await this.refreshModelData();
} else { } else {
alert(`Failed to save ${provider} key: ${result.error}`); alert(`Failed to save ${provider} key: ${result.error}`);
@ -693,24 +700,23 @@ export class SettingsView extends LitElement {
async handleClearKey(provider) { async handleClearKey(provider) {
this.saving = true; this.saving = true;
await window.api.feature.settings.removeApiKey({ provider }); const { ipcRenderer } = window.require('electron');
this.apiKeys = { ...this.apiKeys, [provider]: '' }; await ipcRenderer.invoke('settings:clear-api-key', { provider });
await this.refreshModelData(); await this.refreshModelData();
this.saving = false; this.saving = false;
} }
async refreshModelData() { async refreshModelData() {
const [availableLlm, availableStt, selected, storedKeys] = await Promise.all([ const { ipcRenderer } = window.require('electron');
window.api.feature.settings.getAvailableModels({ type: 'llm' }), const result = await ipcRenderer.invoke('settings:get-model-settings');
window.api.feature.settings.getAvailableModels({ type: 'stt' }), if (result.success) {
window.api.feature.settings.getSelectedModels(), const { availableLlm, availableStt, selectedModels, storedKeys } = result.data;
window.api.feature.settings.getAllKeys() this.availableLlmModels = availableLlm;
]); this.availableSttModels = availableStt;
this.availableLlmModels = availableLlm; this.selectedLlm = selectedModels.llm;
this.availableSttModels = availableStt; this.selectedStt = selectedModels.stt;
this.selectedLlm = selected.llm; this.apiKeys = storedKeys;
this.selectedStt = selected.stt; }
this.apiKeys = storedKeys;
this.requestUpdate(); this.requestUpdate();
} }
@ -755,7 +761,8 @@ export class SettingsView extends LitElement {
} }
this.saving = true; 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 === 'llm') this.selectedLlm = modelId;
if (type === 'stt') this.selectedStt = modelId; if (type === 'stt') this.selectedStt = modelId;
this.isLlmListVisible = false; this.isLlmListVisible = false;
@ -765,7 +772,8 @@ export class SettingsView extends LitElement {
} }
async refreshOllamaStatus() { 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) { if (ollamaStatus?.success) {
this.ollamaStatus = { installed: ollamaStatus.installed, running: ollamaStatus.running }; this.ollamaStatus = { installed: ollamaStatus.installed, running: ollamaStatus.running };
this.ollamaModels = ollamaStatus.models || []; this.ollamaModels = ollamaStatus.models || [];
@ -809,6 +817,8 @@ export class SettingsView extends LitElement {
this.requestUpdate(); this.requestUpdate();
try { try {
const { ipcRenderer } = window.require('electron');
// Set up progress listener // Set up progress listener
const progressHandler = (event, { modelId: id, progress }) => { const progressHandler = (event, { modelId: id, progress }) => {
if (id === modelId) { 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 // Start download
const result = await window.api.feature.settings.downloadWhisperModel(modelId); const result = await ipcRenderer.invoke('whisper:download-model', modelId);
if (result.success) { if (result.success) {
// Auto-select the model after download // Auto-select the model after download
@ -830,7 +840,7 @@ export class SettingsView extends LitElement {
} }
// Cleanup // Cleanup
window.api.feature.settings.removeOnWhisperDownloadProgress(progressHandler); ipcRenderer.removeListener('whisper:download-progress', progressHandler);
} catch (error) { } catch (error) {
console.error(`[SettingsView] Error downloading Whisper model ${modelId}:`, error); console.error(`[SettingsView] Error downloading Whisper model ${modelId}:`, error);
alert(`Error downloading ${modelId}: ${error.message}`); alert(`Error downloading ${modelId}: ${error.message}`);
@ -862,12 +872,17 @@ export class SettingsView extends LitElement {
if (this.wasJustDragged) return if (this.wasJustDragged) return
console.log("Requesting Firebase authentication from main process...") 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 //////// //////// after_modelStateService ////////
openShortcutEditor() { openShortcutEditor() {
window.api.feature.settings.openShortcutEditor(); if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('open-shortcut-editor');
}
} }
connectedCallback() { connectedCallback() {
@ -905,6 +920,10 @@ export class SettingsView extends LitElement {
} }
setupIpcListeners() { setupIpcListeners() {
if (!window.require) return;
const { ipcRenderer } = window.require('electron');
this._userStateListener = (event, userState) => { this._userStateListener = (event, userState) => {
console.log('[SettingsView] Received user-state-changed:', userState); console.log('[SettingsView] Received user-state-changed:', userState);
if (userState && userState.isLoggedIn) { if (userState && userState.isLoggedIn) {
@ -926,7 +945,7 @@ export class SettingsView extends LitElement {
this._presetsUpdatedListener = async (event) => { this._presetsUpdatedListener = async (event) => {
console.log('[SettingsView] Received presets-updated, refreshing presets'); console.log('[SettingsView] Received presets-updated, refreshing presets');
try { try {
const presets = await window.api.feature.settings.getPresets(); const presets = await ipcRenderer.invoke('settings:getPresets');
this.presets = presets || []; this.presets = presets || [];
// 현재 선택된 프리셋이 삭제되었는지 확인 (사용자 프리셋만 고려) // 현재 선택된 프리셋이 삭제되었는지 확인 (사용자 프리셋만 고려)
@ -945,24 +964,28 @@ export class SettingsView extends LitElement {
this.shortcuts = keybinds; this.shortcuts = keybinds;
}; };
window.api.feature.settings.onUserStateChanged(this._userStateListener); ipcRenderer.on('user-state-changed', this._userStateListener);
window.api.feature.settings.onSettingsUpdated(this._settingsUpdatedListener); ipcRenderer.on('settings-updated', this._settingsUpdatedListener);
window.api.feature.settings.onPresetsUpdated(this._presetsUpdatedListener); ipcRenderer.on('presets-updated', this._presetsUpdatedListener);
window.api.feature.settings.onShortcutsUpdated(this._shortcutListener); ipcRenderer.on('shortcuts-updated', this._shortcutListener);
} }
cleanupIpcListeners() { cleanupIpcListeners() {
if (!window.require) return;
const { ipcRenderer } = window.require('electron');
if (this._userStateListener) { if (this._userStateListener) {
window.api.feature.settings.removeOnUserStateChanged(this._userStateListener); ipcRenderer.removeListener('user-state-changed', this._userStateListener);
} }
if (this._settingsUpdatedListener) { if (this._settingsUpdatedListener) {
window.api.feature.settings.removeOnSettingsUpdated(this._settingsUpdatedListener); ipcRenderer.removeListener('settings-updated', this._settingsUpdatedListener);
} }
if (this._presetsUpdatedListener) { if (this._presetsUpdatedListener) {
window.api.feature.settings.removeOnPresetsUpdated(this._presetsUpdatedListener); ipcRenderer.removeListener('presets-updated', this._presetsUpdatedListener);
} }
if (this._shortcutListener) { 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 = () => { handleMouseEnter = () => {
window.api.window.cancelHideSettingsWindow(); if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.send('cancel-hide-settings-window');
}
} }
handleMouseLeave = () => { handleMouseLeave = () => {
window.api.window.hideSettingsWindow(); if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.send('hide-settings-window');
}
} }
// getMainShortcuts() { // getMainShortcuts() {
@ -1050,74 +1079,70 @@ export class SettingsView extends LitElement {
handleMoveLeft() { handleMoveLeft() {
console.log('Move Left clicked'); 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() { handleMoveRight() {
console.log('Move Right clicked'); 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() { async handlePersonalize() {
console.log('Personalize clicked'); console.log('Personalize clicked');
try { if (window.require) {
await window.api.window.openLoginPage(); const { ipcRenderer } = window.require('electron');
} catch (error) { try {
console.error('Failed to open personalize page:', error); await ipcRenderer.invoke('open-login-page');
} catch (error) {
console.error('Failed to open personalize page:', error);
}
} }
} }
async handleToggleInvisibility() { async handleToggleInvisibility() {
console.log('Toggle Invisibility clicked'); console.log('Toggle Invisibility clicked');
this.isContentProtectionOn = await window.api.window.toggleContentProtection(); if (window.require) {
this.requestUpdate(); const { ipcRenderer } = window.require('electron');
} this.isContentProtectionOn = await ipcRenderer.invoke('toggle-content-protection');
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);
} }
} }
async handleClearApiKey() {
console.log('Clear API Key clicked');
await window.api.feature.settings.removeApiKey();
this.apiKey = null;
this.requestUpdate();
}
handleQuit() { handleQuit() {
console.log('Quit clicked'); console.log('Quit clicked');
window.api.window.quitApplication(); if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('quit-application');
}
} }
handleFirebaseLogout() { handleFirebaseLogout() {
console.log('Firebase Logout clicked'); console.log('Firebase Logout clicked');
window.api.window.firebaseLogout(); if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('firebase-logout');
}
} }
async handleOllamaShutdown() { async handleOllamaShutdown() {
console.log('[SettingsView] Shutting down Ollama service...'); console.log('[SettingsView] Shutting down Ollama service...');
if (!window.require) return;
const { ipcRenderer } = window.require('electron');
try { try {
// Show loading state // Show loading state
this.ollamaStatus = { ...this.ollamaStatus, running: false }; this.ollamaStatus = { ...this.ollamaStatus, running: false };
this.requestUpdate(); 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) { if (result.success) {
console.log('[SettingsView] Ollama shut down successfully'); console.log('[SettingsView] Ollama shut down successfully');
@ -1135,139 +1160,329 @@ export class SettingsView extends LitElement {
} }
} }
//////// after_modelStateService ////////
render() {
if (this.isLoading) {
return html`
<div class="settings-container">
<div class="loading-state">
<div class="loading-spinner"></div>
<span>Loading...</span>
</div>
</div>
`;
}
//////// before_modelStateService //////// const loggedIn = !!this.firebaseUser;
// render() {
// if (this.isLoading) {
// return html`
// <div class="settings-container">
// <div class="loading-state">
// <div class="loading-spinner"></div>
// <span>Loading...</span>
// </div>
// </div>
// `;
// }
// const loggedIn = !!this.firebaseUser; const apiKeyManagementHTML = html`
<div class="api-key-section">
${Object.entries(this.providerConfig)
.filter(([id, config]) => !id.includes('-glass'))
.map(([id, config]) => {
if (id === 'ollama') {
// Special UI for Ollama
return html`
<div class="provider-key-group">
<label>${config.name} (Local)</label>
${this.ollamaStatus.installed && this.ollamaStatus.running ? html`
<div style="padding: 8px; background: rgba(0,255,0,0.1); border-radius: 4px; font-size: 11px; color: rgba(0,255,0,0.8);">
Ollama is running
</div>
<button class="settings-button full-width danger" @click=${this.handleOllamaShutdown}>
Stop Ollama Service
</button>
` : this.ollamaStatus.installed ? html`
<div style="padding: 8px; background: rgba(255,200,0,0.1); border-radius: 4px; font-size: 11px; color: rgba(255,200,0,0.8);">
Ollama installed but not running
</div>
<button class="settings-button full-width" @click=${() => this.handleSaveKey(id)}>
Start Ollama
</button>
` : html`
<div style="padding: 8px; background: rgba(255,100,100,0.1); border-radius: 4px; font-size: 11px; color: rgba(255,100,100,0.8);">
Ollama not installed
</div>
<button class="settings-button full-width" @click=${() => this.handleSaveKey(id)}>
Install & Setup Ollama
</button>
`}
</div>
`;
}
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`
<div class="provider-key-group">
<label>${config.name} (Local STT)</label>
${this.apiKeys[id] === 'local' ? html`
<div style="padding: 8px; background: rgba(0,255,0,0.1); border-radius: 4px; font-size: 11px; color: rgba(0,255,0,0.8); margin-bottom: 8px;">
Whisper is enabled
</div>
<!-- Whisper Model Selection Dropdown -->
<label style="font-size: 10px; margin-top: 8px;">Select Model:</label>
<select
class="model-dropdown"
style="width: 100%; padding: 6px; background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.2); color: white; border-radius: 4px; font-size: 11px; margin-bottom: 8px;"
@change=${(e) => this.handleWhisperModelSelect(e.target.value)}
.value=${selectedWhisperModel || ''}
>
<option value="">Choose a model...</option>
${whisperModels.map(model => {
const isInstalling = this.installingModels[model.id] !== undefined;
const progress = this.installingModels[model.id] || 0;
let statusText = '';
if (isInstalling) {
statusText = ` (Downloading ${progress}%)`;
} else if (model.installed) {
statusText = ' (Installed)';
}
return html`
<option value="${model.id}" ?disabled=${isInstalling}>
${model.name}${statusText}
</option>
`;
})}
</select>
${Object.entries(this.installingModels).map(([modelId, progress]) => {
if (modelId.startsWith('whisper-') && progress !== undefined) {
return html`
<div style="margin: 8px 0;">
<div style="font-size: 10px; color: rgba(255,255,255,0.7); margin-bottom: 4px;">
Downloading ${modelId}...
</div>
<div class="install-progress" style="height: 4px; background: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden;">
<div class="install-progress-bar" style="height: 100%; background: rgba(0, 122, 255, 0.8); width: ${progress}%; transition: width 0.3s ease;"></div>
</div>
</div>
`;
}
return null;
})}
<button class="settings-button full-width danger" @click=${() => this.handleClearKey(id)}>
Disable Whisper
</button>
` : html`
<button class="settings-button full-width" @click=${() => this.handleSaveKey(id)}>
Enable Whisper STT
</button>
`}
</div>
`;
}
// Regular providers
return html`
<div class="provider-key-group">
<label for="key-input-${id}">${config.name} API Key</label>
<input type="password" id="key-input-${id}"
placeholder=${loggedIn ? "Using Pickle's Key" : `Enter ${config.name} API Key`}
.value=${this.apiKeys[id] || ''}
>
<div class="key-buttons">
<button class="settings-button" @click=${() => this.handleSaveKey(id)} >Save</button>
<button class="settings-button danger" @click=${() => this.handleClearKey(id)} }>Clear</button>
</div>
</div>
`;
})}
</div>
`;
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` const modelSelectionHTML = html`
// <div class="settings-container"> <div class="model-selection-section">
// <div class="header-section"> <div class="model-select-group">
// <div> <label>LLM Model: <strong>${getModelName('llm', this.selectedLlm) || 'Not Set'}</strong></label>
// <h1 class="app-title">Pickle Glass</h1> <button class="settings-button full-width" @click=${() => this.toggleModelList('llm')} ?disabled=${this.saving || this.availableLlmModels.length === 0}>
// <div class="account-info"> Change LLM Model
// ${this.firebaseUser </button>
// ? html`Account: ${this.firebaseUser.email || 'Logged In'}` ${this.isLlmListVisible ? html`
// : this.apiKey && this.apiKey.length > 10 <div class="model-list">
// ? html`API Key: ${this.apiKey.substring(0, 6)}...${this.apiKey.substring(this.apiKey.length - 6)}` ${this.availableLlmModels.map(model => {
// : `Account: Not Logged In` const isOllama = this.getProviderForModel('llm', model.id) === 'ollama';
// } const ollamaModel = isOllama ? this.ollamaModels.find(m => m.name === model.id) : null;
// </div> const isInstalling = this.installingModels[model.id] !== undefined;
// </div> const installProgress = this.installingModels[model.id] || 0;
// <div class="invisibility-icon ${this.isContentProtectionOn ? 'visible' : ''}" title="Invisibility is On">
// <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"> return html`
// <path d="M9.785 7.41787C8.7 7.41787 7.79 8.19371 7.55667 9.22621C7.0025 8.98704 6.495 9.05121 6.11 9.22037C5.87083 8.18204 4.96083 7.41787 3.88167 7.41787C2.61583 7.41787 1.58333 8.46204 1.58333 9.75121C1.58333 11.0404 2.61583 12.0845 3.88167 12.0845C5.08333 12.0845 6.06333 11.1395 6.15667 9.93787C6.355 9.79787 6.87417 9.53537 7.51 9.94954C7.615 11.1454 8.58333 12.0845 9.785 12.0845C11.0508 12.0845 12.0833 11.0404 12.0833 9.75121C12.0833 8.46204 11.0508 7.41787 9.785 7.41787ZM3.88167 11.4195C2.97167 11.4195 2.2425 10.6729 2.2425 9.75121C2.2425 8.82954 2.9775 8.08287 3.88167 8.08287C4.79167 8.08287 5.52083 8.82954 5.52083 9.75121C5.52083 10.6729 4.79167 11.4195 3.88167 11.4195ZM9.785 11.4195C8.875 11.4195 8.14583 10.6729 8.14583 9.75121C8.14583 8.82954 8.875 8.08287 9.785 8.08287C10.695 8.08287 11.43 8.82954 11.43 9.75121C11.43 10.6729 10.6892 11.4195 9.785 11.4195ZM12.6667 5.95954H1V6.83454H12.6667V5.95954ZM8.8925 1.36871C8.76417 1.08287 8.4375 0.931207 8.12833 1.03037L6.83333 1.46204L5.5325 1.03037L5.50333 1.02454C5.19417 0.93704 4.8675 1.10037 4.75083 1.39787L3.33333 5.08454H10.3333L8.91 1.39787L8.8925 1.36871Z" fill="white"/> <div class="model-item ${this.selectedLlm === model.id ? 'selected' : ''}"
// </svg> @click=${() => this.selectModel('llm', model.id)}>
// </div> <span>${model.name}</span>
// </div> ${isOllama ? html`
${isInstalling ? html`
<div class="install-progress">
<div class="install-progress-bar" style="width: ${installProgress}%"></div>
</div>
` : ollamaModel?.installed ? html`
<span class="model-status installed"> Installed</span>
` : html`
<span class="model-status not-installed">Click to install</span>
`}
` : ''}
</div>
`;
})}
</div>
` : ''}
</div>
<div class="model-select-group">
<label>STT Model: <strong>${getModelName('stt', this.selectedStt) || 'Not Set'}</strong></label>
<button class="settings-button full-width" @click=${() => this.toggleModelList('stt')} ?disabled=${this.saving || this.availableSttModels.length === 0}>
Change STT Model
</button>
${this.isSttListVisible ? html`
<div class="model-list">
${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`
<div class="model-item ${this.selectedStt === model.id ? 'selected' : ''}"
@click=${() => this.selectModel('stt', model.id)}>
<span>${model.name}</span>
${isWhisper && isInstalling ? html`
<div class="install-progress">
<div class="install-progress-bar" style="width: ${installProgress}%"></div>
</div>
` : ''}
</div>
`;
})}
</div>
` : ''}
</div>
</div>
`;
// <div class="api-key-section"> return html`
// <input <div class="settings-container">
// type="password" <div class="header-section">
// id="api-key-input" <div>
// placeholder="Enter API Key" <h1 class="app-title">Pickle Glass</h1>
// .value=${this.apiKey || ''} <div class="account-info">
// ?disabled=${loggedIn} ${this.firebaseUser
// > ? html`Account: ${this.firebaseUser.email || 'Logged In'}`
// <button class="settings-button full-width" @click=${this.handleSaveApiKey} ?disabled=${loggedIn}> : `Account: Not Logged In`
// Save API Key }
// </button> </div>
// </div> </div>
<div class="invisibility-icon ${this.isContentProtectionOn ? 'visible' : ''}" title="Invisibility is On">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.785 7.41787C8.7 7.41787 7.79 8.19371 7.55667 9.22621C7.0025 8.98704 6.495 9.05121 6.11 9.22037C5.87083 8.18204 4.96083 7.41787 3.88167 7.41787C2.61583 7.41787 1.58333 8.46204 1.58333 9.75121C1.58333 11.0404 2.61583 12.0845 3.88167 12.0845C5.08333 12.0845 6.06333 11.1395 6.15667 9.93787C6.355 9.79787 6.87417 9.53537 7.51 9.94954C7.615 11.1454 8.58333 12.0845 9.785 12.0845C11.0508 12.0845 12.0833 11.0404 12.0833 9.75121C12.0833 8.46204 11.0508 7.41787 9.785 7.41787ZM3.88167 11.4195C2.97167 11.4195 2.2425 10.6729 2.2425 9.75121C2.2425 8.82954 2.9775 8.08287 3.88167 8.08287C4.79167 8.08287 5.52083 8.82954 5.52083 9.75121C5.52083 10.6729 4.79167 11.4195 3.88167 11.4195ZM9.785 11.4195C8.875 11.4195 8.14583 10.6729 8.14583 9.75121C8.14583 8.82954 8.875 8.08287 9.785 8.08287C10.695 8.08287 11.43 8.82954 11.43 9.75121C11.43 10.6729 10.6892 11.4195 9.785 11.4195ZM12.6667 5.95954H1V6.83454H12.6667V5.95954ZM8.8925 1.36871C8.76417 1.08287 8.4375 0.931207 8.12833 1.03037L6.83333 1.46204L5.5325 1.03037L5.50333 1.02454C5.19417 0.93704 4.8675 1.10037 4.75083 1.39787L3.33333 5.08454H10.3333L8.91 1.39787L8.8925 1.36871Z" fill="white"/>
</svg>
</div>
</div>
// <div class="shortcuts-section"> ${apiKeyManagementHTML}
// ${this.getMainShortcuts().map(shortcut => html` ${modelSelectionHTML}
// <div class="shortcut-item">
// <span class="shortcut-name">${shortcut.name}</span>
// <div class="shortcut-keys">
// <span class="cmd-key">⌘</span>
// <span class="shortcut-key">${shortcut.key}</span>
// </div>
// </div>
// `)}
// </div>
// <!-- Preset Management Section --> <div class="buttons-section" style="border-top: 1px solid rgba(255, 255, 255, 0.1); padding-top: 6px; margin-top: 6px;">
// <div class="preset-section"> <button class="settings-button full-width" @click=${this.openShortcutEditor}>
// <div class="preset-header"> Edit Shortcuts
// <span class="preset-title"> </button>
// My Presets </div>
// <span class="preset-count">(${this.presets.filter(p => p.is_default === 0).length})</span>
// </span>
// <span class="preset-toggle" @click=${this.togglePresets}> <div class="shortcuts-section">
// ${this.showPresets ? '▼' : '▶'} ${this.getMainShortcuts().map(shortcut => html`
// </span> <div class="shortcut-item">
// </div> <span class="shortcut-name">${shortcut.name}</span>
<div class="shortcut-keys">
${this.renderShortcutKeys(shortcut.accelerator)}
</div>
</div>
`)}
</div>
<div class="preset-section">
<div class="preset-header">
<span class="preset-title">
My Presets
<span class="preset-count">(${this.presets.filter(p => p.is_default === 0).length})</span>
</span>
<span class="preset-toggle" @click=${this.togglePresets}>
${this.showPresets ? '▼' : '▶'}
</span>
</div>
// <div class="preset-list ${this.showPresets ? '' : 'hidden'}"> <div class="preset-list ${this.showPresets ? '' : 'hidden'}">
// ${this.presets.filter(p => p.is_default === 0).length === 0 ? html` ${this.presets.filter(p => p.is_default === 0).length === 0 ? html`
// <div class="no-presets-message"> <div class="no-presets-message">
// No custom presets yet.<br> No custom presets yet.<br>
// <span class="web-link" @click=${this.handlePersonalize}> <span class="web-link" @click=${this.handlePersonalize}>
// Create your first preset Create your first preset
// </span> </span>
// </div> </div>
// ` : this.presets.filter(p => p.is_default === 0).map(preset => html` ` : this.presets.filter(p => p.is_default === 0).map(preset => html`
// <div class="preset-item ${this.selectedPreset?.id === preset.id ? 'selected' : ''}" <div class="preset-item ${this.selectedPreset?.id === preset.id ? 'selected' : ''}"
// @click=${() => this.handlePresetSelect(preset)}> @click=${() => this.handlePresetSelect(preset)}>
// <span class="preset-name">${preset.title}</span> <span class="preset-name">${preset.title}</span>
// ${this.selectedPreset?.id === preset.id ? html`<span class="preset-status">Selected</span>` : ''} ${this.selectedPreset?.id === preset.id ? html`<span class="preset-status">Selected</span>` : ''}
// </div> </div>
// `)} `)}
// </div> </div>
// </div> </div>
// <div class="buttons-section"> <div class="buttons-section">
// <button class="settings-button full-width" @click=${this.handlePersonalize}> <button class="settings-button full-width" @click=${this.handlePersonalize}>
// <span>Personalize / Meeting Notes</span> <span>Personalize / Meeting Notes</span>
// </button> </button>
<button class="settings-button full-width" @click=${this.handleToggleAutoUpdate} ?disabled=${this.autoUpdateLoading}>
<span>Automatic Updates: ${this.autoUpdateEnabled ? 'On' : 'Off'}</span>
</button>
// <div class="move-buttons"> <div class="move-buttons">
// <button class="settings-button half-width" @click=${this.handleMoveLeft}> <button class="settings-button half-width" @click=${this.handleMoveLeft}>
// <span>← Move</span> <span> Move</span>
// </button> </button>
// <button class="settings-button half-width" @click=${this.handleMoveRight}> <button class="settings-button half-width" @click=${this.handleMoveRight}>
// <span>Move →</span> <span>Move </span>
// </button> </button>
// </div> </div>
// <button class="settings-button full-width" @click=${this.handleToggleInvisibility}> <button class="settings-button full-width" @click=${this.handleToggleInvisibility}>
// <span>${this.isContentProtectionOn ? 'Disable Invisibility' : 'Enable Invisibility'}</span> <span>${this.isContentProtectionOn ? 'Disable Invisibility' : 'Enable Invisibility'}</span>
// </button> </button>
// <div class="bottom-buttons"> <div class="bottom-buttons">
// ${this.firebaseUser ${this.firebaseUser
// ? html` ? html`
// <button class="settings-button half-width danger" @click=${this.handleFirebaseLogout}> <button class="settings-button half-width danger" @click=${this.handleFirebaseLogout}>
// <span>Logout</span> <span>Logout</span>
// </button> </button>
// ` `
// : html` : html`
// <button class="settings-button half-width danger" @click=${this.handleClearApiKey}> <button class="settings-button half-width" @click=${this.handleUsePicklesKey}>
// <span>Clear API Key</span> <span>Login</span>
// </button> </button>
// ` `
// } }
// <button class="settings-button half-width danger" @click=${this.handleQuit}> <button class="settings-button half-width danger" @click=${this.handleQuit}>
// <span>Quit</span> <span>Quit</span>
// </button> </button>
// </div> </div>
// </div> </div>
// </div> </div>
// `; `;
// } }
//////// before_modelStateService ////////
//////// after_modelStateService //////// //////// after_modelStateService ////////
} }

View File

@ -295,6 +295,10 @@ function createFeatureWindows(header, namesToCreate) {
}); });
} }
windowPool.set('settings', settings); windowPool.set('settings', settings);
if (!app.isPackaged) {
settings.webContents.openDevTools({ mode: 'detach' });
}
break; break;
} }
@ -1051,6 +1055,7 @@ function setupIpcHandlers(movementManager) {
const win = windowPool.get('settings'); const win = windowPool.get('settings');
if (win && !win.isDestroyed()) { if (win && !win.isDestroyed()) {
console.log('[WindowManager] Showing settings window');
if (settingsHideTimer) { if (settingsHideTimer) {
clearTimeout(settingsHideTimer); clearTimeout(settingsHideTimer);
settingsHideTimer = null; settingsHideTimer = null;
@ -1077,6 +1082,9 @@ function setupIpcHandlers(movementManager) {
win.show(); win.show();
win.moveTop(); win.moveTop();
win.setAlwaysOnTop(true); win.setAlwaysOnTop(true);
console.log('[WindowManager] Settings window shown');
} else {
console.log('[WindowManager] Settings window not found');
} }
}); });