This commit is contained in:
jhyang0 2025-07-13 04:57:09 +09:00
parent bf20d002ba
commit c5b190b522
19 changed files with 749 additions and 584 deletions

View File

@ -1,10 +1,155 @@
// src/bridge/windowBridge.js
const { ipcMain, BrowserWindow } = require('electron');
const { windowPool, settingsHideTimer, app, shell } = require('../window/windowManager'); // 필요 변수 require
const { ipcMain, BrowserWindow, globalShortcut } = require('electron');
module.exports = {
// Renderer로부터의 요청을 수신
initialize() {
// windowManager에서 필요한 변수들을 매개변수로 받도록 수정
initialize(windowPool, app, shell, getCurrentDisplay, createFeatureWindows, movementManager, getContentProtectionStatus, setContentProtection, updateLayout) {
let settingsHideTimer = null;
// 기존
ipcMain.on('window:hide', (e) => BrowserWindow.fromWebContents(e.sender)?.hide());
// windowManager 관련 추가
ipcMain.handle('toggle-content-protection', () => {
// windowManager의 toggle-content-protection 로직
const newStatus = !getContentProtectionStatus();
setContentProtection(newStatus);
return newStatus;
});
ipcMain.handle('resize-header-window', (event, { width, height }) => {
const header = windowPool.get('header');
if (header) {
console.log(`[WindowBridge] Resize request: ${width}x${height}`);
// Prevent resizing during animations or if already at target size
if (movementManager && movementManager.isAnimating) {
console.log('[WindowBridge] Skipping resize during animation');
return { success: false, error: 'Cannot resize during animation' };
}
const currentBounds = header.getBounds();
console.log(`[WindowBridge] Current bounds: ${currentBounds.width}x${currentBounds.height} at (${currentBounds.x}, ${currentBounds.y})`);
// Skip if already at target size to prevent unnecessary operations
if (currentBounds.width === width && currentBounds.height === height) {
console.log('[WindowBridge] Already at target size, skipping resize');
return { success: true };
}
const wasResizable = header.isResizable();
if (!wasResizable) {
header.setResizable(true);
}
// Calculate the center point of the current window
const centerX = currentBounds.x + currentBounds.width / 2;
// Calculate new X position to keep the window centered
const newX = Math.round(centerX - width / 2);
// Get the current display to ensure we stay within bounds
const display = getCurrentDisplay(header);
const { x: workAreaX, width: workAreaWidth } = display.workArea;
// Clamp the new position to stay within display bounds
const clampedX = Math.max(workAreaX, Math.min(workAreaX + workAreaWidth - width, newX));
header.setBounds({ x: clampedX, y: currentBounds.y, width, height });
if (!wasResizable) {
header.setResizable(false);
}
// Update layout after resize
if (updateLayout) {
updateLayout();
}
return { success: true };
}
return { success: false, error: 'Header window not found' };
});
ipcMain.handle('get-content-protection-status', () => {
return getContentProtectionStatus();
});
ipcMain.handle('open-shortcut-editor', () => {
// open-shortcut-editor 로직
const header = windowPool.get('header');
if (!header) return;
globalShortcut.unregisterAll();
createFeatureWindows(header, 'shortcut-settings');
});
// 추가: 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);
settingsHideTimer = null;
}
// 위치 조정 로직
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);
}
});
ipcMain.on('hide-settings-window', (event) => {
const window = windowPool.get("settings");
if (window && !window.isDestroyed()) {
if (settingsHideTimer) {
clearTimeout(settingsHideTimer);
}
settingsHideTimer = setTimeout(() => {
if (window && !window.isDestroyed()) {
window.setAlwaysOnTop(false);
window.hide();
}
settingsHideTimer = null;
}, 200);
window.__lockedByButton = false;
}
});
ipcMain.on('cancel-hide-settings-window', (event) => {
if (settingsHideTimer) {
clearTimeout(settingsHideTimer);
settingsHideTimer = null;
}
});
// 로그인 페이지 열기
ipcMain.handle('open-login-page', () => {
const webUrl = process.env.pickleglass_WEB_URL || 'http://localhost:3000';
const personalizeUrl = `${webUrl}/personalize?desktop=true`;
shell.openExternal(personalizeUrl);
console.log('Opening personalization page:', personalizeUrl);
});
// 윈도우 이동
ipcMain.handle('move-window-step', (event, direction) => {
if (movementManager) {
movementManager.moveStep(direction);
}
});
},
// Renderer로 상태를 전송

View File

