Merge branch 'refactor/0712' of https://github.com/pickle-com/glass into refactor/0712

This commit is contained in:
sanio 2025-07-12 23:25:51 +09:00
commit d18c4c9276
16 changed files with 430 additions and 222 deletions

View File

@ -1,10 +1,16 @@
// src/bridge/featureBridge.js // src/bridge/featureBridge.js
const { ipcMain } = require('electron'); const { ipcMain } = require('electron');
const settingsService = require('../features/settings/settingsService'); const settingsService = require('../features/settings/settingsService');
const askService = require('../features/ask/askService');
module.exports = { module.exports = {
// Renderer로부터의 요청을 수신 // Renderer로부터의 요청을 수신
initialize() { initialize() {
// Ask 관련 핸들러 추가
ipcMain.handle('ask:sendMessage', async (event, userPrompt) => {
return askService.sendMessage(userPrompt);
});
// 기존 ask 핸들러 유지 // 기존 ask 핸들러 유지
ipcMain.handle('feature:ask', (e, query) => { ipcMain.handle('feature:ask', (e, query) => {
// 실제로는 여기서 Controller -> Service 로직 수행 // 실제로는 여기서 Controller -> Service 로직 수행
@ -61,7 +67,7 @@ module.exports = {
return await settingsService.setAutoUpdateSetting(isEnabled); return await settingsService.setAutoUpdateSetting(isEnabled);
}); });
console.log('[FeatureBridge] Initialized with settings handlers.'); console.log('[FeatureBridge] Initialized with ask and settings handlers.');
}, },
// Renderer로 상태를 전송 // Renderer로 상태를 전송

View File

@ -26,7 +26,7 @@ async function sendMessage(userPrompt) {
let sessionId; let sessionId;
try { try {
console.log(`[AskService] 🤖 Processing message: ${userPrompt.substring(0, 50)}...`); console.log(`[AskService] Processing message: ${userPrompt.substring(0, 50)}...`);
// --- Save user's message immediately --- // --- Save user's message immediately ---
// This ensures the user message is always timestamped before the assistant's response. // This ensures the user message is always timestamped before the assistant's response.
@ -134,12 +134,11 @@ async function sendMessage(userPrompt) {
} }
function initialize() { function initialize() {
ipcMain.handle('ask:sendMessage', async (event, userPrompt) => { // IPC 핸들러는 featureBridge.js로 이동됨
return sendMessage(userPrompt);
});
console.log('[AskService] Initialized and ready.'); console.log('[AskService] Initialized and ready.');
} }
module.exports = { module.exports = {
initialize, initialize,
sendMessage, // sendMessage 함수 export 추가
}; };

View File

@ -2,8 +2,254 @@
const { contextBridge, ipcRenderer } = require('electron'); const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('api', { contextBridge.exposeInMainWorld('api', {
// Ask
ask: {
// sendMessage
sendMessage: (message) => ipcRenderer.invoke('ask:sendMessage', message),
// window
adjustWindowHeight: (height) => ipcRenderer.invoke('adjust-window-height', height),
forceCloseWindow: (windowName) => ipcRenderer.invoke('force-close-window', windowName),
closeWindowIfEmpty: () => ipcRenderer.invoke('close-ask-window-if-empty'),
// event listener
onGlobalSend: (callback) => ipcRenderer.on('ask-global-send', callback),
onReceiveQuestionFromAssistant: (callback) => ipcRenderer.on('receive-question-from-assistant', callback),
onHideTextInput: (callback) => ipcRenderer.on('hide-text-input', callback),
onWindowHideAnimation: (callback) => ipcRenderer.on('window-hide-animation', callback),
onWindowBlur: (callback) => ipcRenderer.on('window-blur', callback),
onWindowDidShow: (callback) => ipcRenderer.on('window-did-show', callback),
onResponseChunk: (callback) => ipcRenderer.on('ask-response-chunk', callback),
onResponseStreamEnd: (callback) => ipcRenderer.on('ask-response-stream-end', callback),
onScrollResponseUp: (callback) => ipcRenderer.on('scroll-response-up', callback),
onScrollResponseDown: (callback) => ipcRenderer.on('scroll-response-down', callback),
// event listener remove
removeOnGlobalSend: (callback) => ipcRenderer.removeListener('ask-global-send', callback),
removeOnReceiveQuestionFromAssistant: (callback) => ipcRenderer.removeListener('receive-question-from-assistant', callback),
removeOnHideTextInput: (callback) => ipcRenderer.removeListener('hide-text-input', callback),
removeOnWindowHideAnimation: (callback) => ipcRenderer.removeListener('window-hide-animation', callback),
removeOnWindowBlur: (callback) => ipcRenderer.removeListener('window-blur', callback),
removeOnWindowDidShow: (callback) => ipcRenderer.removeListener('window-did-show', callback),
removeOnResponseChunk: (callback) => ipcRenderer.removeListener('ask-response-chunk', callback),
removeOnResponseStreamEnd: (callback) => ipcRenderer.removeListener('ask-response-stream-end', callback),
removeOnScrollResponseUp: (callback) => ipcRenderer.removeListener('scroll-response-up', callback),
removeOnScrollResponseDown: (callback) => ipcRenderer.removeListener('scroll-response-down', callback)
},
// Listen
listen: {
// window
adjustWindowHeight: (height) => ipcRenderer.invoke('adjust-window-height', height),
// event listener
onSessionStateChanged: (callback) => ipcRenderer.on('session-state-changed', callback),
onSttUpdate: (callback) => ipcRenderer.on('stt-update', callback),
onSummaryUpdate: (callback) => ipcRenderer.on('summary-update', callback),
// remove event listener
removeOnSessionStateChanged: (callback) => ipcRenderer.removeListener('session-state-changed', callback),
removeOnSttUpdate: (callback) => ipcRenderer.removeListener('stt-update', callback),
removeOnSummaryUpdate: (callback) => ipcRenderer.removeListener('summary-update', callback),
// Ask window
isAskWindowVisible: (windowName) => ipcRenderer.invoke('is-ask-window-visible', windowName),
toggleFeature: (featureName) => ipcRenderer.invoke('toggle-feature', featureName),
sendQuestionToAsk: (question) => ipcRenderer.invoke('send-question-to-ask', question)
},
// Audio
audio: {
// audio capture
sendAudioContent: (options) => ipcRenderer.invoke('send-audio-content', options),
sendSystemAudioContent: (options) => ipcRenderer.invoke('send-system-audio-content', options),
// macOS audio
startMacosAudio: () => ipcRenderer.invoke('start-macos-audio'),
stopMacosAudio: () => ipcRenderer.invoke('stop-macos-audio'),
// screen capture
startScreenCapture: () => ipcRenderer.invoke('start-screen-capture'),
stopScreenCapture: () => ipcRenderer.invoke('stop-screen-capture'),
captureScreenshot: (options) => ipcRenderer.invoke('capture-screenshot', options),
getCurrentScreenshot: () => ipcRenderer.invoke('get-current-screenshot'),
// session
isSessionActive: () => ipcRenderer.invoke('is-session-active'),
// event listener
onChangeListenCaptureState: (callback) => ipcRenderer.on('change-listen-capture-state', callback),
onSystemAudioData: (callback) => ipcRenderer.on('system-audio-data', callback),
// remove event listener
removeOnChangeListenCaptureState: (callback) => ipcRenderer.removeListener('change-listen-capture-state', callback),
removeOnSystemAudioData: (callback) => ipcRenderer.removeListener('system-audio-data', callback)
},
// Settings
settings: {
// shortcut
saveShortcuts: (shortcuts) => ipcRenderer.invoke('save-shortcuts', shortcuts),
getDefaultShortcuts: () => ipcRenderer.invoke('get-default-shortcuts'),
// shortcut editor
closeShortcutEditor: () => ipcRenderer.send('close-shortcut-editor'),
// event listener
onLoadShortcuts: (callback) => ipcRenderer.on('load-shortcuts', callback),
// remove event listener
removeOnLoadShortcuts: (callback) => ipcRenderer.removeListener('load-shortcuts', callback)
},
// App
app: {
// quit application
quitApplication: () => ipcRenderer.invoke('quit-application'),
// session
isSessionActive: () => ipcRenderer.invoke('is-session-active'),
// event listener
onClickThroughToggled: (callback) => ipcRenderer.on('click-through-toggled', callback),
// remove event listener
removeOnClickThroughToggled: (callback) => ipcRenderer.removeListener('click-through-toggled', callback),
// remove all listeners
removeAllListeners: (eventName) => ipcRenderer.removeAllListeners(eventName)
},
// API Key Header
apikey: {
// model
getProviderConfig: () => ipcRenderer.invoke('model:get-provider-config'),
validateKey: (options) => ipcRenderer.invoke('model:validate-key', options),
setSelectedModel: (options) => ipcRenderer.invoke('model:set-selected-model', options),
areProvidersConfigured: () => ipcRenderer.invoke('model:are-providers-configured'),
// Ollama
getOllamaStatus: () => ipcRenderer.invoke('ollama:get-status'),
getModelSuggestions: () => ipcRenderer.invoke('ollama:get-model-suggestions'),
ensureReady: () => ipcRenderer.invoke('ollama:ensure-ready'),
installOllama: () => ipcRenderer.invoke('ollama:install'),
startService: () => ipcRenderer.invoke('ollama:start-service'),
pullModel: (modelName) => ipcRenderer.invoke('ollama:pull-model', modelName),
// Whisper
downloadModel: (modelId) => ipcRenderer.invoke('whisper:download-model', modelId),
// position
getHeaderPosition: () => ipcRenderer.invoke('get-header-position'),
moveHeaderTo: (x, y) => ipcRenderer.invoke('move-header-to', x, y),
// authentication
startFirebaseAuth: () => ipcRenderer.invoke('start-firebase-auth'),
getCurrentUser: () => ipcRenderer.invoke('get-current-user'),
quitApplication: () => ipcRenderer.invoke('quit-application'),
// event listener
onOllamaInstallProgress: (callback) => ipcRenderer.on('ollama:install-progress', callback),
onOllamaInstallComplete: (callback) => ipcRenderer.on('ollama:install-complete', callback),
onOllamaPullProgress: (callback) => ipcRenderer.on('ollama:pull-progress', callback),
onWhisperDownloadProgress: (callback) => ipcRenderer.on('whisper:download-progress', callback),
// remove event listener
removeOnOllamaInstallProgress: (callback) => ipcRenderer.removeListener('ollama:install-progress', callback),
removeOnOllamaInstallComplete: (callback) => ipcRenderer.removeListener('ollama:install-complete', callback),
removeOnOllamaPullProgress: (callback) => ipcRenderer.removeListener('ollama:pull-progress', callback),
removeOnWhisperDownloadProgress: (callback) => ipcRenderer.removeListener('whisper:download-progress', callback),
// remove all listeners
removeAllListeners: (eventName) => ipcRenderer.removeAllListeners(eventName)
},
// Controller
controller: {
// user state
getCurrentUser: () => ipcRenderer.invoke('get-current-user'),
// model
areProvidersConfigured: () => ipcRenderer.invoke('model:are-providers-configured'),
// permission
checkPermissionsCompleted: () => ipcRenderer.invoke('check-permissions-completed'),
checkSystemPermissions: () => ipcRenderer.invoke('check-system-permissions'),
// window
resizeHeaderWindow: (options) => ipcRenderer.invoke('resize-header-window', options),
// state change
sendHeaderStateChanged: (state) => ipcRenderer.send('header-state-changed', state),
// event listener
onUserStateChanged: (callback) => ipcRenderer.on('user-state-changed', callback),
onAuthFailed: (callback) => ipcRenderer.on('auth-failed', callback),
onForceShowApiKeyHeader: (callback) => ipcRenderer.on('force-show-apikey-header', callback),
// remove event listener
removeOnUserStateChanged: (callback) => ipcRenderer.removeListener('user-state-changed', callback),
removeOnAuthFailed: (callback) => ipcRenderer.removeListener('auth-failed', callback),
removeOnForceShowApiKeyHeader: (callback) => ipcRenderer.removeListener('force-show-apikey-header', callback)
},
// Header
header: {
// position
getHeaderPosition: () => ipcRenderer.invoke('get-header-position'),
moveHeaderTo: (x, y) => ipcRenderer.invoke('move-header-to', x, y),
// event listener
onSessionStateText: (callback) => ipcRenderer.on('session-state-text', callback),
onShortcutsUpdated: (callback) => ipcRenderer.on('shortcuts-updated', callback),
// remove event listener
removeOnSessionStateText: (callback) => ipcRenderer.removeListener('session-state-text', callback),
removeOnShortcutsUpdated: (callback) => ipcRenderer.removeListener('shortcuts-updated', callback),
// animation
sendAnimationFinished: (state) => ipcRenderer.send('header-animation-finished', state),
// settings window
cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'),
showSettingsWindow: (options) => ipcRenderer.send('show-settings-window', options),
hideSettingsWindow: () => ipcRenderer.send('hide-settings-window'),
// invoke
invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args)
},
// Permissions
permissions: {
checkSystemPermissions: () => ipcRenderer.invoke('check-system-permissions'),
requestMicrophonePermission: () => ipcRenderer.invoke('request-microphone-permission'),
openSystemPreferences: (section) => ipcRenderer.invoke('open-system-preferences', section),
markPermissionsCompleted: () => ipcRenderer.invoke('mark-permissions-completed'),
quitApplication: () => ipcRenderer.invoke('quit-application')
},
// Animation
animation: {
// send animation finished
sendAnimationFinished: () => ipcRenderer.send('animation-finished'),
// event listener
onWindowShowAnimation: (callback) => ipcRenderer.on('window-show-animation', callback),
onWindowHideAnimation: (callback) => ipcRenderer.on('window-hide-animation', callback),
onSettingsWindowHideAnimation: (callback) => ipcRenderer.on('settings-window-hide-animation', callback),
onListenWindowMoveToCenter: (callback) => ipcRenderer.on('listen-window-move-to-center', callback),
onListenWindowMoveToLeft: (callback) => ipcRenderer.on('listen-window-move-to-left', callback),
// remove event listener
removeOnWindowShowAnimation: (callback) => ipcRenderer.removeListener('window-show-animation', callback),
removeOnWindowHideAnimation: (callback) => ipcRenderer.removeListener('window-hide-animation', callback),
removeOnSettingsWindowHideAnimation: (callback) => ipcRenderer.removeListener('settings-window-hide-animation', callback),
removeOnListenWindowMoveToCenter: (callback) => ipcRenderer.removeListener('listen-window-move-to-center', callback),
removeOnListenWindowMoveToLeft: (callback) => ipcRenderer.removeListener('listen-window-move-to-left', callback)
},
feature: { feature: {
// 기존 ask 관련 유지 // ask
submitAsk: (query) => ipcRenderer.invoke('feature:ask', query), submitAsk: (query) => ipcRenderer.invoke('feature:ask', query),
onAskProgress: (callback) => ipcRenderer.on('feature:ask:progress', (e, p) => callback(p)), onAskProgress: (callback) => ipcRenderer.on('feature:ask:progress', (e, p) => callback(p)),
@ -51,13 +297,13 @@ contextBridge.exposeInMainWorld('api', {
hideSettingsWindow: () => ipcRenderer.send('hide-settings-window') hideSettingsWindow: () => ipcRenderer.send('hide-settings-window')
} }
}, },
// 기존 window 유지 // window
window: { window: {
// 기존 // window
hide: () => ipcRenderer.send('window:hide'), hide: () => ipcRenderer.send('window:hide'),
onFocusChange: (callback) => ipcRenderer.on('window:focus-change', (e, f) => callback(f)), onFocusChange: (callback) => ipcRenderer.on('window:focus-change', (e, f) => callback(f)),
// 추가 // settings window
showSettingsWindow: (bounds) => ipcRenderer.send('show-settings-window', bounds), showSettingsWindow: (bounds) => ipcRenderer.send('show-settings-window', bounds),
hideSettingsWindow: () => ipcRenderer.send('hide-settings-window'), hideSettingsWindow: () => ipcRenderer.send('hide-settings-window'),
cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'), cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'),
@ -67,7 +313,7 @@ contextBridge.exposeInMainWorld('api', {
ollamaShutdown: (graceful) => ipcRenderer.invoke('ollama:shutdown', graceful), ollamaShutdown: (graceful) => ipcRenderer.invoke('ollama:shutdown', graceful),
startFirebaseAuth: () => ipcRenderer.invoke('start-firebase-auth'), startFirebaseAuth: () => ipcRenderer.invoke('start-firebase-auth'),
// on methods (listeners) // event listener
onUserStateChanged: (callback) => ipcRenderer.on('user-state-changed', callback), onUserStateChanged: (callback) => ipcRenderer.on('user-state-changed', callback),
removeOnUserStateChanged: (callback) => ipcRenderer.removeListener('user-state-changed', callback), removeOnUserStateChanged: (callback) => ipcRenderer.removeListener('user-state-changed', callback),
onSettingsUpdated: (callback) => ipcRenderer.on('settings-updated', callback), onSettingsUpdated: (callback) => ipcRenderer.on('settings-updated', callback),
@ -79,7 +325,7 @@ contextBridge.exposeInMainWorld('api', {
onWhisperDownloadProgress: (callback) => ipcRenderer.on('whisper:download-progress', callback), onWhisperDownloadProgress: (callback) => ipcRenderer.on('whisper:download-progress', callback),
removeOnWhisperDownloadProgress: (callback) => ipcRenderer.removeListener('whisper:download-progress', callback), removeOnWhisperDownloadProgress: (callback) => ipcRenderer.removeListener('whisper:download-progress', callback),
// send methods // send
cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'), cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'),
hideSettingsWindow: () => ipcRenderer.send('hide-settings-window') hideSettingsWindow: () => ipcRenderer.send('hide-settings-window')
} }

View File

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

View File

@ -50,22 +50,20 @@ class HeaderTransitionManager {
this._bootstrap(); this._bootstrap();
if (window.require) { if (window.api && window.api.controller) {
const { ipcRenderer } = window.require('electron'); window.api.controller.onUserStateChanged((event, userState) => {
ipcRenderer.on('user-state-changed', (event, userState) => {
console.log('[HeaderController] Received user state change:', userState); console.log('[HeaderController] Received user state change:', userState);
this.handleStateUpdate(userState); this.handleStateUpdate(userState);
}); });
ipcRenderer.on('auth-failed', (event, { message }) => { window.api.controller.onAuthFailed((event, { message }) => {
console.error('[HeaderController] Received auth failure from main process:', message); console.error('[HeaderController] Received auth failure from main process:', message);
if (this.apiKeyHeader) { if (this.apiKeyHeader) {
this.apiKeyHeader.errorMessage = 'Authentication failed. Please try again.'; this.apiKeyHeader.errorMessage = 'Authentication failed. Please try again.';
this.apiKeyHeader.isLoading = false; this.apiKeyHeader.isLoading = false;
} }
}); });
ipcRenderer.on('force-show-apikey-header', async () => { window.api.controller.onForceShowApiKeyHeader(async () => {
console.log('[HeaderController] Received broadcast to show apikey header. Switching now.'); console.log('[HeaderController] Received broadcast to show apikey header. Switching now.');
await this._resizeForApiKey(); await this._resizeForApiKey();
this.ensureHeader('apikey'); this.ensureHeader('apikey');
@ -75,16 +73,16 @@ class HeaderTransitionManager {
notifyHeaderState(stateOverride) { notifyHeaderState(stateOverride) {
const state = stateOverride || this.currentHeaderType || 'apikey'; const state = stateOverride || this.currentHeaderType || 'apikey';
if (window.require) { if (window.api && window.api.controller) {
window.require('electron').ipcRenderer.send('header-state-changed', state); window.api.controller.sendHeaderStateChanged(state);
} }
} }
async _bootstrap() { async _bootstrap() {
// The initial state will be sent by the main process via 'user-state-changed' // The initial state will be sent by the main process via 'user-state-changed'
// We just need to request it. // We just need to request it.
if (window.require) { if (window.api && window.api.controller) {
const userState = await window.require('electron').ipcRenderer.invoke('get-current-user'); const userState = await window.api.controller.getCurrentUser();
console.log('[HeaderController] Bootstrapping with initial user state:', userState); console.log('[HeaderController] Bootstrapping with initial user state:', userState);
this.handleStateUpdate(userState); this.handleStateUpdate(userState);
} else { } else {
@ -96,8 +94,8 @@ class HeaderTransitionManager {
//////// after_modelStateService //////// //////// after_modelStateService ////////
async handleStateUpdate(userState) { async handleStateUpdate(userState) {
const { ipcRenderer } = window.require('electron'); if (!window.api || !window.api.controller) return;
const isConfigured = await ipcRenderer.invoke('model:are-providers-configured'); const isConfigured = await window.api.controller.areProvidersConfigured();
if (isConfigured) { if (isConfigured) {
const { isLoggedIn } = userState; const { isLoggedIn } = userState;
@ -126,10 +124,9 @@ class HeaderTransitionManager {
} }
// Check if permissions were previously completed // Check if permissions were previously completed
if (window.require) { if (window.api && window.api.controller) {
const { ipcRenderer } = window.require('electron');
try { try {
const permissionsCompleted = await ipcRenderer.invoke('check-permissions-completed'); const permissionsCompleted = await window.api.controller.checkPermissionsCompleted();
if (permissionsCompleted) { if (permissionsCompleted) {
console.log('[HeaderController] Permissions were previously completed, checking current status...'); console.log('[HeaderController] Permissions were previously completed, checking current status...');
@ -162,38 +159,30 @@ class HeaderTransitionManager {
} }
_resizeForMain() { _resizeForMain() {
if (!window.require) return; if (!window.api || !window.api.controller) return;
return window return window.api.controller.resizeHeaderWindow({ width: 353, height: 47 })
.require('electron')
.ipcRenderer.invoke('resize-header-window', { width: 353, height: 47 })
.catch(() => {}); .catch(() => {});
} }
async _resizeForApiKey() { async _resizeForApiKey() {
if (!window.require) return; if (!window.api || !window.api.controller) return;
return window return window.api.controller.resizeHeaderWindow({ width: 350, height: 300 })
.require('electron')
.ipcRenderer.invoke('resize-header-window', { width: 350, height: 300 })
.catch(() => {}); .catch(() => {});
} }
async _resizeForPermissionHeader() { async _resizeForPermissionHeader() {
if (!window.require) return; if (!window.api || !window.api.controller) return;
return window return window.api.controller.resizeHeaderWindow({ width: 285, height: 220 })
.require('electron')
.ipcRenderer.invoke('resize-header-window', { width: 285, height: 220 })
.catch(() => {}); .catch(() => {});
} }
async checkPermissions() { async checkPermissions() {
if (!window.require) { if (!window.api || !window.api.controller) {
return { success: true }; return { success: true };
} }
const { ipcRenderer } = window.require('electron');
try { try {
const permissions = await ipcRenderer.invoke('check-system-permissions'); const permissions = await window.api.controller.checkSystemPermissions();
console.log('[HeaderController] Current permissions:', permissions); console.log('[HeaderController] Current permissions:', permissions);
if (!permissions.needsSetup) { if (!permissions.needsSetup) {

View File

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

View File

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

View File

@ -74,13 +74,11 @@ export class PickleGlassApp extends LitElement {
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
if (window.require) { if (window.api && window.api.app) {
const { ipcRenderer } = window.require('electron'); window.api.app.onClickThroughToggled((_, isEnabled) => {
ipcRenderer.on('click-through-toggled', (_, isEnabled) => {
this._isClickThrough = isEnabled; this._isClickThrough = isEnabled;
}); });
// ipcRenderer.on('start-listening-session', () => { // window.api.app.onStartListeningSession(() => {
// console.log('Received start-listening-session command, calling handleListenClick.'); // console.log('Received start-listening-session command, calling handleListenClick.');
// this.handleListenClick(); // this.handleListenClick();
// }); // });
@ -89,10 +87,9 @@ export class PickleGlassApp extends LitElement {
disconnectedCallback() { disconnectedCallback() {
super.disconnectedCallback(); super.disconnectedCallback();
if (window.require) { if (window.api && window.api.app) {
const { ipcRenderer } = window.require('electron'); window.api.app.removeAllListeners('click-through-toggled');
ipcRenderer.removeAllListeners('click-through-toggled'); // window.api.app.removeAllListeners('start-listening-session');
// ipcRenderer.removeAllListeners('start-listening-session');
} }
} }
@ -160,9 +157,8 @@ export class PickleGlassApp extends LitElement {
// } // }
async handleClose() { async handleClose() {
if (window.require) { if (window.api && window.api.app) {
const { ipcRenderer } = window.require('electron'); await window.api.app.quitApplication();
await ipcRenderer.invoke('quit-application');
} }
} }

View File

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

View File

@ -1113,9 +1113,8 @@ export class AskView extends LitElement {
requestWindowResize(targetHeight) { requestWindowResize(targetHeight) {
if (window.require) { if (window.api && window.api.ask) {
const { ipcRenderer } = window.require('electron'); window.api.ask.adjustWindowHeight(targetHeight);
ipcRenderer.invoke('adjust-window-height', targetHeight);
} }
} }
@ -1279,9 +1278,8 @@ export class AskView extends LitElement {
this.requestUpdate(); this.requestUpdate();
this.renderContent(); this.renderContent();
if (window.require) { if (window.api && window.api.ask) {
const { ipcRenderer } = window.require('electron'); window.api.ask.sendMessage(text).catch(error => {
ipcRenderer.invoke('ask:sendMessage', text).catch(error => {
console.error('Error sending text:', error); console.error('Error sending text:', error);
this.isLoading = false; this.isLoading = false;
this.isStreaming = false; this.isStreaming = false;
@ -1410,7 +1408,7 @@ export class AskView extends LitElement {
// Dynamically resize the BrowserWindow to fit current content // Dynamically resize the BrowserWindow to fit current content
adjustWindowHeight() { adjustWindowHeight() {
if (!window.require) return; if (!window.api || !window.api.ask) return;
this.updateComplete.then(() => { this.updateComplete.then(() => {
const headerEl = this.shadowRoot.querySelector('.response-header'); const headerEl = this.shadowRoot.querySelector('.response-header');
@ -1427,8 +1425,7 @@ export class AskView extends LitElement {
const targetHeight = Math.min(700, idealHeight); const targetHeight = Math.min(700, idealHeight);
const { ipcRenderer } = window.require('electron'); window.api.ask.adjustWindowHeight(targetHeight);
ipcRenderer.invoke('adjust-window-height', targetHeight);
}).catch(err => console.error('AskView adjustWindowHeight error:', err)); }).catch(err => console.error('AskView adjustWindowHeight error:', err));
} }

View File

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

View File

@ -198,7 +198,7 @@ function runAecSync(micF32, sysF32) {
// System audio data handler // System audio data handler
ipcRenderer.on('system-audio-data', (event, { data }) => { window.api.audio.onSystemAudioData((event, { data }) => {
systemAudioBuffer.push({ systemAudioBuffer.push({
data: data, data: data,
timestamp: Date.now(), timestamp: Date.now(),
@ -336,7 +336,7 @@ async function setupMicProcessing(micStream) {
const pcm16 = convertFloat32ToInt16(processedChunk); const pcm16 = convertFloat32ToInt16(processedChunk);
const b64 = arrayBufferToBase64(pcm16.buffer); const b64 = arrayBufferToBase64(pcm16.buffer);
ipcRenderer.invoke('send-audio-content', { window.api.audio.sendAudioContent({
data: b64, data: b64,
mimeType: 'audio/pcm;rate=24000', mimeType: 'audio/pcm;rate=24000',
}); });
@ -369,7 +369,7 @@ function setupLinuxMicProcessing(micStream) {
const pcmData16 = convertFloat32ToInt16(chunk); const pcmData16 = convertFloat32ToInt16(chunk);
const base64Data = arrayBufferToBase64(pcmData16.buffer); const base64Data = arrayBufferToBase64(pcmData16.buffer);
await ipcRenderer.invoke('send-audio-content', { await window.api.audio.sendAudioContent({
data: base64Data, data: base64Data,
mimeType: 'audio/pcm;rate=24000', mimeType: 'audio/pcm;rate=24000',
}); });
@ -403,7 +403,7 @@ function setupSystemAudioProcessing(systemStream) {
const base64Data = arrayBufferToBase64(pcmData16.buffer); const base64Data = arrayBufferToBase64(pcmData16.buffer);
try { try {
await ipcRenderer.invoke('send-system-audio-content', { await window.api.audio.sendSystemAudioContent({
data: base64Data, data: base64Data,
mimeType: 'audio/pcm;rate=24000', mimeType: 'audio/pcm;rate=24000',
}); });
@ -433,7 +433,7 @@ async function captureScreenshot(imageQuality = 'medium', isManual = false) {
try { try {
// Request screenshot from main process // Request screenshot from main process
const result = await ipcRenderer.invoke('capture-screenshot', { const result = await window.api.audio.captureScreenshot({
quality: imageQuality, quality: imageQuality,
}); });
@ -470,7 +470,7 @@ async function captureManualScreenshot(imageQuality = null) {
async function getCurrentScreenshot() { async function getCurrentScreenshot() {
try { try {
// First try to get a fresh screenshot from main process // First try to get a fresh screenshot from main process
const result = await ipcRenderer.invoke('get-current-screenshot'); const result = await window.api.audio.getCurrentScreenshot();
if (result.success && result.base64) { if (result.success && result.base64) {
console.log('📸 Got fresh screenshot from main process'); console.log('📸 Got fresh screenshot from main process');
@ -479,7 +479,7 @@ async function getCurrentScreenshot() {
// If no screenshot available, capture one now // If no screenshot available, capture one now
console.log('📸 No screenshot available, capturing new one'); console.log('📸 No screenshot available, capturing new one');
const captureResult = await ipcRenderer.invoke('capture-screenshot', { const captureResult = await window.api.audio.captureScreenshot({
quality: currentImageQuality, quality: currentImageQuality,
}); });
@ -518,15 +518,15 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
console.log('Starting macOS capture with SystemAudioDump...'); console.log('Starting macOS capture with SystemAudioDump...');
// Start macOS audio capture // Start macOS audio capture
const audioResult = await ipcRenderer.invoke('start-macos-audio'); const audioResult = await window.api.audio.startMacosAudio();
if (!audioResult.success) { if (!audioResult.success) {
console.warn('[listenCapture] macOS audio start failed:', audioResult.error); console.warn('[listenCapture] macOS audio start failed:', audioResult.error);
// 이미 실행 중 → stop 후 재시도 // 이미 실행 중 → stop 후 재시도
if (audioResult.error === 'already_running') { if (audioResult.error === 'already_running') {
await ipcRenderer.invoke('stop-macos-audio'); await window.api.audio.stopMacosAudio();
await new Promise(r => setTimeout(r, 500)); await new Promise(r => setTimeout(r, 500));
const retry = await ipcRenderer.invoke('start-macos-audio'); const retry = await window.api.audio.startMacosAudio();
if (!retry.success) { if (!retry.success) {
throw new Error('Retry failed: ' + retry.error); throw new Error('Retry failed: ' + retry.error);
} }
@ -536,7 +536,7 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
} }
// Initialize screen capture in main process // Initialize screen capture in main process
const screenResult = await ipcRenderer.invoke('start-screen-capture'); const screenResult = await window.api.audio.startScreenCapture();
if (!screenResult.success) { if (!screenResult.success) {
throw new Error('Failed to start screen capture: ' + screenResult.error); throw new Error('Failed to start screen capture: ' + screenResult.error);
} }
@ -604,13 +604,13 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
console.log('Starting Windows capture with native loopback audio...'); console.log('Starting Windows capture with native loopback audio...');
// Start screen capture in main process for screenshots // Start screen capture in main process for screenshots
const screenResult = await ipcRenderer.invoke('start-screen-capture'); const screenResult = await window.api.audio.startScreenCapture();
if (!screenResult.success) { if (!screenResult.success) {
throw new Error('Failed to start screen capture: ' + screenResult.error); throw new Error('Failed to start screen capture: ' + screenResult.error);
} }
// Ensure STT sessions are initialized before starting audio capture // Ensure STT sessions are initialized before starting audio capture
const sessionActive = await ipcRenderer.invoke('is-session-active'); const sessionActive = await window.api.audio.isSessionActive();
if (!sessionActive) { if (!sessionActive) {
throw new Error('STT sessions not initialized - please wait for initialization to complete'); throw new Error('STT sessions not initialized - please wait for initialization to complete');
} }
@ -715,13 +715,13 @@ function stopCapture() {
} }
// Stop screen capture in main process // Stop screen capture in main process
ipcRenderer.invoke('stop-screen-capture').catch(err => { window.api.audio.stopScreenCapture().catch(err => {
console.error('Error stopping screen capture:', err); console.error('Error stopping screen capture:', err);
}); });
// Stop macOS audio capture if running // Stop macOS audio capture if running
if (isMacOS) { if (isMacOS) {
ipcRenderer.invoke('stop-macos-audio').catch(err => { window.api.audio.stopMacosAudio().catch(err => {
console.error('Error stopping macOS audio:', err); console.error('Error stopping macOS audio:', err);
}); });
} }

View File

@ -15,7 +15,7 @@ window.pickleGlass = {
}; };
ipcRenderer.on('change-listen-capture-state', (_event, { status }) => { window.api.audio.onChangeListenCaptureState((_event, { status }) => {
if (!isListenView) { if (!isListenView) {
console.log('[Renderer] Non-listen view: ignoring capture-state change'); console.log('[Renderer] Non-listen view: ignoring capture-state change');
return; return;

View File

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

View File

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

View File

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