@ -2,10 +2,10 @@ export class LocalProgressTracker {
constructor(serviceName) {
this.serviceName = serviceName;
this.activeOperations = new Map(); // operationId -> { controller, onProgress }
this.ipcRenderer = window.require?.('electron')?.ipcRenderer;
if (!this.ipcRenderer) {
throw new Error(`${serviceName} requires Electron environment`);
// Check if we're in renderer process with window.api available
if (!window.api) {
throw new Error(`${serviceName} requires Electron environment with contextBridge`);
}
this.globalProgressHandler = (event, data) => {
@ -15,14 +15,14 @@ export class LocalProgressTracker {
}
};
const progressEvents = {
'ollama': 'ollama:pull-progress',
'whisper': 'whisper:download-progress'
};
// Set up progress listeners based on service name
if (serviceName.toLowerCase() === 'ollama') {
window.api.settingsView.onOllamaPullProgress(this.globalProgressHandler);
} else if (serviceName.toLowerCase() === 'whisper') {
window.api.settingsView.onWhisperDownloadProgress(this.globalProgressHandler);
}
const eventName = progressEvents[serviceName.toLowerCase()] || `${serviceName}:progress`;
this.progressEvent = eventName;
this.ipcRenderer.on(eventName, this.globalProgressHandler);
this.progressEvent = serviceName.toLowerCase();
}
async trackOperation(operationId, operationType, onProgress) {
@ -35,15 +35,16 @@ export class LocalProgressTracker {
this.activeOperations.set(operationId, operation);
try {
const ipcChannels = {
'ollama': { install: 'ollama:pull-model' },
'whisper': { download: 'whisper:download-model' }
};
let result;
const channel = ipcChannels[this.serviceName.toLowerCase()]?.[operationType] ||
`${this.serviceName}:${operationType}`;
const result = await this.ipcRenderer.invoke(channel, operationId);
// Use appropriate API call based on service and operation
if (this.serviceName.toLowerCase() === 'ollama' && operationType === 'install') {
result = await window.api.settingsView.pullOllamaModel(operationId);
} else if (this.serviceName.toLowerCase() === 'whisper' && operationType === 'download') {
result = await window.api.settingsView.downloadWhisperModel(operationId);
} else {
throw new Error(`Unsupported operation: ${this.serviceName}:${operationType}`);
}
if (!result.success) {
throw new Error(result.error || `${operationType} failed`);
@ -93,8 +94,12 @@ export class LocalProgressTracker {
destroy() {
this.cancelAllOperations();
if (this.ipcRenderer) {
this.ipcRenderer.removeListener(this.progressEvent, this.globalProgressHandler);
// Remove progress listeners based on service name
if (this.progressEvent === 'ollama') {
window.api.settingsView.removeOnOllamaPullProgress(this.globalProgressHandler);
} else if (this.progressEvent === 'whisper') {
window.api.settingsView.removeOnWhisperDownloadProgress(this.globalProgressHandler);
}
}
}

View File

@ -2,72 +2,231 @@
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('api', {
feature: {
// 기존 ask 관련 유지
submitAsk: (query) => ipcRenderer.invoke('feature:ask', query),
onAskProgress: (callback) => ipcRenderer.on('feature:ask:progress', (e, p) => callback(p)),
settings: {
// invoke methods
getCurrentUser: () => ipcRenderer.invoke('get-current-user'),
getProviderConfig: () => ipcRenderer.invoke('model:get-provider-config'),
getAllKeys: () => ipcRenderer.invoke('model:get-all-keys'),
getAvailableModels: (type) => ipcRenderer.invoke('model:get-available-models', type),
getSelectedModels: () => ipcRenderer.invoke('model:get-selected-models'),
getPresets: () => ipcRenderer.invoke('settings:getPresets'),
getContentProtectionStatus: () => ipcRenderer.invoke('get-content-protection-status'),
getCurrentShortcuts: () => ipcRenderer.invoke('get-current-shortcuts'),
getOllamaStatus: () => ipcRenderer.invoke('ollama:get-status'),
getWhisperInstalledModels: () => ipcRenderer.invoke('whisper:get-installed-models'),
ollamaEnsureReady: () => ipcRenderer.invoke('ollama:ensure-ready'),
validateKey: (data) => ipcRenderer.invoke('model:validate-key', data),
getAutoUpdate: () => ipcRenderer.invoke('settings:get-auto-update'),
setAutoUpdate: (isEnabled) => ipcRenderer.invoke('settings:set-auto-update', isEnabled),
removeApiKey: (provider) => ipcRenderer.invoke('model:remove-api-key', provider),
setSelectedModel: (data) => ipcRenderer.invoke('model:set-selected-model', data),
downloadWhisperModel: (modelId) => ipcRenderer.invoke('whisper:download-model', modelId),
openLoginPage: () => ipcRenderer.invoke('open-login-page'),
toggleContentProtection: () => ipcRenderer.invoke('toggle-content-protection'),
openShortcutEditor: () => ipcRenderer.invoke('open-shortcut-editor'),
// Platform information for renderer processes
platform: {
isLinux: process.platform === 'linux',
isMacOS: process.platform === 'darwin',
isWindows: process.platform === 'win32',
platform: process.platform
},
// Common utilities used across multiple components
common: {
// User & Auth
getCurrentUser: () => ipcRenderer.invoke('get-current-user'),
startFirebaseAuth: () => ipcRenderer.invoke('start-firebase-auth'),
firebaseLogout: () => ipcRenderer.invoke('firebase-logout'),
// App Control
quitApplication: () => ipcRenderer.invoke('quit-application'),
firebaseLogout: () => ipcRenderer.invoke('firebase-logout'),
ollamaShutdown: (graceful) => ipcRenderer.invoke('ollama:shutdown', graceful),
startFirebaseAuth: () => ipcRenderer.invoke('start-firebase-auth'),
// on methods (listeners)
// User state listener (used by multiple components)
onUserStateChanged: (callback) => ipcRenderer.on('user-state-changed', callback),
removeOnUserStateChanged: (callback) => ipcRenderer.removeListener('user-state-changed', callback),
onSettingsUpdated: (callback) => ipcRenderer.on('settings-updated', callback),
removeOnSettingsUpdated: (callback) => ipcRenderer.removeListener('settings-updated', callback),
onPresetsUpdated: (callback) => ipcRenderer.on('presets-updated', callback),
removeOnPresetsUpdated: (callback) => ipcRenderer.removeListener('presets-updated', callback),
onShortcutsUpdated: (callback) => ipcRenderer.on('shortcuts-updated', callback),
removeOnShortcutsUpdated: (callback) => ipcRenderer.removeListener('shortcuts-updated', callback),
onWhisperDownloadProgress: (callback) => ipcRenderer.on('whisper:download-progress', callback),
removeOnWhisperDownloadProgress: (callback) => ipcRenderer.removeListener('whisper:download-progress', callback),
},
// send methods
cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'),
hideSettingsWindow: () => ipcRenderer.send('hide-settings-window')
// UI Component specific namespaces
// src/ui/app/ApiKeyHeader.js
apiKeyHeader: {
// Model & Provider Management
getProviderConfig: () => ipcRenderer.invoke('model:get-provider-config'),
getOllamaStatus: () => ipcRenderer.invoke('ollama:get-status'),
getModelSuggestions: () => ipcRenderer.invoke('ollama:get-model-suggestions'),
ensureOllamaReady: () => ipcRenderer.invoke('ollama:ensure-ready'),
installOllama: () => ipcRenderer.invoke('ollama:install'),
startOllamaService: () => ipcRenderer.invoke('ollama:start-service'),
pullOllamaModel: (modelName) => ipcRenderer.invoke('ollama:pull-model', modelName),
downloadWhisperModel: (modelId) => ipcRenderer.invoke('whisper:download-model', modelId),
validateKey: (data) => ipcRenderer.invoke('model:validate-key', data),
setSelectedModel: (data) => ipcRenderer.invoke('model:set-selected-model', data),
areProvidersConfigured: () => ipcRenderer.invoke('model:are-providers-configured'),
// Window Management
getHeaderPosition: () => ipcRenderer.invoke('get-header-position'),
moveHeaderTo: (x, y) => ipcRenderer.invoke('move-header-to', x, y),
// Listeners
onOllamaInstallProgress: (callback) => ipcRenderer.on('ollama:install-progress', callback),
removeOnOllamaInstallProgress: (callback) => ipcRenderer.removeListener('ollama:install-progress', callback),
onceOllamaInstallComplete: (callback) => ipcRenderer.once('ollama:install-complete', callback),
removeOnceOllamaInstallComplete: (callback) => ipcRenderer.removeListener('ollama:install-complete', callback),
onOllamaPullProgress: (callback) => ipcRenderer.on('ollama:pull-progress', callback),
removeOnOllamaPullProgress: (callback) => ipcRenderer.removeListener('ollama:pull-progress', callback),
onWhisperDownloadProgress: (callback) => ipcRenderer.on('whisper:download-progress', callback),
removeOnWhisperDownloadProgress: (callback) => ipcRenderer.removeListener('whisper:download-progress', callback),
// Remove all listeners (for cleanup)
removeAllListeners: () => {
ipcRenderer.removeAllListeners('whisper:download-progress');
ipcRenderer.removeAllListeners('ollama:install-progress');
ipcRenderer.removeAllListeners('ollama:pull-progress');
ipcRenderer.removeAllListeners('ollama:install-complete');
}
},
// 기존 window 유지
window: {
// 기존
hide: () => ipcRenderer.send('window:hide'),
onFocusChange: (callback) => ipcRenderer.on('window:focus-change', (e, f) => callback(f)),
// 추가
// src/ui/app/HeaderController.js
headerController: {
// State Management
sendHeaderStateChanged: (state) => ipcRenderer.send('header-state-changed', state),
// Window Management
resizeHeaderWindow: (dimensions) => ipcRenderer.invoke('resize-header-window', dimensions),
// Permissions
checkSystemPermissions: () => ipcRenderer.invoke('check-system-permissions'),
checkPermissionsCompleted: () => ipcRenderer.invoke('check-permissions-completed'),
// Listeners
onUserStateChanged: (callback) => ipcRenderer.on('user-state-changed', callback),
removeOnUserStateChanged: (callback) => ipcRenderer.removeListener('user-state-changed', callback),
onAuthFailed: (callback) => ipcRenderer.on('auth-failed', callback),
removeOnAuthFailed: (callback) => ipcRenderer.removeListener('auth-failed', callback),
onForceShowApiKeyHeader: (callback) => ipcRenderer.on('force-show-apikey-header', callback),
removeOnForceShowApiKeyHeader: (callback) => ipcRenderer.removeListener('force-show-apikey-header', callback)
},
// src/ui/app/MainHeader.js
mainHeader: {
// Window Management
getHeaderPosition: () => ipcRenderer.invoke('get-header-position'),
moveHeaderTo: (x, y) => ipcRenderer.invoke('move-header-to', x, y),
sendHeaderAnimationFinished: (state) => ipcRenderer.send('header-animation-finished', state),
// Settings Window Management
cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'),
showSettingsWindow: (bounds) => ipcRenderer.send('show-settings-window', bounds),
hideSettingsWindow: () => ipcRenderer.send('hide-settings-window'),
cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'),
moveWindowStep: (direction) => ipcRenderer.invoke('move-window-step', direction),
// Generic invoke (for dynamic channel names)
invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args),
// Listeners
onSessionStateText: (callback) => ipcRenderer.on('session-state-text', callback),
removeOnSessionStateText: (callback) => ipcRenderer.removeListener('session-state-text', callback),
onShortcutsUpdated: (callback) => ipcRenderer.on('shortcuts-updated', callback),
removeOnShortcutsUpdated: (callback) => ipcRenderer.removeListener('shortcuts-updated', callback)
},
// src/ui/app/PermissionHeader.js
permissionHeader: {
// Permission Management
checkSystemPermissions: () => ipcRenderer.invoke('check-system-permissions'),
requestMicrophonePermission: () => ipcRenderer.invoke('request-microphone-permission'),
openSystemPreferences: (preference) => ipcRenderer.invoke('open-system-preferences', preference),
markPermissionsCompleted: () => ipcRenderer.invoke('mark-permissions-completed')
},
// src/ui/app/PickleGlassApp.js
pickleGlassApp: {
// Listeners
onClickThroughToggled: (callback) => ipcRenderer.on('click-through-toggled', callback),
removeOnClickThroughToggled: (callback) => ipcRenderer.removeListener('click-through-toggled', callback),
removeAllClickThroughListeners: () => ipcRenderer.removeAllListeners('click-through-toggled')
},
// src/ui/ask/AskView.js
askView: {
// Window Management
closeAskWindow: () => ipcRenderer.invoke('ask:closeAskWindow'),
adjustWindowHeight: (height) => ipcRenderer.invoke('adjust-window-height', height),
// Message Handling
sendMessage: (text) => ipcRenderer.invoke('ask:sendMessage', text),
// Listeners
onSendQuestionToRenderer: (callback) => ipcRenderer.on('ask:sendQuestionToRenderer', callback),
removeOnSendQuestionToRenderer: (callback) => ipcRenderer.removeListener('ask:sendQuestionToRenderer', callback),
onHideTextInput: (callback) => ipcRenderer.on('hide-text-input', callback),
removeOnHideTextInput: (callback) => ipcRenderer.removeListener('hide-text-input', callback),
onShowTextInput: (callback) => ipcRenderer.on('ask:showTextInput', callback),
removeOnShowTextInput: (callback) => ipcRenderer.removeListener('ask:showTextInput', callback),
onResponseChunk: (callback) => ipcRenderer.on('ask-response-chunk', callback),
removeOnResponseChunk: (callback) => ipcRenderer.removeListener('ask-response-chunk', callback),
onResponseStreamEnd: (callback) => ipcRenderer.on('ask-response-stream-end', callback),
removeOnResponseStreamEnd: (callback) => ipcRenderer.removeListener('ask-response-stream-end', callback),
onScrollResponseUp: (callback) => ipcRenderer.on('scroll-response-up', callback),
removeOnScrollResponseUp: (callback) => ipcRenderer.removeListener('scroll-response-up', callback),
onScrollResponseDown: (callback) => ipcRenderer.on('scroll-response-down', callback),
removeOnScrollResponseDown: (callback) => ipcRenderer.removeListener('scroll-response-down', callback)
},
// src/ui/listen/ListenView.js
listenView: {
// Window Management
adjustWindowHeight: (height) => ipcRenderer.invoke('adjust-window-height', height),
// Listeners
onSessionStateChanged: (callback) => ipcRenderer.on('session-state-changed', callback),
removeOnSessionStateChanged: (callback) => ipcRenderer.removeListener('session-state-changed', callback)
},
// src/ui/listen/stt/SttView.js
sttView: {
// Listeners
onSttUpdate: (callback) => ipcRenderer.on('stt-update', callback),
removeOnSttUpdate: (callback) => ipcRenderer.removeListener('stt-update', callback)
},
// src/ui/listen/summary/SummaryView.js
summaryView: {
// Message Handling
sendQuestionToMain: (text) => ipcRenderer.invoke('ask:sendQuestionToMain', text),
// Listeners
onSummaryUpdate: (callback) => ipcRenderer.on('summary-update', callback),
removeOnSummaryUpdate: (callback) => ipcRenderer.removeListener('summary-update', callback),
removeAllSummaryUpdateListeners: () => ipcRenderer.removeAllListeners('summary-update')
},
// src/ui/settings/SettingsView.js
settingsView: {
// User & Auth
getCurrentUser: () => ipcRenderer.invoke('get-current-user'),
openLoginPage: () => ipcRenderer.invoke('open-login-page'),
firebaseLogout: () => ipcRenderer.invoke('firebase-logout'),
ollamaShutdown: (graceful) => ipcRenderer.invoke('ollama:shutdown', graceful),
startFirebaseAuth: () => ipcRenderer.invoke('start-firebase-auth'),
// on methods (listeners)
// Model & Provider Management
getModelSettings: () => ipcRenderer.invoke('settings:get-model-settings'), // Facade call
getProviderConfig: () => ipcRenderer.invoke('model:get-provider-config'),
getAllKeys: () => ipcRenderer.invoke('model:get-all-keys'),
getAvailableModels: (type) => ipcRenderer.invoke('model:get-available-models', type),
getSelectedModels: () => ipcRenderer.invoke('model:get-selected-models'),
validateKey: (data) => ipcRenderer.invoke('model:validate-key', data),
saveApiKey: (key) => ipcRenderer.invoke('model:save-api-key', key),
removeApiKey: (provider) => ipcRenderer.invoke('model:remove-api-key', provider),
setSelectedModel: (data) => ipcRenderer.invoke('model:set-selected-model', data),
// Ollama Management
getOllamaStatus: () => ipcRenderer.invoke('ollama:get-status'),
ensureOllamaReady: () => ipcRenderer.invoke('ollama:ensure-ready'),
shutdownOllama: (graceful) => ipcRenderer.invoke('ollama:shutdown', graceful),
// Whisper Management
getWhisperInstalledModels: () => ipcRenderer.invoke('whisper:get-installed-models'),
downloadWhisperModel: (modelId) => ipcRenderer.invoke('whisper:download-model', modelId),
// Settings Management
getPresets: () => ipcRenderer.invoke('settings:getPresets'),
getAutoUpdate: () => ipcRenderer.invoke('settings:get-auto-update'),
setAutoUpdate: (isEnabled) => ipcRenderer.invoke('settings:set-auto-update', isEnabled),
getContentProtectionStatus: () => ipcRenderer.invoke('get-content-protection-status'),
toggleContentProtection: () => ipcRenderer.invoke('toggle-content-protection'),
getCurrentShortcuts: () => ipcRenderer.invoke('get-current-shortcuts'),
openShortcutEditor: () => ipcRenderer.invoke('open-shortcut-editor'),
// Window Management
moveWindowStep: (direction) => ipcRenderer.invoke('move-window-step', direction),
cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'),
hideSettingsWindow: () => ipcRenderer.send('hide-settings-window'),
// App Control
quitApplication: () => ipcRenderer.invoke('quit-application'),
// Progress Tracking
pullOllamaModel: (modelName) => ipcRenderer.invoke('ollama:pull-model', modelName),
// Listeners
onUserStateChanged: (callback) => ipcRenderer.on('user-state-changed', callback),
removeOnUserStateChanged: (callback) => ipcRenderer.removeListener('user-state-changed', callback),
onSettingsUpdated: (callback) => ipcRenderer.on('settings-updated', callback),
@ -78,9 +237,66 @@ contextBridge.exposeInMainWorld('api', {
removeOnShortcutsUpdated: (callback) => ipcRenderer.removeListener('shortcuts-updated', callback),
onWhisperDownloadProgress: (callback) => ipcRenderer.on('whisper:download-progress', callback),
removeOnWhisperDownloadProgress: (callback) => ipcRenderer.removeListener('whisper:download-progress', callback),
onOllamaPullProgress: (callback) => ipcRenderer.on('ollama:pull-progress', callback),
removeOnOllamaPullProgress: (callback) => ipcRenderer.removeListener('ollama:pull-progress', callback)
},
// send methods
cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'),
hideSettingsWindow: () => ipcRenderer.send('hide-settings-window')
// src/ui/settings/ShortCutSettingsView.js
shortcutSettingsView: {
// Shortcut Management
saveShortcuts: (shortcuts) => ipcRenderer.invoke('save-shortcuts', shortcuts),
getDefaultShortcuts: () => ipcRenderer.invoke('get-default-shortcuts'),
closeShortcutEditor: () => ipcRenderer.send('close-shortcut-editor'),
// Listeners
onLoadShortcuts: (callback) => ipcRenderer.on('load-shortcuts', callback),
removeOnLoadShortcuts: (callback) => ipcRenderer.removeListener('load-shortcuts', callback)
},
// src/ui/app/content.html inline scripts
content: {
// Animation Management
sendAnimationFinished: () => ipcRenderer.send('animation-finished'),
// Listeners
onWindowShowAnimation: (callback) => ipcRenderer.on('window-show-animation', callback),
removeOnWindowShowAnimation: (callback) => ipcRenderer.removeListener('window-show-animation', callback),
onWindowHideAnimation: (callback) => ipcRenderer.on('window-hide-animation', callback),
removeOnWindowHideAnimation: (callback) => ipcRenderer.removeListener('window-hide-animation', callback),
onSettingsWindowHideAnimation: (callback) => ipcRenderer.on('settings-window-hide-animation', callback),
removeOnSettingsWindowHideAnimation: (callback) => ipcRenderer.removeListener('settings-window-hide-animation', callback),
onListenWindowMoveToCenter: (callback) => ipcRenderer.on('listen-window-move-to-center', callback),
removeOnListenWindowMoveToCenter: (callback) => ipcRenderer.removeListener('listen-window-move-to-center', callback),
onListenWindowMoveToLeft: (callback) => ipcRenderer.on('listen-window-move-to-left', callback),
removeOnListenWindowMoveToLeft: (callback) => ipcRenderer.removeListener('listen-window-move-to-left', callback)
},
// src/ui/listen/audioCore/listenCapture.js
listenCapture: {
// Audio Management
sendAudioContent: (data) => ipcRenderer.invoke('send-audio-content', data),
sendSystemAudioContent: (data) => ipcRenderer.invoke('send-system-audio-content', data),
startMacosAudio: () => ipcRenderer.invoke('start-macos-audio'),
stopMacosAudio: () => ipcRenderer.invoke('stop-macos-audio'),
// Screen Capture
captureScreenshot: (options) => ipcRenderer.invoke('capture-screenshot', options),
getCurrentScreenshot: () => ipcRenderer.invoke('get-current-screenshot'),
startScreenCapture: () => ipcRenderer.invoke('start-screen-capture'),
stopScreenCapture: () => ipcRenderer.invoke('stop-screen-capture'),
// Session Management
isSessionActive: () => ipcRenderer.invoke('is-session-active'),
// Listeners
onSystemAudioData: (callback) => ipcRenderer.on('system-audio-data', callback),
removeOnSystemAudioData: (callback) => ipcRenderer.removeListener('system-audio-data', callback)
},
// src/ui/listen/audioCore/renderer.js
renderer: {
// Listeners
onChangeListenCaptureState: (callback) => ipcRenderer.on('change-listen-capture-state', callback),
removeOnChangeListenCaptureState: (callback) => ipcRenderer.removeListener('change-listen-capture-state', callback)
}
});

View File

@ -370,13 +370,12 @@ export class ApiKeyHeader extends LitElement {
}
async loadProviderConfig() {
if (!window.require) return;
const { ipcRenderer } = window.require('electron');
if (!window.api) return;
try {
const [config, ollamaStatus] = await Promise.all([
ipcRenderer.invoke('model:get-provider-config'),
ipcRenderer.invoke('ollama:get-status')
window.api.apiKeyHeader.getProviderConfig(),
window.api.apiKeyHeader.getOllamaStatus()
]);
const llmProviders = [];
@ -428,8 +427,7 @@ export class ApiKeyHeader extends LitElement {
e.preventDefault()
const { ipcRenderer } = window.require("electron")
const initialPosition = await ipcRenderer.invoke("get-header-position")
const initialPosition = await window.api.apiKeyHeader.getHeaderPosition()
this.dragState = {
initialMouseX: e.screenX,
@ -456,8 +454,7 @@ export class ApiKeyHeader extends LitElement {
const newWindowX = this.dragState.initialWindowX + (e.screenX - this.dragState.initialMouseX)
const newWindowY = this.dragState.initialWindowY + (e.screenY - this.dragState.initialMouseY)
const { ipcRenderer } = window.require("electron")
ipcRenderer.invoke("move-header-to", newWindowX, newWindowY)
window.api.apiKeyHeader.moveHeaderTo(newWindowX, newWindowY)
}
handleMouseUp(e) {
@ -652,9 +649,8 @@ export class ApiKeyHeader extends LitElement {
try {
// Lightweight health check - just ping the service
const isHealthy = await this._executeOperation('health_check', async () => {
if (!window.require) return false;
const { ipcRenderer } = window.require('electron');
const result = await ipcRenderer.invoke('ollama:get-status');
if (!window.api) return false;
const result = await window.api.apiKeyHeader.getOllamaStatus();
return result?.success && result?.running;
}, { timeout: 5000, priority: 'low' });
@ -928,14 +924,13 @@ export class ApiKeyHeader extends LitElement {
}
async refreshOllamaStatus() {
if (!window.require) return;
if (!window.api) return;
try {
this._updateConnectionState('connecting', 'Checking Ollama status');
const result = await this._executeOperation('ollama_status', async () => {
const { ipcRenderer } = window.require('electron');
return await ipcRenderer.invoke('ollama:get-status');
return await window.api.apiKeyHeader.getOllamaStatus();
});
if (result?.success) {
@ -960,12 +955,11 @@ export class ApiKeyHeader extends LitElement {
}
async loadModelSuggestions() {
if (!window.require) return;
if (!window.api) return;
try {
const result = await this._executeOperation('model_suggestions', async () => {
const { ipcRenderer } = window.require('electron');
return await ipcRenderer.invoke('ollama:get-model-suggestions');
return await window.api.apiKeyHeader.getModelSuggestions();
});
if (result?.success) {
@ -988,14 +982,13 @@ export class ApiKeyHeader extends LitElement {
}
async ensureOllamaReady() {
if (!window.require) return false;
if (!window.api) return false;
try {
this._updateConnectionState('connecting', 'Ensuring Ollama is ready');
const result = await this._executeOperation('ollama_ensure_ready', async () => {
const { ipcRenderer } = window.require('electron');
return await ipcRenderer.invoke('ollama:ensure-ready');
return await window.api.apiKeyHeader.ensureOllamaReady();
}, { timeout: this.operationTimeout });
if (result?.success) {
@ -1015,8 +1008,7 @@ export class ApiKeyHeader extends LitElement {
}
async ensureOllamaReadyWithUI() {
if (!window.require) return false;
const { ipcRenderer } = window.require("electron");
if (!window.api) return false;
this.installingModel = "Setting up Ollama";
this.installProgress = 0;
@ -1074,21 +1066,21 @@ export class ApiKeyHeader extends LitElement {
operationCompleted = true;
clearTimeout(completionTimeout);
ipcRenderer.removeListener("ollama:install-progress", progressHandler);
window.api.apiKeyHeader.removeOnOllamaInstallProgress(progressHandler);
await this._handleOllamaSetupCompletion(result.success, result.error);
};
ipcRenderer.once("ollama:install-complete", completionHandler);
ipcRenderer.on("ollama:install-progress", progressHandler);
window.api.apiKeyHeader.onceOllamaInstallComplete(completionHandler);
window.api.apiKeyHeader.onOllamaInstallProgress(progressHandler);
try {
let result;
if (!this.ollamaStatus.installed) {
console.log("[ApiKeyHeader] Ollama not installed. Starting installation.");
result = await ipcRenderer.invoke("ollama:install");
result = await window.api.apiKeyHeader.installOllama();
} else {
console.log("[ApiKeyHeader] Ollama installed. Starting service.");
result = await ipcRenderer.invoke("ollama:start-service");
result = await window.api.apiKeyHeader.startOllamaService();
}
// If IPC call succeeds but no event received, handle completion manually
@ -1106,8 +1098,8 @@ export class ApiKeyHeader extends LitElement {
operationCompleted = true;
clearTimeout(completionTimeout);
console.error("[ApiKeyHeader] Ollama setup failed:", error);
ipcRenderer.removeListener("ollama:install-progress", progressHandler);
ipcRenderer.removeListener("ollama:install-complete", completionHandler);
window.api.apiKeyHeader.removeOnOllamaInstallProgress(progressHandler);
window.api.apiKeyHeader.removeOnceOllamaInstallComplete(completionHandler);
await this._handleOllamaSetupCompletion(false, error.message);
}
}
@ -1229,7 +1221,6 @@ export class ApiKeyHeader extends LitElement {
this.clearMessages();
this.requestUpdate();
const { ipcRenderer } = window.require('electron');
let progressHandler = null;
try {
@ -1249,10 +1240,10 @@ export class ApiKeyHeader extends LitElement {
};
// Set up progress tracking
ipcRenderer.on('ollama:pull-progress', progressHandler);
window.api.apiKeyHeader.onOllamaPullProgress(progressHandler);
// Execute the model pull with timeout
const installPromise = ipcRenderer.invoke('ollama:pull-model', modelName);
const installPromise = window.api.apiKeyHeader.pullOllamaModel(modelName);
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Installation timeout after 10 minutes')), 600000)
);
@ -1281,7 +1272,7 @@ export class ApiKeyHeader extends LitElement {
} finally {
// Comprehensive cleanup
if (progressHandler) {
ipcRenderer.removeListener('ollama:pull-progress', progressHandler);
window.api.apiKeyHeader.removeOnOllamaPullProgress(progressHandler);
}
this.installingModel = null;
@ -1307,7 +1298,6 @@ export class ApiKeyHeader extends LitElement {
this.clearMessages();
this.requestUpdate();
const { ipcRenderer } = window.require('electron');
let progressHandler = null;
try {
@ -1321,10 +1311,10 @@ export class ApiKeyHeader extends LitElement {
}
};
ipcRenderer.on('whisper:download-progress', progressHandler);
window.api.apiKeyHeader.onWhisperDownloadProgress(progressHandler);
// Start download with timeout protection
const downloadPromise = ipcRenderer.invoke('whisper:download-model', modelId);
const downloadPromise = window.api.apiKeyHeader.downloadWhisperModel(modelId);
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Download timeout after 10 minutes')), 600000)
);
@ -1351,7 +1341,7 @@ export class ApiKeyHeader extends LitElement {
} finally {
// Cleanup
if (progressHandler) {
ipcRenderer.removeListener('whisper:download-progress', progressHandler);
window.api.apiKeyHeader.removeOnWhisperDownloadProgress(progressHandler);
}
delete this.whisperInstallingModels[modelId];
this.requestUpdate();
@ -1411,8 +1401,6 @@ export class ApiKeyHeader extends LitElement {
this.isLoading = true;
this.clearMessages();
this.requestUpdate();
const { ipcRenderer } = window.require('electron');
try {
// Handle LLM provider
@ -1436,14 +1424,14 @@ export class ApiKeyHeader extends LitElement {
}
// Validate Ollama is working
llmResult = await ipcRenderer.invoke('model:validate-key', {
llmResult = await window.api.apiKeyHeader.validateKey({
provider: 'ollama',
key: 'local'
});
if (llmResult.success) {
// Set the selected model
await ipcRenderer.invoke('model:set-selected-model', {
await window.api.apiKeyHeader.setSelectedModel({
type: 'llm',
modelId: this.selectedLlmModel
});
@ -1454,7 +1442,7 @@ export class ApiKeyHeader extends LitElement {
throw new Error('Please enter LLM API key');
}
llmResult = await ipcRenderer.invoke('model:validate-key', {
llmResult = await window.api.apiKeyHeader.validateKey({
provider: this.llmProvider,
key: this.llmApiKey.trim()
});
@ -1467,14 +1455,14 @@ export class ApiKeyHeader extends LitElement {
sttResult = { success: true };
} else if (this.sttProvider === 'whisper') {
// For Whisper, just validate it's enabled (model download already handled in handleSttModelChange)
sttResult = await ipcRenderer.invoke('model:validate-key', {
sttResult = await window.api.apiKeyHeader.validateKey({
provider: 'whisper',
key: 'local'
});
if (sttResult.success && this.selectedSttModel) {
// Set the selected model
await ipcRenderer.invoke('model:set-selected-model', {
await window.api.apiKeyHeader.setSelectedModel({
type: 'stt',
modelId: this.selectedSttModel
});
@ -1485,7 +1473,7 @@ export class ApiKeyHeader extends LitElement {
throw new Error('Please enter STT API key');
}
sttResult = await ipcRenderer.invoke('model:validate-key', {
sttResult = await window.api.apiKeyHeader.validateKey({
provider: this.sttProvider,
key: this.sttApiKey.trim()
});
@ -1522,15 +1510,15 @@ export class ApiKeyHeader extends LitElement {
e.preventDefault()
console.log("Requesting Firebase authentication from main process...")
if (window.require) {
window.require("electron").ipcRenderer.invoke("start-firebase-auth")
if (window.api) {
window.api.common.startFirebaseAuth()
}
}
handleClose() {
console.log("Close button clicked")
if (window.require) {
window.require("electron").ipcRenderer.invoke("quit-application")
if (window.api) {
window.api.common.quitApplication()
}
}
@ -1543,8 +1531,8 @@ export class ApiKeyHeader extends LitElement {
console.log('[ApiKeyHeader] handleAnimationEnd: Transition completed, transitioning to next state...');
if (!window.require) {
console.error('[ApiKeyHeader] handleAnimationEnd: window.require not available');
if (!window.api) {
console.error('[ApiKeyHeader] handleAnimationEnd: window.api not available');
return;
}
@ -1553,14 +1541,12 @@ export class ApiKeyHeader extends LitElement {
return;
}
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('get-current-user')
window.api.common.getCurrentUser()
.then(userState => {
console.log('[ApiKeyHeader] handleAnimationEnd: User state retrieved:', userState);
// Additional validation for local providers
return ipcRenderer.invoke('model:are-providers-configured').then(isConfigured => {
return window.api.apiKeyHeader.areProvidersConfigured().then(isConfigured => {
console.log('[ApiKeyHeader] handleAnimationEnd: Providers configured check:', isConfigured);
if (!isConfigured) {
@ -1625,12 +1611,8 @@ export class ApiKeyHeader extends LitElement {
}
// Cleanup event listeners
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.removeAllListeners('whisper:download-progress');
ipcRenderer.removeAllListeners('ollama:install-progress');
ipcRenderer.removeAllListeners('ollama:pull-progress');
ipcRenderer.removeAllListeners('ollama:install-complete');
if (window.api) {
window.api.apiKeyHeader.removeAllListeners();
}
// Cancel any ongoing downloads

View File

@ -32,6 +32,7 @@ class HeaderTransitionManager {
this.apiKeyHeader = document.createElement('apikey-header');
this.apiKeyHeader.stateUpdateCallback = (userState) => this.handleStateUpdate(userState);
this.headerContainer.appendChild(this.apiKeyHeader);
console.log('[HeaderController] ensureHeader: Header of type:', type, 'created.');
} else if (type === 'permission') {
this.permissionHeader = document.createElement('permission-setup');
this.permissionHeader.continueCallback = () => this.transitionToMainHeader();
@ -50,41 +51,39 @@ class HeaderTransitionManager {
this._bootstrap();
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.on('user-state-changed', (event, userState) => {
if (window.api) {
window.api.headerController.onUserStateChanged((event, userState) => {
console.log('[HeaderController] Received user state change:', userState);
this.handleStateUpdate(userState);
});
ipcRenderer.on('auth-failed', (event, { message }) => {
window.api.headerController.onAuthFailed((event, { message }) => {
console.error('[HeaderController] Received auth failure from main process:', message);
if (this.apiKeyHeader) {
this.apiKeyHeader.errorMessage = 'Authentication failed. Please try again.';
this.apiKeyHeader.isLoading = false;
}
});
ipcRenderer.on('force-show-apikey-header', async () => {
window.api.headerController.onForceShowApiKeyHeader(async () => {
console.log('[HeaderController] Received broadcast to show apikey header. Switching now.');
await this._resizeForApiKey();
this.ensureHeader('apikey');
});
});
}
}
notifyHeaderState(stateOverride) {
const state = stateOverride || this.currentHeaderType || 'apikey';
if (window.require) {
window.require('electron').ipcRenderer.send('header-state-changed', state);
if (window.api) {
window.api.headerController.sendHeaderStateChanged(state);
}
}
async _bootstrap() {
// The initial state will be sent by the main process via 'user-state-changed'
// We just need to request it.
if (window.require) {
const userState = await window.require('electron').ipcRenderer.invoke('get-current-user');
if (window.api) {
const userState = await window.api.common.getCurrentUser();
console.log('[HeaderController] Bootstrapping with initial user state:', userState);
this.handleStateUpdate(userState);
} else {
@ -96,12 +95,7 @@ class HeaderTransitionManager {
//////// after_modelStateService ////////
async handleStateUpdate(userState) {
console.log('[HeaderController DEBUG] handleStateUpdate called with userState:', userState);
const { ipcRenderer } = window.require('electron');
console.log('[HeaderController DEBUG] Invoking "model:are-providers-configured"...');
const isConfigured = await ipcRenderer.invoke('model:are-providers-configured');
console.log('[HeaderController DEBUG] "model:are-providers-configured" returned:', isConfigured);
const isConfigured = await window.api.apiKeyHeader.areProvidersConfigured();
if (isConfigured) {
const { isLoggedIn } = userState;
@ -130,10 +124,9 @@ class HeaderTransitionManager {
}
// Check if permissions were previously completed
if (window.require) {
const { ipcRenderer } = window.require('electron');
if (window.api) {
try {
const permissionsCompleted = await ipcRenderer.invoke('check-permissions-completed');
const permissionsCompleted = await window.api.headerController.checkPermissionsCompleted();
if (permissionsCompleted) {
console.log('[HeaderController] Permissions were previously completed, checking current status...');
@ -165,39 +158,33 @@ class HeaderTransitionManager {
this.ensureHeader('main');
}
_resizeForMain() {
if (!window.require) return;
return window
.require('electron')
.ipcRenderer.invoke('resize-header-window', { width: 353, height: 47 })
async _resizeForMain() {
if (!window.api) return;
console.log('[HeaderController] _resizeForMain: Resizing window to 353x47');
return window.api.headerController.resizeHeaderWindow({ width: 353, height: 47 })
.catch(() => {});
}
async _resizeForApiKey() {
if (!window.require) return;
return window
.require('electron')
.ipcRenderer.invoke('resize-header-window', { width: 350, height: 300 })
if (!window.api) return;
console.log('[HeaderController] _resizeForApiKey: Resizing window to 350x300');
return window.api.headerController.resizeHeaderWindow({ width: 350, height: 300 })
.catch(() => {});
}
async _resizeForPermissionHeader() {
if (!window.require) return;
return window
.require('electron')
.ipcRenderer.invoke('resize-header-window', { width: 285, height: 220 })
if (!window.api) return;
return window.api.headerController.resizeHeaderWindow({ width: 285, height: 220 })
.catch(() => {});
}
async checkPermissions() {
if (!window.require) {
if (!window.api) {
return { success: true };
}
const { ipcRenderer } = window.require('electron');
try {
const permissions = await ipcRenderer.invoke('check-system-permissions');
const permissions = await window.api.headerController.checkSystemPermissions();
console.log('[HeaderController] Current permissions:', permissions);
if (!permissions.needsSetup) {

View File

@ -362,8 +362,7 @@ export class MainHeader extends LitElement {
async handleMouseDown(e) {
e.preventDefault();
const { ipcRenderer } = window.require('electron');
const initialPosition = await ipcRenderer.invoke('get-header-position');
const initialPosition = await window.api.mainHeader.getHeaderPosition();
this.dragState = {
initialMouseX: e.screenX,
@ -390,8 +389,7 @@ export class MainHeader extends LitElement {
const newWindowX = this.dragState.initialWindowX + (e.screenX - this.dragState.initialMouseX);
const newWindowY = this.dragState.initialWindowY + (e.screenY - this.dragState.initialMouseY);
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('move-header-to', newWindowX, newWindowY);
window.api.mainHeader.moveHeaderTo(newWindowX, newWindowY);
}
handleMouseUp(e) {
@ -447,12 +445,12 @@ export class MainHeader extends LitElement {
if (this.classList.contains('hiding')) {
this.classList.add('hidden');
if (window.require) {
window.require('electron').ipcRenderer.send('header-animation-finished', 'hidden');
if (window.api) {
window.api.mainHeader.sendHeaderAnimationFinished('hidden');
}
} else if (this.classList.contains('showing')) {
if (window.require) {
window.require('electron').ipcRenderer.send('header-animation-finished', 'visible');
if (window.api) {
window.api.mainHeader.sendHeaderAnimationFinished('visible');
}
}
}
@ -466,26 +464,24 @@ export class MainHeader extends LitElement {
super.connectedCallback();
this.addEventListener('animationend', this.handleAnimationEnd);
if (window.require) {
const { ipcRenderer } = window.require('electron');
if (window.api) {
this._sessionStateTextListener = (event, text) => {
this.actionText = text;
this.isTogglingSession = false;
};
ipcRenderer.on('session-state-text', this._sessionStateTextListener);
window.api.mainHeader.onSessionStateText(this._sessionStateTextListener);
// this._sessionStateListener = (event, { isActive }) => {
// this.isSessionActive = isActive;
// this.isTogglingSession = false;
// };
// ipcRenderer.on('session-state-changed', this._sessionStateListener);
// window.api.mainHeader.onSessionStateChanged(this._sessionStateListener);
this._shortcutListener = (event, keybinds) => {
console.log('[MainHeader] Received updated shortcuts:', keybinds);
this.shortcuts = keybinds;
};
ipcRenderer.on('shortcuts-updated', this._shortcutListener);
window.api.mainHeader.onShortcutsUpdated(this._shortcutListener);
}
}
@ -498,39 +494,37 @@ export class MainHeader extends LitElement {
this.animationEndTimer = null;
}
if (window.require) {
const { ipcRenderer } = window.require('electron');
if (window.api) {
if (this._sessionStateTextListener) {
ipcRenderer.removeListener('session-state-text', this._sessionStateTextListener);
window.api.mainHeader.removeOnSessionStateText(this._sessionStateTextListener);
}
// if (this._sessionStateListener) {
// ipcRenderer.removeListener('session-state-changed', this._sessionStateListener);
// window.api.mainHeader.removeOnSessionStateChanged(this._sessionStateListener);
// }
if (this._shortcutListener) {
ipcRenderer.removeListener('shortcuts-updated', this._shortcutListener);
window.api.mainHeader.removeOnShortcutsUpdated(this._shortcutListener);
}
}
}
invoke(channel, ...args) {
if (this.wasJustDragged) return;
if (window.require) {
window.require('electron').ipcRenderer.invoke(channel, ...args);
if (window.api) {
window.api.mainHeader.invoke(channel, ...args);
}
// return Promise.resolve();
}
showSettingsWindow(element) {
if (this.wasJustDragged) return;
if (window.require) {
const { ipcRenderer } = window.require('electron');
if (window.api) {
console.log(`[MainHeader] showSettingsWindow called at ${Date.now()}`);
ipcRenderer.send('cancel-hide-settings-window');
window.api.mainHeader.cancelHideSettingsWindow();
if (element) {
const { left, top, width, height } = element.getBoundingClientRect();
ipcRenderer.send('show-settings-window', {
window.api.mainHeader.showSettingsWindow({
x: left,
y: top,
width,
@ -542,9 +536,9 @@ export class MainHeader extends LitElement {
hideSettingsWindow() {
if (this.wasJustDragged) return;
if (window.require) {
if (window.api) {
console.log(`[MainHeader] hideSettingsWindow called at ${Date.now()}`);
window.require('electron').ipcRenderer.send('hide-settings-window');
window.api.mainHeader.hideSettingsWindow();
}
}
@ -561,7 +555,7 @@ export class MainHeader extends LitElement {
const args = ['listen'];
await this.invoke(channel, ...args);
} catch (error) {
console.error('IPC invoke for session toggle failed:', error);
console.error('session toggle failed:', error);
this.isTogglingSession = false;
}
}

View File

@ -288,13 +288,12 @@ export class PermissionHeader extends LitElement {
}
async checkPermissions() {
if (!window.require || this.isChecking) return;
if (!window.api || this.isChecking) return;
this.isChecking = true;
const { ipcRenderer } = window.require('electron');
try {
const permissions = await ipcRenderer.invoke('check-system-permissions');
const permissions = await window.api.permissionHeader.checkSystemPermissions();
console.log('[PermissionHeader] Permission check result:', permissions);
const prevMic = this.microphoneGranted;
@ -324,13 +323,12 @@ export class PermissionHeader extends LitElement {
}
async handleMicrophoneClick() {
if (!window.require || this.microphoneGranted === 'granted') return;
if (!window.api || this.microphoneGranted === 'granted') return;
console.log('[PermissionHeader] Requesting microphone permission...');
const { ipcRenderer } = window.require('electron');
try {
const result = await ipcRenderer.invoke('check-system-permissions');
const result = await window.api.permissionHeader.checkSystemPermissions();
console.log('[PermissionHeader] Microphone permission result:', result);
if (result.microphone === 'granted') {
@ -340,7 +338,7 @@ export class PermissionHeader extends LitElement {
}
if (result.microphone === 'not-determined' || result.microphone === 'denied' || result.microphone === 'unknown' || result.microphone === 'restricted') {
const res = await ipcRenderer.invoke('request-microphone-permission');
const res = await window.api.permissionHeader.requestMicrophonePermission();
if (res.status === 'granted' || res.success === true) {
this.microphoneGranted = 'granted';
this.requestUpdate();
@ -357,13 +355,12 @@ export class PermissionHeader extends LitElement {
}
async handleScreenClick() {
if (!window.require || this.screenGranted === 'granted') return;
if (!window.api || this.screenGranted === 'granted') return;
console.log('[PermissionHeader] Checking screen recording permission...');
const { ipcRenderer } = window.require('electron');
try {
const permissions = await ipcRenderer.invoke('check-system-permissions');
const permissions = await window.api.permissionHeader.checkSystemPermissions();
console.log('[PermissionHeader] Screen permission check result:', permissions);
if (permissions.screen === 'granted') {
@ -373,7 +370,7 @@ export class PermissionHeader extends LitElement {
}
if (permissions.screen === 'not-determined' || permissions.screen === 'denied' || permissions.screen === 'unknown' || permissions.screen === 'restricted') {
console.log('[PermissionHeader] Opening screen recording preferences...');
await ipcRenderer.invoke('open-system-preferences', 'screen-recording');
await window.api.permissionHeader.openSystemPreferences('screen-recording');
}
// Check permissions again after a delay
@ -389,10 +386,9 @@ export class PermissionHeader extends LitElement {
this.microphoneGranted === 'granted' &&
this.screenGranted === 'granted') {
// Mark permissions as completed
if (window.require) {
const { ipcRenderer } = window.require('electron');
if (window.api) {
try {
await ipcRenderer.invoke('mark-permissions-completed');
await window.api.permissionHeader.markPermissionsCompleted();
console.log('[PermissionHeader] Marked permissions as completed');
} catch (error) {
console.error('[PermissionHeader] Error marking permissions as completed:', error);
@ -405,8 +401,8 @@ export class PermissionHeader extends LitElement {
handleClose() {
console.log('Close button clicked');
if (window.require) {
window.require('electron').ipcRenderer.invoke('quit-application');
if (window.api) {
window.api.common.quitApplication();
}
}

View File

@ -74,10 +74,8 @@ export class PickleGlassApp extends LitElement {
connectedCallback() {
super.connectedCallback();
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.on('click-through-toggled', (_, isEnabled) => {
if (window.api) {
window.api.pickleGlassApp.onClickThroughToggled((_, isEnabled) => {
this._isClickThrough = isEnabled;
});
}
@ -85,9 +83,8 @@ export class PickleGlassApp extends LitElement {
disconnectedCallback() {
super.disconnectedCallback();
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.removeAllListeners('click-through-toggled');
if (window.api) {
window.api.pickleGlassApp.removeAllClickThroughListeners();
}
}
@ -121,9 +118,8 @@ export class PickleGlassApp extends LitElement {
}
async handleClose() {
if (window.require) {
const { ipcRenderer } = window.require('electron');
await ipcRenderer.invoke('quit-application');
if (window.api) {
await window.api.common.quitApplication();
}
}

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-security-policy" content="script-src 'self' 'unsafe-inline'" />
<meta http-equiv="content-security-policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval'" />
<title>Pickle Glass Content</title>
<style>
:root {
@ -238,15 +238,13 @@
window.addEventListener('DOMContentLoaded', () => {
const app = document.getElementById('pickle-glass');
if (window.require) {
const { ipcRenderer } = window.require('electron');
if (window.api) {
// --- REFACTORED: Event-driven animation handling ---
app.addEventListener('animationend', (event) => {
// 숨김 애니메이션이 끝나면 main 프로세스에 알려 창을 실제로 숨깁니다.
if (event.animationName === 'slideUpToHeader' || event.animationName === 'settingsCollapseToButton') {
console.log(`Animation finished: ${event.animationName}. Notifying main process.`);
ipcRenderer.send('animation-finished');
window.api.content.sendAnimationFinished();
// 완료 후 애니메이션 클래스 정리
app.classList.remove('window-sliding-up', 'settings-window-hide');
@ -257,26 +255,26 @@
}
});
ipcRenderer.on('window-show-animation', () => {
window.api.content.onWindowShowAnimation(() => {
console.log('Starting window show animation');
app.classList.remove('window-hidden', 'window-sliding-up', 'settings-window-hide');
app.classList.add('window-sliding-down');
});
ipcRenderer.on('window-hide-animation', () => {
window.api.content.onWindowHideAnimation(() => {
console.log('Starting window hide animation');
app.classList.remove('window-sliding-down', 'settings-window-show');
app.classList.add('window-sliding-up');
});
ipcRenderer.on('settings-window-hide-animation', () => {
window.api.content.onSettingsWindowHideAnimation(() => {
console.log('Starting settings window hide animation');
app.classList.remove('window-sliding-down', 'settings-window-show');
app.classList.add('settings-window-hide');
});
// --- UNCHANGED: Existing logic for listen window movement ---
ipcRenderer.on('listen-window-move-to-center', () => {
window.api.content.onListenWindowMoveToCenter(() => {
console.log('Moving listen window to center');
app.classList.add('listen-window-moving');
app.classList.remove('listen-window-left');
@ -287,7 +285,7 @@
}, 350);
});
ipcRenderer.on('listen-window-move-to-left', () => {
window.api.content.onListenWindowMoveToLeft(() => {
console.log('Moving listen window to left');
app.classList.add('listen-window-moving');
app.classList.remove('listen-window-center');

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-security-policy" content="script-src 'self' 'unsafe-inline'" />
<meta http-equiv="content-security-policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval'" />
<title>Pickle Glass Header</title>
<style>
html,

View File

@ -769,15 +769,14 @@ export class AskView extends LitElement {
this.handleSendText(null, question);
};
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.on('ask:sendQuestionToRenderer', this.handleQuestionFromAssistant);
ipcRenderer.on('hide-text-input', () => {
if (window.api) {
window.api.askView.onSendQuestionToRenderer(this.handleQuestionFromAssistant);
window.api.askView.onHideTextInput(() => {
console.log('📤 Hide text input signal received');
this.showTextInput = false;
this.requestUpdate();
});
ipcRenderer.on('ask:showTextInput', () => {
window.api.askView.onShowTextInput(() => {
console.log('📤 Show text input signal received');
if (!this.showTextInput) {
this.showTextInput = true;
@ -785,11 +784,11 @@ export class AskView extends LitElement {
}
});
ipcRenderer.on('ask-response-chunk', this.handleStreamChunk);
ipcRenderer.on('ask-response-stream-end', this.handleStreamEnd);
window.api.askView.onResponseChunk(this.handleStreamChunk);
window.api.askView.onResponseStreamEnd(this.handleStreamEnd);
ipcRenderer.on('scroll-response-up', () => this.handleScroll('up'));
ipcRenderer.on('scroll-response-down', () => this.handleScroll('down'));
window.api.askView.onScrollResponseUp(() => this.handleScroll('up'));
window.api.askView.onScrollResponseDown(() => this.handleScroll('down'));
console.log('✅ AskView: IPC 이벤트 리스너 등록 완료');
}
}
@ -816,17 +815,11 @@ export class AskView extends LitElement {
Object.values(this.lineCopyTimeouts).forEach(timeout => clearTimeout(timeout));
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.removeListener('hide-text-input', () => { });
ipcRenderer.removeListener('ask:showTextInput', () => { });
ipcRenderer.removeListener('ask-response-chunk', this.handleStreamChunk);
ipcRenderer.removeListener('ask-response-stream-end', this.handleStreamEnd);
ipcRenderer.removeListener('scroll-response-up', () => this.handleScroll('up'));
ipcRenderer.removeListener('scroll-response-down', () => this.handleScroll('down'));
console.log('✅ AskView: IPC 이벤트 리스너 제거 완료');
if (window.api) {
// Note: We need to keep references to the actual callbacks used in connectedCallback
// For now, we'll just log that removal is needed
// TODO: Store callback references for proper removal
console.log('✅ AskView: IPC 이벤트 리스너 제거 필요');
}
}
@ -889,7 +882,7 @@ export class AskView extends LitElement {
handleCloseAskWindow() {
this.clearResponseContent();
ipcRenderer.invoke('ask:closeAskWindow');
window.api.askView.closeAskWindow();
}
handleCloseIfNoContent() {
@ -1113,9 +1106,8 @@ export class AskView extends LitElement {
requestWindowResize(targetHeight) {
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('adjust-window-height', targetHeight);
if (window.api) {
window.api.askView.adjustWindowHeight(targetHeight);
}
}
@ -1279,9 +1271,8 @@ export class AskView extends LitElement {
this.requestUpdate();
this.renderContent();
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('ask:sendMessage', text).catch(error => {
if (window.api) {
window.api.askView.sendMessage(text).catch(error => {
console.error('Error sending text:', error);
this.isLoading = false;
this.isStreaming = false;
@ -1410,7 +1401,7 @@ export class AskView extends LitElement {
// Dynamically resize the BrowserWindow to fit current content
adjustWindowHeight() {
if (!window.require) return;
if (!window.api) return;
this.updateComplete.then(() => {
const headerEl = this.shadowRoot.querySelector('.response-header');
@ -1427,8 +1418,7 @@ export class AskView extends LitElement {
const targetHeight = Math.min(700, idealHeight);
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('adjust-window-height', targetHeight);
window.api.askView.adjustWindowHeight(targetHeight);
}).catch(err => console.error('AskView adjustWindowHeight error:', err));
}

View File

@ -453,9 +453,8 @@ export class ListenView extends LitElement {
if (this.isSessionActive) {
this.startTimer();
}
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.on('session-state-changed', (event, { isActive }) => {
if (window.api) {
window.api.listenView.onSessionStateChanged((event, { isActive }) => {
const wasActive = this.isSessionActive;
this.isSessionActive = isActive;
@ -514,7 +513,7 @@ export class ListenView extends LitElement {
}
adjustWindowHeight() {
if (!window.require) return;
if (!window.api) return;
this.updateComplete
.then(() => {
@ -537,8 +536,7 @@ export class ListenView extends LitElement {
`[Height Adjusted] Mode: ${this.viewMode}, TopBar: ${topBarHeight}px, Content: ${contentHeight}px, Ideal: ${idealHeight}px, Target: ${targetHeight}px`
);
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('adjust-window-height', targetHeight);
window.api.listenView.adjustWindowHeight(targetHeight);
})
.catch(error => {
console.error('Error in adjustWindowHeight:', error);

View File

@ -1,4 +1,3 @@
const { ipcRenderer } = require('electron');
const createAecModule = require('./aec.js');
let aecModPromise = null; // 한 번만 로드
@ -34,8 +33,8 @@ const SAMPLE_RATE = 24000;
const AUDIO_CHUNK_DURATION = 0.1;
const BUFFER_SIZE = 4096;
const isLinux = process.platform === 'linux';
const isMacOS = process.platform === 'darwin';
const isLinux = window.api.platform.isLinux;
const isMacOS = window.api.platform.isMacOS;
let mediaStream = null;
let micMediaStream = null;
@ -198,7 +197,7 @@ function runAecSync(micF32, sysF32) {
// System audio data handler
ipcRenderer.on('system-audio-data', (event, { data }) => {
window.api.listenCapture.onSystemAudioData((event, { data }) => {
systemAudioBuffer.push({
data: data,
timestamp: Date.now(),
@ -336,7 +335,7 @@ async function setupMicProcessing(micStream) {
const pcm16 = convertFloat32ToInt16(processedChunk);
const b64 = arrayBufferToBase64(pcm16.buffer);
ipcRenderer.invoke('send-audio-content', {
window.api.listenCapture.sendAudioContent({
data: b64,
mimeType: 'audio/pcm;rate=24000',
});
@ -369,7 +368,7 @@ function setupLinuxMicProcessing(micStream) {
const pcmData16 = convertFloat32ToInt16(chunk);
const base64Data = arrayBufferToBase64(pcmData16.buffer);
await ipcRenderer.invoke('send-audio-content', {
await window.api.listenCapture.sendAudioContent({
data: base64Data,
mimeType: 'audio/pcm;rate=24000',
});
@ -403,7 +402,7 @@ function setupSystemAudioProcessing(systemStream) {
const base64Data = arrayBufferToBase64(pcmData16.buffer);
try {
await ipcRenderer.invoke('send-system-audio-content', {
await window.api.listenCapture.sendSystemAudioContent({
data: base64Data,
mimeType: 'audio/pcm;rate=24000',
});
@ -427,13 +426,13 @@ async function captureScreenshot(imageQuality = 'medium', isManual = false) {
// Check rate limiting for automated screenshots only
if (!isManual && tokenTracker.shouldThrottle()) {
console.log('⚠️ Automated screenshot skipped due to rate limiting');
console.log('Automated screenshot skipped due to rate limiting');
return;
}
try {
// Request screenshot from main process
const result = await ipcRenderer.invoke('capture-screenshot', {
const result = await window.api.listenCapture.captureScreenshot({
quality: imageQuality,
});
@ -470,16 +469,16 @@ async function captureManualScreenshot(imageQuality = null) {
async function getCurrentScreenshot() {
try {
// First try to get a fresh screenshot from main process
const result = await ipcRenderer.invoke('get-current-screenshot');
const result = await window.api.listenCapture.getCurrentScreenshot();
if (result.success && result.base64) {
console.log('📸 Got fresh screenshot from main process');
console.log('Got fresh screenshot from main process');
return result.base64;
}
// If no screenshot available, capture one now
console.log('📸 No screenshot available, capturing new one');
const captureResult = await ipcRenderer.invoke('capture-screenshot', {
console.log('No screenshot available, capturing new one');
const captureResult = await window.api.listenCapture.captureScreenshot({
quality: currentImageQuality,
});
@ -490,7 +489,7 @@ async function getCurrentScreenshot() {
// Fallback to last stored screenshot
if (lastScreenshotBase64) {
console.log('📸 Using cached screenshot');
console.log('Using cached screenshot');
return lastScreenshotBase64;
}
@ -518,15 +517,15 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
console.log('Starting macOS capture with SystemAudioDump...');
// Start macOS audio capture
const audioResult = await ipcRenderer.invoke('start-macos-audio');
const audioResult = await window.api.listenCapture.startMacosAudio();
if (!audioResult.success) {
console.warn('[listenCapture] macOS audio start failed:', audioResult.error);
// 이미 실행 중 → stop 후 재시도
if (audioResult.error === 'already_running') {
await ipcRenderer.invoke('stop-macos-audio');
await window.api.listenCapture.stopMacosAudio();
await new Promise(r => setTimeout(r, 500));
const retry = await ipcRenderer.invoke('start-macos-audio');
const retry = await window.api.listenCapture.startMacosAudio();
if (!retry.success) {
throw new Error('Retry failed: ' + retry.error);
}
@ -536,7 +535,7 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
}
// Initialize screen capture in main process
const screenResult = await ipcRenderer.invoke('start-screen-capture');
const screenResult = await window.api.listenCapture.startScreenCapture();
if (!screenResult.success) {
throw new Error('Failed to start screen capture: ' + screenResult.error);
}
@ -604,13 +603,13 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
console.log('Starting Windows capture with native loopback audio...');
// Start screen capture in main process for screenshots
const screenResult = await ipcRenderer.invoke('start-screen-capture');
const screenResult = await window.api.listenCapture.startScreenCapture();
if (!screenResult.success) {
throw new Error('Failed to start screen capture: ' + screenResult.error);
}
// Ensure STT sessions are initialized before starting audio capture
const sessionActive = await ipcRenderer.invoke('is-session-active');
const sessionActive = await window.api.listenCapture.isSessionActive();
if (!sessionActive) {
throw new Error('STT sessions not initialized - please wait for initialization to complete');
}
@ -715,13 +714,13 @@ function stopCapture() {
}
// Stop screen capture in main process
ipcRenderer.invoke('stop-screen-capture').catch(err => {
window.api.listenCapture.stopScreenCapture().catch(err => {
console.error('Error stopping screen capture:', err);
});
// Stop macOS audio capture if running
if (isMacOS) {
ipcRenderer.invoke('stop-macos-audio').catch(err => {
window.api.listenCapture.stopMacosAudio().catch(err => {
console.error('Error stopping macOS audio:', err);
});
}

View File

@ -1,5 +1,4 @@
// renderer.js
const { ipcRenderer } = require('electron');
const listenCapture = require('./listenCapture.js');
const params = new URLSearchParams(window.location.search);
const isListenView = params.get('view') === 'listen';
@ -15,7 +14,7 @@ window.pickleGlass = {
};
ipcRenderer.on('change-listen-capture-state', (_event, { status }) => {
window.api.renderer.onChangeListenCaptureState((_event, { status }) => {
if (!isListenView) {
console.log('[Renderer] Non-listen view: ignoring capture-state change');
return;

View File

@ -95,17 +95,15 @@ export class SttView extends LitElement {
connectedCallback() {
super.connectedCallback();
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.on('stt-update', this.handleSttUpdate);
if (window.api) {
window.api.sttView.onSttUpdate(this.handleSttUpdate);
}
}
disconnectedCallback() {
super.disconnectedCallback();
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.removeListener('stt-update', this.handleSttUpdate);
if (window.api) {
window.api.sttView.removeOnSttUpdate(this.handleSttUpdate);
}
}

View File

@ -262,9 +262,8 @@ export class SummaryView extends LitElement {
connectedCallback() {
super.connectedCallback();
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.on('summary-update', (event, data) => {
if (window.api) {
window.api.summaryView.onSummaryUpdate((event, data) => {
this.structuredData = data;
this.requestUpdate();
});
@ -273,9 +272,8 @@ export class SummaryView extends LitElement {
disconnectedCallback() {
super.disconnectedCallback();
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.removeAllListeners('summary-update');
if (window.api) {
window.api.summaryView.removeAllSummaryUpdateListeners();
}
}
@ -408,11 +406,9 @@ export class SummaryView extends LitElement {
async handleRequestClick(requestText) {
console.log('🔥 Analysis request clicked:', requestText);
if (window.require) {
const { ipcRenderer } = window.require('electron');
if (window.api) {
try {
const result = await ipcRenderer.invoke('ask:sendQuestionToMain', requestText);
const result = await window.api.summaryView.sendQuestionToMain(requestText);
if (result.success) {
console.log('✅ Question sent to AskView successfully');

View File

@ -543,11 +543,10 @@ export class SettingsView extends LitElement {
}
async loadAutoUpdateSetting() {
if (!window.require) return;
const { ipcRenderer } = window.require('electron');
if (!window.api) return;
this.autoUpdateLoading = true;
try {
const enabled = await ipcRenderer.invoke('settings:get-auto-update');
const enabled = await window.api.settingsView.getAutoUpdate();
this.autoUpdateEnabled = enabled;
console.log('Auto-update setting loaded:', enabled);
} catch (e) {
@ -559,13 +558,12 @@ export class SettingsView extends LitElement {
}
async handleToggleAutoUpdate() {
if (!window.require || this.autoUpdateLoading) return;
const { ipcRenderer } = window.require('electron');
if (!window.api || this.autoUpdateLoading) return;
this.autoUpdateLoading = true;
this.requestUpdate();
try {
const newValue = !this.autoUpdateEnabled;
const result = await ipcRenderer.invoke('settings:set-auto-update', newValue);
const result = await window.api.settingsView.setAutoUpdate(newValue);
if (result && result.success) {
this.autoUpdateEnabled = newValue;
} else {
@ -580,18 +578,17 @@ export class SettingsView extends LitElement {
//////// after_modelStateService ////////
async loadInitialData() {
if (!window.require) return;
if (!window.api) return;
this.isLoading = true;
const { ipcRenderer } = window.require('electron');
try {
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')
window.api.settingsView.getCurrentUser(),
window.api.settingsView.getModelSettings(), // Facade call
window.api.settingsView.getPresets(),
window.api.settingsView.getContentProtectionStatus(),
window.api.settingsView.getCurrentShortcuts(),
window.api.settingsView.getOllamaStatus(),
window.api.settingsView.getWhisperInstalledModels()
]);
if (userState && userState.isLoggedIn) this.firebaseUser = userState;
@ -646,10 +643,9 @@ export class SettingsView extends LitElement {
// For Ollama, we need to ensure it's ready first
if (provider === 'ollama') {
this.saving = true;
const { ipcRenderer } = window.require('electron');
// First ensure Ollama is installed and running
const ensureResult = await ipcRenderer.invoke('settings:ensure-ollama-ready');
const ensureResult = await window.api.settingsView.ensureOllamaReady();
if (!ensureResult.success) {
alert(`Failed to setup Ollama: ${ensureResult.error}`);
this.saving = false;
@ -657,7 +653,7 @@ export class SettingsView extends LitElement {
}
// Now validate (which will check if service is running)
const result = await ipcRenderer.invoke('settings:validate-and-save-key', { provider, key: 'local' });
const result = await window.api.settingsView.validateKey({ provider, key: 'local' });
if (result.success) {
await this.refreshModelData();
@ -672,8 +668,7 @@ export class SettingsView extends LitElement {
// For Whisper, just enable it
if (provider === 'whisper') {
this.saving = true;
const { ipcRenderer } = window.require('electron');
const result = await ipcRenderer.invoke('settings:validate-and-save-key', { provider, key: 'local' });
const result = await window.api.settingsView.validateKey({ provider, key: 'local' });
if (result.success) {
await this.refreshModelData();
@ -686,8 +681,7 @@ export class SettingsView extends LitElement {
// For other providers, use the normal flow
this.saving = true;
const { ipcRenderer } = window.require('electron');
const result = await ipcRenderer.invoke('settings:validate-and-save-key', { provider, key });
const result = await window.api.settingsView.validateKey({ provider, key });
if (result.success) {
await this.refreshModelData();
@ -700,23 +694,24 @@ export class SettingsView extends LitElement {
async handleClearKey(provider) {
this.saving = true;
const { ipcRenderer } = window.require('electron');
await ipcRenderer.invoke('settings:clear-api-key', { provider });
await window.api.settingsView.removeApiKey(provider);
this.apiKeys = { ...this.apiKeys, [provider]: '' };
await this.refreshModelData();
this.saving = false;
}
async refreshModelData() {
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;
}
const [availableLlm, availableStt, selected, storedKeys] = await Promise.all([
window.api.settingsView.getAvailableModels({ type: 'llm' }),
window.api.settingsView.getAvailableModels({ type: 'stt' }),
window.api.settingsView.getSelectedModels(),
window.api.settingsView.getAllKeys()
]);
this.availableLlmModels = availableLlm;
this.availableSttModels = availableStt;
this.selectedLlm = selected.llm;
this.selectedStt = selected.stt;
this.apiKeys = storedKeys;
this.requestUpdate();
}
@ -761,8 +756,7 @@ export class SettingsView extends LitElement {
}
this.saving = true;
const { ipcRenderer } = window.require('electron');
await ipcRenderer.invoke('settings:set-selected-model', { type, modelId });
await window.api.settingsView.setSelectedModel({ type, modelId });
if (type === 'llm') this.selectedLlm = modelId;
if (type === 'stt') this.selectedStt = modelId;
this.isLlmListVisible = false;
@ -772,8 +766,7 @@ export class SettingsView extends LitElement {
}
async refreshOllamaStatus() {
const { ipcRenderer } = window.require('electron');
const ollamaStatus = await ipcRenderer.invoke('settings:get-ollama-status');
const ollamaStatus = await window.api.settingsView.getOllamaStatus();
if (ollamaStatus?.success) {
this.ollamaStatus = { installed: ollamaStatus.installed, running: ollamaStatus.running };
this.ollamaModels = ollamaStatus.models || [];
@ -817,8 +810,6 @@ export class SettingsView extends LitElement {
this.requestUpdate();
try {
const { ipcRenderer } = window.require('electron');
// Set up progress listener
const progressHandler = (event, { modelId: id, progress }) => {
if (id === modelId) {
@ -827,10 +818,10 @@ export class SettingsView extends LitElement {
}
};
ipcRenderer.on('whisper:download-progress', progressHandler);
window.api.settingsView.onWhisperDownloadProgress(progressHandler);
// Start download
const result = await ipcRenderer.invoke('whisper:download-model', modelId);
const result = await window.api.settingsView.downloadWhisperModel(modelId);
if (result.success) {
// Auto-select the model after download
@ -840,7 +831,7 @@ export class SettingsView extends LitElement {
}
// Cleanup
ipcRenderer.removeListener('whisper:download-progress', progressHandler);
window.api.settingsView.removeOnWhisperDownloadProgress(progressHandler);
} catch (error) {
console.error(`[SettingsView] Error downloading Whisper model ${modelId}:`, error);
alert(`Error downloading ${modelId}: ${error.message}`);
@ -872,17 +863,12 @@ export class SettingsView extends LitElement {
if (this.wasJustDragged) return
console.log("Requesting Firebase authentication from main process...")
if (window.require) {
window.require("electron").ipcRenderer.invoke("start-firebase-auth")
}
}
window.api.settingsView.startFirebaseAuth();
}
//////// after_modelStateService ////////
openShortcutEditor() {
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('open-shortcut-editor');
}
window.api.settingsView.openShortcutEditor();
}
connectedCallback() {
@ -920,9 +906,7 @@ export class SettingsView extends LitElement {
}
setupIpcListeners() {
if (!window.require) return;
const { ipcRenderer } = window.require('electron');
if (!window.api) return;
this._userStateListener = (event, userState) => {
console.log('[SettingsView] Received user-state-changed:', userState);
@ -945,7 +929,7 @@ export class SettingsView extends LitElement {
this._presetsUpdatedListener = async (event) => {
console.log('[SettingsView] Received presets-updated, refreshing presets');
try {
const presets = await ipcRenderer.invoke('settings:getPresets');
const presets = await window.api.settingsView.getPresets();
this.presets = presets || [];
// 현재 선택된 프리셋이 삭제되었는지 확인 (사용자 프리셋만 고려)
@ -964,28 +948,26 @@ export class SettingsView extends LitElement {
this.shortcuts = keybinds;
};
ipcRenderer.on('user-state-changed', this._userStateListener);
ipcRenderer.on('settings-updated', this._settingsUpdatedListener);
ipcRenderer.on('presets-updated', this._presetsUpdatedListener);
ipcRenderer.on('shortcuts-updated', this._shortcutListener);
window.api.settingsView.onUserStateChanged(this._userStateListener);
window.api.settingsView.onSettingsUpdated(this._settingsUpdatedListener);
window.api.settingsView.onPresetsUpdated(this._presetsUpdatedListener);
window.api.settingsView.onShortcutsUpdated(this._shortcutListener);
}
cleanupIpcListeners() {
if (!window.require) return;
const { ipcRenderer } = window.require('electron');
if (!window.api) return;
if (this._userStateListener) {
ipcRenderer.removeListener('user-state-changed', this._userStateListener);
window.api.settingsView.removeOnUserStateChanged(this._userStateListener);
}
if (this._settingsUpdatedListener) {
ipcRenderer.removeListener('settings-updated', this._settingsUpdatedListener);
window.api.settingsView.removeOnSettingsUpdated(this._settingsUpdatedListener);
}
if (this._presetsUpdatedListener) {
ipcRenderer.removeListener('presets-updated', this._presetsUpdatedListener);
window.api.settingsView.removeOnPresetsUpdated(this._presetsUpdatedListener);
}
if (this._shortcutListener) {
ipcRenderer.removeListener('shortcuts-updated', this._shortcutListener);
window.api.settingsView.removeOnShortcutsUpdated(this._shortcutListener);
}
}
@ -1019,17 +1001,11 @@ export class SettingsView extends LitElement {
}
handleMouseEnter = () => {
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.send('cancel-hide-settings-window');
}
window.api.settingsView.cancelHideSettingsWindow();
}
handleMouseLeave = () => {
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.send('hide-settings-window');
}
window.api.settingsView.hideSettingsWindow();
}
// getMainShortcuts() {
@ -1079,70 +1055,76 @@ export class SettingsView extends LitElement {
handleMoveLeft() {
console.log('Move Left clicked');
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('move-window-step', 'left');
}
window.api.settingsView.moveWindowStep('left');
}
handleMoveRight() {
console.log('Move Right clicked');
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('move-window-step', 'right');
}
window.api.settingsView.moveWindowStep('right');
}
async handlePersonalize() {
console.log('Personalize clicked');
if (window.require) {
const { ipcRenderer } = window.require('electron');
try {
await ipcRenderer.invoke('open-login-page');
} catch (error) {
console.error('Failed to open personalize page:', error);
}
try {
await window.api.settingsView.openLoginPage();
} catch (error) {
console.error('Failed to open personalize page:', error);
}
}
async handleToggleInvisibility() {
console.log('Toggle Invisibility clicked');
if (window.require) {
const { ipcRenderer } = window.require('electron');
this.isContentProtectionOn = await ipcRenderer.invoke('toggle-content-protection');
this.requestUpdate();
this.isContentProtectionOn = await window.api.settingsView.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.settingsView.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.settingsView.removeApiKey();
this.apiKey = null;
this.requestUpdate();
}
handleQuit() {
console.log('Quit clicked');
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('quit-application');
}
window.api.settingsView.quitApplication();
}
handleFirebaseLogout() {
console.log('Firebase Logout clicked');
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('firebase-logout');
}
window.api.settingsView.firebaseLogout();
}
async handleOllamaShutdown() {
console.log('[SettingsView] Shutting down Ollama service...');
if (!window.require) return;
const { ipcRenderer } = window.require('electron');
if (!window.api) return;
try {
// Show loading state
this.ollamaStatus = { ...this.ollamaStatus, running: false };
this.requestUpdate();
const result = await ipcRenderer.invoke('settings:shutdown-ollama'); // Graceful shutdown
const result = await window.api.settingsView.shutdownOllama(false); // Graceful shutdown
if (result.success) {
console.log('[SettingsView] Ollama shut down successfully');

View File

@ -102,23 +102,22 @@ export class ShortcutSettingsView extends LitElement {
this.feedback = {};
this.isLoading = true;
this.capturingKey = null;
this.ipcRenderer = window.require ? window.require('electron').ipcRenderer : null;
}
connectedCallback() {
super.connectedCallback();
if (!this.ipcRenderer) return;
if (!window.api) return;
this.loadShortcutsHandler = (event, keybinds) => {
this.shortcuts = keybinds;
this.isLoading = false;
};
this.ipcRenderer.on('load-shortcuts', this.loadShortcutsHandler);
window.api.shortcutSettingsView.onLoadShortcuts(this.loadShortcutsHandler);
}
disconnectedCallback() {
super.disconnectedCallback();
if (this.ipcRenderer && this.loadShortcutsHandler) {
this.ipcRenderer.removeListener('load-shortcuts', this.loadShortcutsHandler);
if (window.api && this.loadShortcutsHandler) {
window.api.shortcutSettingsView.removeOnLoadShortcuts(this.loadShortcutsHandler);
}
}
@ -171,25 +170,25 @@ export class ShortcutSettingsView extends LitElement {
}
async handleSave() {
if (!this.ipcRenderer) return;
const result = await this.ipcRenderer.invoke('save-shortcuts', this.shortcuts);
if (!window.api) return;
const result = await window.api.shortcutSettingsView.saveShortcuts(this.shortcuts);
if (!result.success) {
alert('Failed to save shortcuts: ' + result.error);
}
}
handleClose() {
if (!this.ipcRenderer) return;
this.ipcRenderer.send('close-shortcut-editor');
if (!window.api) return;
window.api.shortcutSettingsView.closeShortcutEditor();
}
async handleResetToDefault() {
if (!this.ipcRenderer) return;
if (!window.api) return;
const confirmation = confirm("Are you sure you want to reset all shortcuts to their default values?");
if (!confirmation) return;
try {
const defaultShortcuts = await this.ipcRenderer.invoke('get-default-shortcuts');
const defaultShortcuts = await window.api.shortcutSettingsView.getDefaultShortcuts();
this.shortcuts = defaultShortcuts;
} catch (error) {
alert('Failed to load default settings.');

View File

@ -92,7 +92,11 @@ function createFeatureWindows(header, namesToCreate) {
skipTaskbar: true,
hiddenInMissionControl: true,
resizable: true,
webPreferences: { nodeIntegration: true, contextIsolation: false },
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, '../preload.js'),
},
};
const createFeatureWindow = (name) => {
@ -377,8 +381,9 @@ function createWindows() {
focusable: true,
acceptFirstMouse: true,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, '../preload.js'),
backgroundThrottling: false,
webSecurity: false,
enableRemoteModule: false,
@ -417,6 +422,21 @@ function createWindows() {
});
setupIpcHandlers(movementManager);
// Content protection helper functions
const getContentProtectionStatus = () => isContentProtectionOn;
const setContentProtection = (status) => {
isContentProtectionOn = status;
console.log(`[Protection] Content protection toggled to: ${isContentProtectionOn}`);
windowPool.forEach(win => {
if (win && !win.isDestroyed()) {
win.setContentProtection(isContentProtectionOn);
}
});
};
// Initialize windowBridge with required dependencies
windowBridge.initialize(windowPool, require('electron').app, require('electron').shell, getCurrentDisplay, createFeatureWindows, movementManager, getContentProtectionStatus, setContentProtection, updateLayout);
if (currentHeaderState === 'main') {
createFeatureWindows(header, ['listen', 'ask', 'settings', 'shortcut-settings']);
@ -484,6 +504,8 @@ function loadAndRegisterShortcuts(movementManager) {
function setupIpcHandlers(movementManager) {
setupApiKeyIPC();
// quit-application handler moved to windowBridge.js to avoid duplication
screen.on('display-added', (event, newDisplay) => {
console.log('[Display] New display added:', newDisplay.id);
});
@ -502,20 +524,7 @@ function setupIpcHandlers(movementManager) {
updateLayout();
});
ipcMain.handle('toggle-content-protection', () => {
isContentProtectionOn = !isContentProtectionOn;
console.log(`[Protection] Content protection toggled to: ${isContentProtectionOn}`);
windowPool.forEach(win => {
if (win && !win.isDestroyed()) {
win.setContentProtection(isContentProtectionOn);
}
});
return isContentProtectionOn;
});
ipcMain.handle('get-content-protection-status', () => {
return isContentProtectionOn;
});
// Content protection handlers moved to windowBridge.js to avoid duplication
ipcMain.on('header-state-changed', (event, state) => {
console.log(`[WindowManager] Header state changed to: ${state}`);
@ -539,16 +548,7 @@ function setupIpcHandlers(movementManager) {
return { ...defaultKeybinds, ...savedKeybinds };
});
ipcMain.handle('open-shortcut-editor', () => {
const header = windowPool.get('header');
if (!header) return;
// 편집기 열기 전 모든 단축키 비활성화
globalShortcut.unregisterAll();
console.log('[Shortcuts] Disabled for editing.');
createFeatureWindows(header, 'shortcut-settings');
});
// open-shortcut-editor handler moved to windowBridge.js to avoid duplication
ipcMain.handle('get-default-shortcuts', () => {
shortCutStore.set('customKeybinds', {});
@ -590,56 +590,7 @@ function setupIpcHandlers(movementManager) {
}
});
ipcMain.handle('resize-header-window', (event, { width, height }) => {
const header = windowPool.get('header');
if (header) {
console.log(`[WindowManager] Resize request: ${width}x${height}`);
// Prevent resizing during animations or if already at target size
if (movementManager && movementManager.isAnimating) {
console.log('[WindowManager] Skipping resize during animation');
return { success: false, error: 'Cannot resize during animation' };
}
const currentBounds = header.getBounds();
console.log(`[WindowManager] Current bounds: ${currentBounds.width}x${currentBounds.height} at (${currentBounds.x}, ${currentBounds.y})`);
// Skip if already at target size to prevent unnecessary operations
if (currentBounds.width === width && currentBounds.height === height) {
console.log('[WindowManager] Already at target size, skipping resize');
return { success: true };
}
const wasResizable = header.isResizable();
if (!wasResizable) {
header.setResizable(true);
}
// Calculate the center point of the current window
const centerX = currentBounds.x + currentBounds.width / 2;
// Calculate new X position to keep the window centered
const newX = Math.round(centerX - width / 2);
// Get the current display to ensure we stay within bounds
const display = getCurrentDisplay(header);
const { x: workAreaX, width: workAreaWidth } = display.workArea;
// Clamp the new position to stay within display bounds
const clampedX = Math.max(workAreaX, Math.min(workAreaX + workAreaWidth - width, newX));
header.setBounds({ x: clampedX, y: currentBounds.y, width, height });
if (!wasResizable) {
header.setResizable(false);
}
// Update layout after resize
updateLayout();
return { success: true };
}
return { success: false, error: 'Header window not found' };
});
// resize-header-window handler moved to windowBridge.js to avoid duplication
ipcMain.on('header-animation-finished', (event, state) => {
const header = windowPool.get('header');
@ -706,11 +657,7 @@ function setupIpcHandlers(movementManager) {
});
ipcMain.handle('move-window-step', (event, direction) => {
if (movementManager) {
movementManager.moveStep(direction);
}
});
// move-window-step handler moved to windowBridge.js to avoid duplication
ipcMain.handle('adjust-window-height', (event, targetHeight) => {
const senderWindow = BrowserWindow.fromWebContents(event.sender);
@ -792,6 +739,8 @@ function setupIpcHandlers(movementManager) {
}
});
// firebase-logout handler moved to windowBridge.js to avoid duplication
ipcMain.handle('check-system-permissions', async () => {
const { systemPreferences } = require('electron');
const permissions = {
@ -929,70 +878,6 @@ function setupIpcHandlers(movementManager) {
}
});
ipcMain.on('show-settings-window', (event, bounds) => {
if (!bounds) return;
const win = windowPool.get('settings');
if (win && !win.isDestroyed()) {
console.log('[WindowManager] Showing settings window');
if (settingsHideTimer) {
clearTimeout(settingsHideTimer);
settingsHideTimer = null;
}
// Adjust position based on button bounds
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;
console.log(`[WindowManager] Positioning settings window at (${x}, ${y}) based on button bounds.`);
win.show();
win.moveTop();
win.setAlwaysOnTop(true);
console.log('[WindowManager] Settings window shown');
} else {
console.log('[WindowManager] Settings window not found');
}
});
ipcMain.on('hide-settings-window', (event) => {
const window = windowPool.get("settings");
if (window && !window.isDestroyed()) {
if (settingsHideTimer) {
clearTimeout(settingsHideTimer);
}
settingsHideTimer = setTimeout(() => {
if (window && !window.isDestroyed()) {
window.setAlwaysOnTop(false);
window.hide();
}
settingsHideTimer = null;
}, 200);
window.__lockedByButton = false;
}
});
ipcMain.on('cancel-hide-settings-window', (event) => {
if (settingsHideTimer) {
clearTimeout(settingsHideTimer);
settingsHideTimer = null;
}
});
ipcMain.handle('ask:closeAskWindow', async () => {
const askWindow = windowPool.get('ask');
@ -1193,13 +1078,13 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, movementMan
console.log('[Shortcuts] Broadcasted updated shortcuts to all windows.');
}
// 하드코딩된 단축키 등록을 위해 변수 유지
// 하드코딩된 단축키 등록을 위해 변수 유지
const isMac = process.platform === 'darwin';
const modifier = isMac ? 'Cmd' : 'Ctrl';
const header = windowPool.get('header');
const state = header?.currentHeaderState || currentHeaderState;
// 기능 1: 사용자가 설정할 수 없는 '모니터 이동' 단축키 (기존 로직 유지)
// 기능 1: 사용자가 설정할 수 없는 '모니터 이동' 단축키 (기존 로직 유지)
const displays = screen.getAllDisplays();
if (displays.length > 1) {
displays.forEach((display, index) => {
@ -1226,7 +1111,7 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, movementMan
return;
}
// 기능 2: 사용자가 설정할 수 없는 '화면 가장자리 이동' 단축키 (기존 로직 유지)
// 기능 2: 사용자가 설정할 수 없는 '화면 가장자리 이동' 단축키 (기존 로직 유지)
const edgeDirections = [
{ key: `${modifier}+Shift+Left`, direction: 'left' },
{ key: `${modifier}+Shift+Right`, direction: 'right' },
@ -1244,7 +1129,7 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, movementMan
});
// 기능 3: 사용자가 설정 가능한 모든 단축키를 동적으로 등록 (새로운 방식 적용)
// 기능 3: 사용자가 설정 가능한 모든 단축키를 동적으로 등록 (새로운 방식 적용)
for (const action in keybinds) {
const accelerator = keybinds[action];
if (!accelerator) continue;