rough refactor done
This commit is contained in:
parent
586d44e57b
commit
d936af46a3
2
aec
2
aec
@ -1 +1 @@
|
|||||||
Subproject commit 9e11f4f95707714464194bdfc9db0222ec5c6163
|
Subproject commit f00bb1fb948053c752b916adfee19f90644a0b2f
|
@ -9,6 +9,7 @@ const shortcutsService = require('../features/shortcuts/shortcutsService');
|
|||||||
|
|
||||||
const askService = require('../features/ask/askService');
|
const askService = require('../features/ask/askService');
|
||||||
const listenService = require('../features/listen/listenService');
|
const listenService = require('../features/listen/listenService');
|
||||||
|
const permissionService = require('../features/common/services/permissionService');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// Renderer로부터의 요청을 수신
|
// Renderer로부터의 요청을 수신
|
||||||
@ -33,6 +34,14 @@ module.exports = {
|
|||||||
ipcMain.handle('save-shortcuts', async (event, newKeybinds) => await shortcutsService.handleSaveShortcuts(newKeybinds));
|
ipcMain.handle('save-shortcuts', async (event, newKeybinds) => await shortcutsService.handleSaveShortcuts(newKeybinds));
|
||||||
|
|
||||||
|
|
||||||
|
// Permissions
|
||||||
|
ipcMain.handle('check-system-permissions', async () => await permissionService.checkSystemPermissions());
|
||||||
|
ipcMain.handle('request-microphone-permission', async () => await permissionService.requestMicrophonePermission());
|
||||||
|
ipcMain.handle('open-system-preferences', async (event, section) => await permissionService.openSystemPreferences(section));
|
||||||
|
ipcMain.handle('mark-permissions-completed', async () => await permissionService.markPermissionsAsCompleted());
|
||||||
|
ipcMain.handle('check-permissions-completed', async () => await permissionService.checkPermissionsCompleted());
|
||||||
|
|
||||||
|
|
||||||
// User/Auth
|
// User/Auth
|
||||||
ipcMain.handle('get-current-user', () => authService.getCurrentUser());
|
ipcMain.handle('get-current-user', () => authService.getCurrentUser());
|
||||||
ipcMain.handle('start-firebase-auth', async () => await authService.startFirebaseAuthFlow());
|
ipcMain.handle('start-firebase-auth', async () => await authService.startFirebaseAuthFlow());
|
||||||
@ -67,7 +76,6 @@ module.exports = {
|
|||||||
ipcMain.handle('ask:sendQuestionFromAsk', async (event, userPrompt) => await askService.sendMessage(userPrompt));
|
ipcMain.handle('ask:sendQuestionFromAsk', async (event, userPrompt) => await askService.sendMessage(userPrompt));
|
||||||
ipcMain.handle('ask:sendQuestionFromSummary', async (event, userPrompt) => await askService.sendMessage(userPrompt));
|
ipcMain.handle('ask:sendQuestionFromSummary', async (event, userPrompt) => await askService.sendMessage(userPrompt));
|
||||||
ipcMain.handle('ask:toggleAskButton', async () => await askService.toggleAskButton());
|
ipcMain.handle('ask:toggleAskButton', async () => await askService.toggleAskButton());
|
||||||
ipcMain.handle('stop-screen-capture', async () => askService.handleStopScreenCapture());
|
|
||||||
|
|
||||||
// Listen
|
// Listen
|
||||||
ipcMain.handle('listen:sendMicAudio', async (event, { data, mimeType }) => await listenService.handleSendMicAudioContent(data, mimeType));
|
ipcMain.handle('listen:sendMicAudio', async (event, { data, mimeType }) => await listenService.handleSendMicAudioContent(data, mimeType));
|
||||||
@ -98,12 +106,11 @@ module.exports = {
|
|||||||
ipcMain.handle('model:validate-key', async (e, { provider, key }) => await modelStateService.handleValidateKey(provider, key));
|
ipcMain.handle('model:validate-key', async (e, { provider, key }) => await modelStateService.handleValidateKey(provider, key));
|
||||||
ipcMain.handle('model:get-all-keys', () => modelStateService.getAllApiKeys());
|
ipcMain.handle('model:get-all-keys', () => modelStateService.getAllApiKeys());
|
||||||
ipcMain.handle('model:set-api-key', async (e, { provider, key }) => await modelStateService.setApiKey(provider, key));
|
ipcMain.handle('model:set-api-key', async (e, { provider, key }) => await modelStateService.setApiKey(provider, key));
|
||||||
ipcMain.handle('model:remove-api-key', async (e, { provider }) => await modelStateService.handleRemoveApiKey(provider));
|
ipcMain.handle('model:remove-api-key', async (e, provider) => await modelStateService.handleRemoveApiKey(provider));
|
||||||
ipcMain.handle('model:get-selected-models', () => modelStateService.getSelectedModels());
|
ipcMain.handle('model:get-selected-models', () => modelStateService.getSelectedModels());
|
||||||
ipcMain.handle('model:set-selected-model', async (e, { type, modelId }) => await modelStateService.handleSetSelectedModel(type, modelId));
|
ipcMain.handle('model:set-selected-model', async (e, { type, modelId }) => await modelStateService.handleSetSelectedModel(type, modelId));
|
||||||
ipcMain.handle('model:get-available-models', (e, { type }) => modelStateService.getAvailableModels(type));
|
ipcMain.handle('model:get-available-models', (e, { type }) => modelStateService.getAvailableModels(type));
|
||||||
ipcMain.handle('model:are-providers-configured', () => modelStateService.areProvidersConfigured());
|
ipcMain.handle('model:are-providers-configured', () => modelStateService.areProvidersConfigured());
|
||||||
ipcMain.handle('model:get-current-model-info', (e, { type }) => modelStateService.getCurrentModelInfo(type));
|
|
||||||
ipcMain.handle('model:get-provider-config', () => modelStateService.getProviderConfig());
|
ipcMain.handle('model:get-provider-config', () => modelStateService.getProviderConfig());
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// src/bridge/windowBridge.js
|
// src/bridge/windowBridge.js
|
||||||
const { ipcMain } = require('electron');
|
const { ipcMain, BrowserWindow } = require('electron');
|
||||||
const windowManager = require('../window/windowManager');
|
const windowManager = require('../window/windowManager');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -15,6 +15,16 @@ module.exports = {
|
|||||||
ipcMain.handle('move-window-step', (event, direction) => windowManager.moveWindowStep(direction));
|
ipcMain.handle('move-window-step', (event, direction) => windowManager.moveWindowStep(direction));
|
||||||
ipcMain.on('close-shortcut-editor', () => windowManager.closeWindow('shortcut-settings'));
|
ipcMain.on('close-shortcut-editor', () => windowManager.closeWindow('shortcut-settings'));
|
||||||
|
|
||||||
|
// Newly moved handlers from windowManager
|
||||||
|
ipcMain.on('header-state-changed', (event, state) => windowManager.handleHeaderStateChanged(state));
|
||||||
|
ipcMain.on('header-animation-finished', (event, state) => windowManager.handleHeaderAnimationFinished(state));
|
||||||
|
ipcMain.handle('get-header-position', () => windowManager.getHeaderPosition());
|
||||||
|
ipcMain.handle('move-header', (event, newX, newY) => windowManager.moveHeader(newX, newY));
|
||||||
|
ipcMain.handle('move-header-to', (event, newX, newY) => windowManager.moveHeaderTo(newX, newY));
|
||||||
|
ipcMain.handle('adjust-window-height', (event, targetHeight) => windowManager.adjustWindowHeight(event.sender, targetHeight));
|
||||||
|
ipcMain.handle('toggle-all-windows-visibility', () => windowManager.toggleAllWindowsVisibility());
|
||||||
|
ipcMain.on('animation-finished', (event) => windowManager.handleAnimationFinished(event.sender));
|
||||||
|
ipcMain.handle('ask:closeAskWindow', () => windowManager.closeAskWindow());
|
||||||
},
|
},
|
||||||
|
|
||||||
notifyFocusChange(win, isFocused) {
|
notifyFocusChange(win, isFocused) {
|
||||||
|
@ -1,6 +1,18 @@
|
|||||||
const { BrowserWindow } = require('electron');
|
const { BrowserWindow } = require('electron');
|
||||||
const { createStreamingLLM } = require('../common/ai/factory');
|
const { createStreamingLLM } = require('../common/ai/factory');
|
||||||
const { getCurrentModelInfo, windowPool, updateLayout } = require('../../window/windowManager');
|
// Lazy require helper to avoid circular dependency issues
|
||||||
|
const getWindowManager = () => require('../../window/windowManager');
|
||||||
|
|
||||||
|
const getWindowPool = () => {
|
||||||
|
try {
|
||||||
|
return getWindowManager().windowPool;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const updateLayout = () => getWindowManager().updateLayout();
|
||||||
|
const ensureAskWindowVisible = () => getWindowManager().ensureAskWindowVisible();
|
||||||
|
|
||||||
const sessionRepository = require('../common/repositories/session');
|
const sessionRepository = require('../common/repositories/session');
|
||||||
const askRepository = require('./repositories');
|
const askRepository = require('./repositories');
|
||||||
const { getSystemPrompt } = require('../common/prompts/promptBuilder');
|
const { getSystemPrompt } = require('../common/prompts/promptBuilder');
|
||||||
@ -10,6 +22,7 @@ const os = require('os');
|
|||||||
const util = require('util');
|
const util = require('util');
|
||||||
const execFile = util.promisify(require('child_process').execFile);
|
const execFile = util.promisify(require('child_process').execFile);
|
||||||
const { desktopCapturer } = require('electron');
|
const { desktopCapturer } = require('electron');
|
||||||
|
const modelStateService = require('../common/services/modelStateService');
|
||||||
|
|
||||||
// Try to load sharp, but don't fail if it's not available
|
// Try to load sharp, but don't fail if it's not available
|
||||||
let sharp;
|
let sharp;
|
||||||
@ -126,33 +139,33 @@ class AskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_broadcastState() {
|
_broadcastState() {
|
||||||
const askWindow = windowPool.get('ask');
|
const askWindow = getWindowPool()?.get('ask');
|
||||||
if (askWindow && !askWindow.isDestroyed()) {
|
if (askWindow && !askWindow.isDestroyed()) {
|
||||||
askWindow.webContents.send('ask:stateUpdate', this.state);
|
askWindow.webContents.send('ask:stateUpdate', this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async toggleAskButton() {
|
async toggleAskButton() {
|
||||||
const askWindow = windowPool.get('ask');
|
const askWindow = getWindowPool()?.get('ask');
|
||||||
|
|
||||||
// 답변이 있거나 스트리밍 중일 때
|
// 답변이 있거나 스트리밍 중일 때
|
||||||
const hasContent = this.state.isStreaming || (this.state.currentResponse && this.state.currentResponse.length > 0);
|
const hasContent = this.state.isStreaming || (this.state.currentResponse && this.state.currentResponse.length > 0);
|
||||||
|
|
||||||
if (askWindow.isVisible() && hasContent) {
|
if (askWindow && askWindow.isVisible() && hasContent) {
|
||||||
// 창을 닫는 대신, 텍스트 입력창만 토글합니다.
|
// 창을 닫는 대신, 텍스트 입력창만 토글합니다.
|
||||||
this.state.showTextInput = !this.state.showTextInput;
|
this.state.showTextInput = !this.state.showTextInput;
|
||||||
this._broadcastState(); // 변경된 상태 전파
|
this._broadcastState(); // 변경된 상태 전파
|
||||||
} else {
|
} else {
|
||||||
// 기존의 창 보이기/숨기기 로직
|
// 기존의 창 보이기/숨기기 로직
|
||||||
if (askWindow.isVisible()) {
|
if (askWindow && askWindow.isVisible()) {
|
||||||
askWindow.webContents.send('window-hide-animation');
|
askWindow.webContents.send('window-hide-animation');
|
||||||
this.state.isVisible = false;
|
this.state.isVisible = false;
|
||||||
} else {
|
} else {
|
||||||
console.log('[AskService] Showing hidden Ask window');
|
console.log('[AskService] Showing hidden Ask window');
|
||||||
this.state.isVisible = true;
|
this.state.isVisible = true;
|
||||||
askWindow.show();
|
askWindow?.show();
|
||||||
updateLayout();
|
updateLayout();
|
||||||
askWindow.webContents.send('window-show-animation');
|
askWindow?.webContents.send('window-show-animation');
|
||||||
}
|
}
|
||||||
// 창이 다시 열릴 때를 대비해 상태를 초기화하고 전파합니다.
|
// 창이 다시 열릴 때를 대비해 상태를 초기화하고 전파합니다.
|
||||||
if (this.state.isVisible) {
|
if (this.state.isVisible) {
|
||||||
@ -182,6 +195,8 @@ class AskService {
|
|||||||
* @returns {Promise<{success: boolean, response?: string, error?: string}>}
|
* @returns {Promise<{success: boolean, response?: string, error?: string}>}
|
||||||
*/
|
*/
|
||||||
async sendMessage(userPrompt, conversationHistoryRaw=[]) {
|
async sendMessage(userPrompt, conversationHistoryRaw=[]) {
|
||||||
|
ensureAskWindowVisible();
|
||||||
|
|
||||||
if (this.abortController) {
|
if (this.abortController) {
|
||||||
this.abortController.abort('New request received.');
|
this.abortController.abort('New request received.');
|
||||||
}
|
}
|
||||||
@ -212,7 +227,7 @@ class AskService {
|
|||||||
await askRepository.addAiMessage({ sessionId, role: 'user', content: userPrompt.trim() });
|
await askRepository.addAiMessage({ sessionId, role: 'user', content: userPrompt.trim() });
|
||||||
console.log(`[AskService] DB: Saved user prompt to session ${sessionId}`);
|
console.log(`[AskService] DB: Saved user prompt to session ${sessionId}`);
|
||||||
|
|
||||||
const modelInfo = await getCurrentModelInfo(null, { type: 'llm' });
|
const modelInfo = modelStateService.getCurrentModelInfo('llm');
|
||||||
if (!modelInfo || !modelInfo.apiKey) {
|
if (!modelInfo || !modelInfo.apiKey) {
|
||||||
throw new Error('AI model or API key not configured.');
|
throw new Error('AI model or API key not configured.');
|
||||||
}
|
}
|
||||||
@ -252,7 +267,7 @@ class AskService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const response = await streamingLLM.streamChat(messages);
|
const response = await streamingLLM.streamChat(messages);
|
||||||
const askWin = windowPool.get('ask');
|
const askWin = getWindowPool()?.get('ask');
|
||||||
|
|
||||||
if (!askWin || askWin.isDestroyed()) {
|
if (!askWin || askWin.isDestroyed()) {
|
||||||
console.error("[AskService] Ask window is not available to send stream to.");
|
console.error("[AskService] Ask window is not available to send stream to.");
|
||||||
@ -351,11 +366,6 @@ class AskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStopScreenCapture() {
|
|
||||||
lastScreenshot = null;
|
|
||||||
console.log('[AskService] Stopped screen capture and cleared cache.');
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const askService = new AskService();
|
const askService = new AskService();
|
||||||
|
@ -82,7 +82,7 @@ async function createSTT({ apiKey, language = 'en', callbacks = {}, usePortkey =
|
|||||||
silence_duration_ms: 100,
|
silence_duration_ms: 100,
|
||||||
},
|
},
|
||||||
input_audio_noise_reduction: {
|
input_audio_noise_reduction: {
|
||||||
type: 'far_field'
|
type: 'near_field'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -359,6 +359,7 @@ class ModelStateService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeApiKey(provider) {
|
removeApiKey(provider) {
|
||||||
|
console.log(`[ModelStateService] Removing API key for provider: ${provider}`);
|
||||||
if (provider in this.state.apiKeys) {
|
if (provider in this.state.apiKeys) {
|
||||||
this.state.apiKeys[provider] = null;
|
this.state.apiKeys[provider] = null;
|
||||||
const llmProvider = this.getProviderForModel('llm', this.state.selectedModels.llm);
|
const llmProvider = this.getProviderForModel('llm', this.state.selectedModels.llm);
|
||||||
@ -542,6 +543,7 @@ class ModelStateService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleRemoveApiKey(provider) {
|
async handleRemoveApiKey(provider) {
|
||||||
|
console.log(`[ModelStateService] handleRemoveApiKey: ${provider}`);
|
||||||
const success = this.removeApiKey(provider);
|
const success = this.removeApiKey(provider);
|
||||||
if (success) {
|
if (success) {
|
||||||
const selectedModels = this.getSelectedModels();
|
const selectedModels = this.getSelectedModels();
|
||||||
|
119
src/features/common/services/permissionService.js
Normal file
119
src/features/common/services/permissionService.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
const { systemPreferences, shell, desktopCapturer } = require('electron');
|
||||||
|
const permissionRepository = require('../repositories/permission');
|
||||||
|
|
||||||
|
class PermissionService {
|
||||||
|
async checkSystemPermissions() {
|
||||||
|
const permissions = {
|
||||||
|
microphone: 'unknown',
|
||||||
|
screen: 'unknown',
|
||||||
|
needsSetup: true
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
const micStatus = systemPreferences.getMediaAccessStatus('microphone');
|
||||||
|
console.log('[Permissions] Microphone status:', micStatus);
|
||||||
|
permissions.microphone = micStatus;
|
||||||
|
|
||||||
|
const screenStatus = systemPreferences.getMediaAccessStatus('screen');
|
||||||
|
console.log('[Permissions] Screen status:', screenStatus);
|
||||||
|
permissions.screen = screenStatus;
|
||||||
|
|
||||||
|
permissions.needsSetup = micStatus !== 'granted' || screenStatus !== 'granted';
|
||||||
|
} else {
|
||||||
|
permissions.microphone = 'granted';
|
||||||
|
permissions.screen = 'granted';
|
||||||
|
permissions.needsSetup = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Permissions] System permissions status:', permissions);
|
||||||
|
return permissions;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Permissions] Error checking permissions:', error);
|
||||||
|
return {
|
||||||
|
microphone: 'unknown',
|
||||||
|
screen: 'unknown',
|
||||||
|
needsSetup: true,
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestMicrophonePermission() {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const status = systemPreferences.getMediaAccessStatus('microphone');
|
||||||
|
console.log('[Permissions] Microphone status:', status);
|
||||||
|
if (status === 'granted') {
|
||||||
|
return { success: true, status: 'granted' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const granted = await systemPreferences.askForMediaAccess('microphone');
|
||||||
|
return {
|
||||||
|
success: granted,
|
||||||
|
status: granted ? 'granted' : 'denied'
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Permissions] Error requesting microphone permission:', error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async openSystemPreferences(section) {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
return { success: false, error: 'Not supported on this platform' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (section === 'screen-recording') {
|
||||||
|
try {
|
||||||
|
console.log('[Permissions] Triggering screen capture request to register app...');
|
||||||
|
await desktopCapturer.getSources({
|
||||||
|
types: ['screen'],
|
||||||
|
thumbnailSize: { width: 1, height: 1 }
|
||||||
|
});
|
||||||
|
console.log('[Permissions] App registered for screen recording');
|
||||||
|
} catch (captureError) {
|
||||||
|
console.log('[Permissions] Screen capture request triggered (expected to fail):', captureError.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// await shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture');
|
||||||
|
}
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Permissions] Error opening system preferences:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async markPermissionsAsCompleted() {
|
||||||
|
try {
|
||||||
|
await permissionRepository.markPermissionsAsCompleted();
|
||||||
|
console.log('[Permissions] Marked permissions as completed');
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Permissions] Error marking permissions as completed:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkPermissionsCompleted() {
|
||||||
|
try {
|
||||||
|
const completed = await permissionRepository.checkPermissionsCompleted();
|
||||||
|
console.log('[Permissions] Permissions completed status:', completed);
|
||||||
|
return completed;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Permissions] Error checking permissions completed status:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissionService = new PermissionService();
|
||||||
|
module.exports = permissionService;
|
@ -1,6 +1,7 @@
|
|||||||
const { BrowserWindow } = require('electron');
|
const { BrowserWindow } = require('electron');
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
const { createSTT } = require('../../common/ai/factory');
|
const { createSTT } = require('../../common/ai/factory');
|
||||||
|
const modelStateService = require('../../common/services/modelStateService');
|
||||||
// const { getStoredApiKey, getStoredProvider, getCurrentModelInfo } = require('../../../window/windowManager');
|
// const { getStoredApiKey, getStoredProvider, getCurrentModelInfo } = require('../../../window/windowManager');
|
||||||
|
|
||||||
const COMPLETION_DEBOUNCE_MS = 2000;
|
const COMPLETION_DEBOUNCE_MS = 2000;
|
||||||
@ -131,8 +132,7 @@ class SttService {
|
|||||||
async initializeSttSessions(language = 'en') {
|
async initializeSttSessions(language = 'en') {
|
||||||
const effectiveLanguage = process.env.OPENAI_TRANSCRIBE_LANG || language || 'en';
|
const effectiveLanguage = process.env.OPENAI_TRANSCRIBE_LANG || language || 'en';
|
||||||
|
|
||||||
const { getCurrentModelInfo } = require('../../../window/windowManager');
|
const modelInfo = modelStateService.getCurrentModelInfo('stt');
|
||||||
const modelInfo = await getCurrentModelInfo(null, { type: 'stt' });
|
|
||||||
if (!modelInfo || !modelInfo.apiKey) {
|
if (!modelInfo || !modelInfo.apiKey) {
|
||||||
throw new Error('AI model or API key is not configured.');
|
throw new Error('AI model or API key is not configured.');
|
||||||
}
|
}
|
||||||
@ -144,6 +144,7 @@ class SttService {
|
|||||||
console.log('[SttService] Ignoring message - session already closed');
|
console.log('[SttService] Ignoring message - session already closed');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log('[SttService] handleMyMessage', message);
|
||||||
|
|
||||||
if (this.modelInfo.provider === 'whisper') {
|
if (this.modelInfo.provider === 'whisper') {
|
||||||
// Whisper STT emits 'transcription' events with different structure
|
// Whisper STT emits 'transcription' events with different structure
|
||||||
@ -411,8 +412,7 @@ class SttService {
|
|||||||
let modelInfo = this.modelInfo;
|
let modelInfo = this.modelInfo;
|
||||||
if (!modelInfo) {
|
if (!modelInfo) {
|
||||||
console.warn('[SttService] modelInfo not found, fetching on-the-fly as a fallback...');
|
console.warn('[SttService] modelInfo not found, fetching on-the-fly as a fallback...');
|
||||||
const { getCurrentModelInfo } = require('../../../window/windowManager');
|
modelInfo = modelStateService.getCurrentModelInfo('stt');
|
||||||
modelInfo = await getCurrentModelInfo(null, { type: 'stt' });
|
|
||||||
}
|
}
|
||||||
if (!modelInfo) {
|
if (!modelInfo) {
|
||||||
throw new Error('STT model info could not be retrieved.');
|
throw new Error('STT model info could not be retrieved.');
|
||||||
@ -433,8 +433,7 @@ class SttService {
|
|||||||
let modelInfo = this.modelInfo;
|
let modelInfo = this.modelInfo;
|
||||||
if (!modelInfo) {
|
if (!modelInfo) {
|
||||||
console.warn('[SttService] modelInfo not found, fetching on-the-fly as a fallback...');
|
console.warn('[SttService] modelInfo not found, fetching on-the-fly as a fallback...');
|
||||||
const { getCurrentModelInfo } = require('../../../window/windowManager');
|
modelInfo = modelStateService.getCurrentModelInfo('stt');
|
||||||
modelInfo = await getCurrentModelInfo(null, { type: 'stt' });
|
|
||||||
}
|
}
|
||||||
if (!modelInfo) {
|
if (!modelInfo) {
|
||||||
throw new Error('STT model info could not be retrieved.');
|
throw new Error('STT model info could not be retrieved.');
|
||||||
@ -515,8 +514,7 @@ class SttService {
|
|||||||
let modelInfo = this.modelInfo;
|
let modelInfo = this.modelInfo;
|
||||||
if (!modelInfo) {
|
if (!modelInfo) {
|
||||||
console.warn('[SttService] modelInfo not found, fetching on-the-fly as a fallback...');
|
console.warn('[SttService] modelInfo not found, fetching on-the-fly as a fallback...');
|
||||||
const { getCurrentModelInfo } = require('../../../window/windowManager');
|
modelInfo = modelStateService.getCurrentModelInfo('stt');
|
||||||
modelInfo = await getCurrentModelInfo(null, { type: 'stt' });
|
|
||||||
}
|
}
|
||||||
if (!modelInfo) {
|
if (!modelInfo) {
|
||||||
throw new Error('STT model info could not be retrieved.');
|
throw new Error('STT model info could not be retrieved.');
|
||||||
|
@ -3,6 +3,7 @@ const { getSystemPrompt } = require('../../common/prompts/promptBuilder.js');
|
|||||||
const { createLLM } = require('../../common/ai/factory');
|
const { createLLM } = require('../../common/ai/factory');
|
||||||
const sessionRepository = require('../../common/repositories/session');
|
const sessionRepository = require('../../common/repositories/session');
|
||||||
const summaryRepository = require('./repositories');
|
const summaryRepository = require('./repositories');
|
||||||
|
const modelStateService = require('../../common/services/modelStateService');
|
||||||
// const { getStoredApiKey, getStoredProvider, getCurrentModelInfo } = require('../../../window/windowManager.js');
|
// const { getStoredApiKey, getStoredProvider, getCurrentModelInfo } = require('../../../window/windowManager.js');
|
||||||
|
|
||||||
class SummaryService {
|
class SummaryService {
|
||||||
@ -97,8 +98,7 @@ Please build upon this context while analyzing the new conversation segments.
|
|||||||
await sessionRepository.touch(this.currentSessionId);
|
await sessionRepository.touch(this.currentSessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { getCurrentModelInfo } = require('../../../window/windowManager');
|
const modelInfo = modelStateService.getCurrentModelInfo('llm');
|
||||||
const modelInfo = await getCurrentModelInfo(null, { type: 'llm' });
|
|
||||||
if (!modelInfo || !modelInfo.apiKey) {
|
if (!modelInfo || !modelInfo.apiKey) {
|
||||||
throw new Error('AI model or API key is not configured.');
|
throw new Error('AI model or API key is not configured.');
|
||||||
}
|
}
|
||||||
|
@ -374,6 +374,7 @@ async function removeApiKey() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('[SettingsService] API key removed for all providers');
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[SettingsService] Error removing API key:', error);
|
console.error('[SettingsService] Error removing API key:', error);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const { globalShortcut, screen } = require('electron');
|
const { globalShortcut, screen } = require('electron');
|
||||||
const shortcutsRepository = require('./repositories');
|
const shortcutsRepository = require('./repositories');
|
||||||
const internalBridge = require('../../bridge/internalBridge');
|
const internalBridge = require('../../bridge/internalBridge');
|
||||||
|
const askService = require('../ask/askService');
|
||||||
|
|
||||||
|
|
||||||
class ShortcutsService {
|
class ShortcutsService {
|
||||||
@ -210,8 +211,7 @@ class ShortcutsService {
|
|||||||
callback = () => this.toggleAllWindowsVisibility(this.windowPool);
|
callback = () => this.toggleAllWindowsVisibility(this.windowPool);
|
||||||
break;
|
break;
|
||||||
case 'nextStep':
|
case 'nextStep':
|
||||||
// Late require to prevent circular dependency
|
callback = () => askService.toggleAskButton();
|
||||||
callback = () => require('../../window/windowManager').toggleFeature('ask', {ask: { targetVisibility: 'show' }});
|
|
||||||
break;
|
break;
|
||||||
case 'scrollUp':
|
case 'scrollUp':
|
||||||
callback = () => {
|
callback = () => {
|
||||||
|
@ -276,12 +276,6 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
startMacosSystemAudio: () => ipcRenderer.invoke('listen:startMacosSystemAudio'),
|
startMacosSystemAudio: () => ipcRenderer.invoke('listen:startMacosSystemAudio'),
|
||||||
stopMacosSystemAudio: () => ipcRenderer.invoke('listen:stopMacosSystemAudio'),
|
stopMacosSystemAudio: () => ipcRenderer.invoke('listen:stopMacosSystemAudio'),
|
||||||
|
|
||||||
// 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
|
// Session Management
|
||||||
isSessionActive: () => ipcRenderer.invoke('is-session-active'),
|
isSessionActive: () => ipcRenderer.invoke('is-session-active'),
|
||||||
|
|
||||||
|
@ -38,13 +38,10 @@ const isMacOS = window.api.platform.isMacOS;
|
|||||||
|
|
||||||
let mediaStream = null;
|
let mediaStream = null;
|
||||||
let micMediaStream = null;
|
let micMediaStream = null;
|
||||||
let screenshotInterval = null;
|
|
||||||
let audioContext = null;
|
let audioContext = null;
|
||||||
let audioProcessor = null;
|
let audioProcessor = null;
|
||||||
let systemAudioContext = null;
|
let systemAudioContext = null;
|
||||||
let systemAudioProcessor = null;
|
let systemAudioProcessor = null;
|
||||||
let currentImageQuality = 'medium';
|
|
||||||
let lastScreenshotBase64 = null;
|
|
||||||
|
|
||||||
let systemAudioBuffer = [];
|
let systemAudioBuffer = [];
|
||||||
const MAX_SYSTEM_BUFFER_SIZE = 10;
|
const MAX_SYSTEM_BUFFER_SIZE = 10;
|
||||||
@ -140,10 +137,6 @@ function runAecSync(micF32, sysF32) {
|
|||||||
return micF32;
|
return micF32;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
|
|
||||||
// 새로운 프레임 단위 처리 로직
|
|
||||||
// ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
|
|
||||||
|
|
||||||
const frameSize = 160; // AEC 모듈 초기화 시 설정한 프레임 크기
|
const frameSize = 160; // AEC 모듈 초기화 시 설정한 프레임 크기
|
||||||
const numFrames = Math.floor(micF32.length / frameSize);
|
const numFrames = Math.floor(micF32.length / frameSize);
|
||||||
|
|
||||||
@ -418,94 +411,10 @@ function setupSystemAudioProcessing(systemStream) {
|
|||||||
return { context: systemAudioContext, processor: systemProcessor };
|
return { context: systemAudioContext, processor: systemProcessor };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// Screenshot functions (exact from renderer.js)
|
|
||||||
// ---------------------------
|
|
||||||
async function captureScreenshot(imageQuality = 'medium', isManual = false) {
|
|
||||||
console.log(`Capturing ${isManual ? 'manual' : 'automated'} screenshot...`);
|
|
||||||
|
|
||||||
// Check rate limiting for automated screenshots only
|
|
||||||
if (!isManual && tokenTracker.shouldThrottle()) {
|
|
||||||
console.log('Automated screenshot skipped due to rate limiting');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Request screenshot from main process
|
|
||||||
const result = await window.api.listenCapture.captureScreenshot({
|
|
||||||
quality: imageQuality,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.success && result.base64) {
|
|
||||||
// Store the latest screenshot
|
|
||||||
lastScreenshotBase64 = result.base64;
|
|
||||||
|
|
||||||
// Note: sendResult is not defined in the original, this was likely an error
|
|
||||||
// Commenting out this section as it references undefined variable
|
|
||||||
/*
|
|
||||||
if (sendResult.success) {
|
|
||||||
// Track image tokens after successful send
|
|
||||||
const imageTokens = tokenTracker.calculateImageTokens(result.width || 1920, result.height || 1080);
|
|
||||||
tokenTracker.addTokens(imageTokens, 'image');
|
|
||||||
console.log(`📊 Image sent successfully - ${imageTokens} tokens used (${result.width}x${result.height})`);
|
|
||||||
} else {
|
|
||||||
console.error('Failed to send image:', sendResult.error);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
} else {
|
|
||||||
console.error('Failed to capture screenshot:', result.error);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error capturing screenshot:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function captureManualScreenshot(imageQuality = null) {
|
|
||||||
console.log('Manual screenshot triggered');
|
|
||||||
const quality = imageQuality || currentImageQuality;
|
|
||||||
await captureScreenshot(quality, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getCurrentScreenshot() {
|
|
||||||
try {
|
|
||||||
// First try to get a fresh screenshot from main process
|
|
||||||
const result = await window.api.listenCapture.getCurrentScreenshot();
|
|
||||||
|
|
||||||
if (result.success && result.base64) {
|
|
||||||
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 window.api.listenCapture.captureScreenshot({
|
|
||||||
quality: currentImageQuality,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (captureResult.success && captureResult.base64) {
|
|
||||||
lastScreenshotBase64 = captureResult.base64;
|
|
||||||
return captureResult.base64;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to last stored screenshot
|
|
||||||
if (lastScreenshotBase64) {
|
|
||||||
console.log('Using cached screenshot');
|
|
||||||
return lastScreenshotBase64;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Failed to get screenshot');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting current screenshot:', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// Main capture functions (exact from renderer.js)
|
// Main capture functions (exact from renderer.js)
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'medium') {
|
async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'medium') {
|
||||||
// Store the image quality for manual screenshots
|
|
||||||
currentImageQuality = imageQuality;
|
|
||||||
|
|
||||||
// Reset token tracker when starting new capture session
|
// Reset token tracker when starting new capture session
|
||||||
tokenTracker.reset();
|
tokenTracker.reset();
|
||||||
@ -534,13 +443,6 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize screen capture in main process
|
|
||||||
const screenResult = await window.api.listenCapture.startScreenCapture();
|
|
||||||
if (!screenResult.success) {
|
|
||||||
throw new Error('Failed to start screen capture: ' + screenResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
micMediaStream = await navigator.mediaDevices.getUserMedia({
|
micMediaStream = await navigator.mediaDevices.getUserMedia({
|
||||||
audio: {
|
audio: {
|
||||||
@ -602,12 +504,6 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
|
|||||||
// Windows - capture mic and system audio separately using native loopback
|
// Windows - capture mic and system audio separately using native loopback
|
||||||
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
|
|
||||||
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
|
// Ensure STT sessions are initialized before starting audio capture
|
||||||
const sessionActive = await window.api.listenCapture.isSessionActive();
|
const sessionActive = await window.api.listenCapture.isSessionActive();
|
||||||
if (!sessionActive) {
|
if (!sessionActive) {
|
||||||
@ -656,20 +552,6 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
|
|||||||
// Continue without system audio
|
// Continue without system audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start capturing screenshots - check if manual mode
|
|
||||||
if (screenshotIntervalSeconds === 'manual' || screenshotIntervalSeconds === 'Manual') {
|
|
||||||
console.log('Manual mode enabled - screenshots will be captured on demand only');
|
|
||||||
// Don't start automatic capture in manual mode
|
|
||||||
} else {
|
|
||||||
// 스크린샷 기능 활성화 (chatModel에서 사용)
|
|
||||||
const intervalMilliseconds = parseInt(screenshotIntervalSeconds) * 1000;
|
|
||||||
screenshotInterval = setInterval(() => captureScreenshot(imageQuality), intervalMilliseconds);
|
|
||||||
|
|
||||||
// Capture first screenshot immediately
|
|
||||||
setTimeout(() => captureScreenshot(imageQuality), 100);
|
|
||||||
console.log(`📸 Screenshot capture enabled with ${screenshotIntervalSeconds}s interval`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error starting capture:', err);
|
console.error('Error starting capture:', err);
|
||||||
// Note: pickleGlass.e() is not available in this context, commenting out
|
// Note: pickleGlass.e() is not available in this context, commenting out
|
||||||
@ -678,11 +560,6 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
|
|||||||
}
|
}
|
||||||
|
|
||||||
function stopCapture() {
|
function stopCapture() {
|
||||||
if (screenshotInterval) {
|
|
||||||
clearInterval(screenshotInterval);
|
|
||||||
screenshotInterval = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up microphone resources
|
// Clean up microphone resources
|
||||||
if (audioProcessor) {
|
if (audioProcessor) {
|
||||||
audioProcessor.disconnect();
|
audioProcessor.disconnect();
|
||||||
@ -713,11 +590,6 @@ function stopCapture() {
|
|||||||
micMediaStream = null;
|
micMediaStream = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop screen capture in main process
|
|
||||||
window.api.listenCapture.stopScreenCapture().catch(err => {
|
|
||||||
console.error('Error stopping screen capture:', err);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Stop macOS audio capture if running
|
// Stop macOS audio capture if running
|
||||||
if (isMacOS) {
|
if (isMacOS) {
|
||||||
window.api.listenCapture.stopMacosSystemAudio().catch(err => {
|
window.api.listenCapture.stopMacosSystemAudio().catch(err => {
|
||||||
@ -735,19 +607,14 @@ module.exports = {
|
|||||||
disposeAec, // 필요시 Rust 객체 파괴
|
disposeAec, // 필요시 Rust 객체 파괴
|
||||||
startCapture,
|
startCapture,
|
||||||
stopCapture,
|
stopCapture,
|
||||||
captureManualScreenshot,
|
|
||||||
getCurrentScreenshot,
|
|
||||||
isLinux,
|
isLinux,
|
||||||
isMacOS,
|
isMacOS,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Expose functions to global scope for external access (exact from renderer.js)
|
// Expose functions to global scope for external access (exact from renderer.js)
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.captureManualScreenshot = captureManualScreenshot;
|
|
||||||
window.listenCapture = module.exports;
|
window.listenCapture = module.exports;
|
||||||
window.pickleGlass = window.pickleGlass || {};
|
window.pickleGlass = window.pickleGlass || {};
|
||||||
window.pickleGlass.startCapture = startCapture;
|
window.pickleGlass.startCapture = startCapture;
|
||||||
window.pickleGlass.stopCapture = stopCapture;
|
window.pickleGlass.stopCapture = stopCapture;
|
||||||
window.pickleGlass.captureManualScreenshot = captureManualScreenshot;
|
|
||||||
window.pickleGlass.getCurrentScreenshot = getCurrentScreenshot;
|
|
||||||
}
|
}
|
@ -693,6 +693,7 @@ export class SettingsView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleClearKey(provider) {
|
async handleClearKey(provider) {
|
||||||
|
console.log(`[SettingsView] handleClearKey: ${provider}`);
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
await window.api.settingsView.removeApiKey(provider);
|
await window.api.settingsView.removeApiKey(provider);
|
||||||
this.apiKeys = { ...this.apiKeys, [provider]: '' };
|
this.apiKeys = { ...this.apiKeys, [provider]: '' };
|
||||||
@ -1097,13 +1098,6 @@ export class SettingsView extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleClearApiKey() {
|
|
||||||
console.log('Clear API Key clicked');
|
|
||||||
await window.api.settingsView.removeApiKey();
|
|
||||||
this.apiKey = null;
|
|
||||||
this.requestUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleQuit() {
|
handleQuit() {
|
||||||
console.log('Quit clicked');
|
console.log('Quit clicked');
|
||||||
window.api.settingsView.quitApplication();
|
window.api.settingsView.quitApplication();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const { BrowserWindow, globalShortcut, ipcMain, screen, app, shell, desktopCapturer } = require('electron');
|
const { BrowserWindow, globalShortcut, screen, app, shell } = require('electron');
|
||||||
const WindowLayoutManager = require('./windowLayoutManager');
|
const WindowLayoutManager = require('./windowLayoutManager');
|
||||||
const SmoothMovementManager = require('./smoothMovementManager');
|
const SmoothMovementManager = require('./smoothMovementManager');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
@ -570,10 +570,7 @@ function createWindows() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupIpcHandlers(movementManager) {
|
function setupIpcHandlers(movementManager) {
|
||||||
setupApiKeyIPC();
|
|
||||||
|
|
||||||
// quit-application handler moved to windowBridge.js to avoid duplication
|
// quit-application handler moved to windowBridge.js to avoid duplication
|
||||||
|
|
||||||
screen.on('display-added', (event, newDisplay) => {
|
screen.on('display-added', (event, newDisplay) => {
|
||||||
console.log('[Display] New display added:', newDisplay.id);
|
console.log('[Display] New display added:', newDisplay.id);
|
||||||
});
|
});
|
||||||
@ -591,10 +588,9 @@ function setupIpcHandlers(movementManager) {
|
|||||||
// console.log('[Display] Display metrics changed:', display.id, changedMetrics);
|
// console.log('[Display] Display metrics changed:', display.id, changedMetrics);
|
||||||
updateLayout();
|
updateLayout();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Content protection handlers moved to windowBridge.js to avoid duplication
|
const handleHeaderStateChanged = (state) => {
|
||||||
|
|
||||||
ipcMain.on('header-state-changed', (event, state) => {
|
|
||||||
console.log(`[WindowManager] Header state changed to: ${state}`);
|
console.log(`[WindowManager] Header state changed to: ${state}`);
|
||||||
currentHeaderState = state;
|
currentHeaderState = state;
|
||||||
|
|
||||||
@ -604,11 +600,9 @@ function setupIpcHandlers(movementManager) {
|
|||||||
destroyFeatureWindows();
|
destroyFeatureWindows();
|
||||||
}
|
}
|
||||||
internalBridge.emit('reregister-shortcuts');
|
internalBridge.emit('reregister-shortcuts');
|
||||||
});
|
};
|
||||||
|
|
||||||
// resize-header-window handler moved to windowBridge.js to avoid duplication
|
const handleHeaderAnimationFinished = (state) => {
|
||||||
|
|
||||||
ipcMain.on('header-animation-finished', (event, state) => {
|
|
||||||
const header = windowPool.get('header');
|
const header = windowPool.get('header');
|
||||||
if (!header || header.isDestroyed()) return;
|
if (!header || header.isDestroyed()) return;
|
||||||
|
|
||||||
@ -619,47 +613,42 @@ function setupIpcHandlers(movementManager) {
|
|||||||
console.log('[WindowManager] Header shown after animation.');
|
console.log('[WindowManager] Header shown after animation.');
|
||||||
updateLayout();
|
updateLayout();
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
ipcMain.handle('get-header-position', () => {
|
const getHeaderPosition = () => {
|
||||||
const header = windowPool.get('header');
|
const header = windowPool.get('header');
|
||||||
if (header) {
|
if (header) {
|
||||||
const [x, y] = header.getPosition();
|
const [x, y] = header.getPosition();
|
||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
return { x: 0, y: 0 };
|
return { x: 0, y: 0 };
|
||||||
});
|
};
|
||||||
|
|
||||||
ipcMain.handle('move-header', (event, newX, newY) => {
|
const moveHeader = (newX, newY) => {
|
||||||
const header = windowPool.get('header');
|
const header = windowPool.get('header');
|
||||||
if (header) {
|
if (header) {
|
||||||
const currentY = newY !== undefined ? newY : header.getBounds().y;
|
const currentY = newY !== undefined ? newY : header.getBounds().y;
|
||||||
header.setPosition(newX, currentY, false);
|
header.setPosition(newX, currentY, false);
|
||||||
|
|
||||||
updateLayout();
|
updateLayout();
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
ipcMain.handle('move-header-to', (event, newX, newY) => {
|
const moveHeaderTo = (newX, newY) => {
|
||||||
const header = windowPool.get('header');
|
const header = windowPool.get('header');
|
||||||
if (header) {
|
if (header) {
|
||||||
const targetDisplay = screen.getDisplayNearestPoint({ x: newX, y: newY });
|
const targetDisplay = screen.getDisplayNearestPoint({ x: newX, y: newY });
|
||||||
const { x: workAreaX, y: workAreaY, width, height } = targetDisplay.workArea;
|
const { x: workAreaX, y: workAreaY, width, height } = targetDisplay.workArea;
|
||||||
const headerBounds = header.getBounds();
|
const headerBounds = header.getBounds();
|
||||||
|
|
||||||
// Only clamp if the new position would actually go out of bounds
|
|
||||||
// This prevents progressive restriction of movement
|
|
||||||
let clampedX = newX;
|
let clampedX = newX;
|
||||||
let clampedY = newY;
|
let clampedY = newY;
|
||||||
|
|
||||||
// Check if we need to clamp X position
|
|
||||||
if (newX < workAreaX) {
|
if (newX < workAreaX) {
|
||||||
clampedX = workAreaX;
|
clampedX = workAreaX;
|
||||||
} else if (newX + headerBounds.width > workAreaX + width) {
|
} else if (newX + headerBounds.width > workAreaX + width) {
|
||||||
clampedX = workAreaX + width - headerBounds.width;
|
clampedX = workAreaX + width - headerBounds.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we need to clamp Y position
|
|
||||||
if (newY < workAreaY) {
|
if (newY < workAreaY) {
|
||||||
clampedY = workAreaY;
|
clampedY = workAreaY;
|
||||||
} else if (newY + headerBounds.height > workAreaY + height) {
|
} else if (newY + headerBounds.height > workAreaY + height) {
|
||||||
@ -667,16 +656,12 @@ function setupIpcHandlers(movementManager) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
header.setPosition(clampedX, clampedY, false);
|
header.setPosition(clampedX, clampedY, false);
|
||||||
|
|
||||||
updateLayout();
|
updateLayout();
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const adjustWindowHeight = (sender, targetHeight) => {
|
||||||
// move-window-step handler moved to windowBridge.js to avoid duplication
|
const senderWindow = BrowserWindow.fromWebContents(sender);
|
||||||
|
|
||||||
ipcMain.handle('adjust-window-height', (event, targetHeight) => {
|
|
||||||
const senderWindow = BrowserWindow.fromWebContents(event.sender);
|
|
||||||
if (senderWindow) {
|
if (senderWindow) {
|
||||||
const wasResizable = senderWindow.isResizable();
|
const wasResizable = senderWindow.isResizable();
|
||||||
if (!wasResizable) {
|
if (!wasResizable) {
|
||||||
@ -702,276 +687,47 @@ function setupIpcHandlers(movementManager) {
|
|||||||
|
|
||||||
updateLayout();
|
updateLayout();
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
ipcMain.handle('check-system-permissions', async () => {
|
const handleAnimationFinished = (sender) => {
|
||||||
const { systemPreferences } = require('electron');
|
const win = BrowserWindow.fromWebContents(sender);
|
||||||
const permissions = {
|
|
||||||
microphone: 'unknown',
|
|
||||||
screen: 'unknown',
|
|
||||||
needsSetup: true
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
// Check microphone permission on macOS
|
|
||||||
const micStatus = systemPreferences.getMediaAccessStatus('microphone');
|
|
||||||
console.log('[Permissions] Microphone status:', micStatus);
|
|
||||||
permissions.microphone = micStatus;
|
|
||||||
|
|
||||||
// Check screen recording permission using the system API
|
|
||||||
const screenStatus = systemPreferences.getMediaAccessStatus('screen');
|
|
||||||
console.log('[Permissions] Screen status:', screenStatus);
|
|
||||||
permissions.screen = screenStatus;
|
|
||||||
|
|
||||||
permissions.needsSetup = micStatus !== 'granted' || screenStatus !== 'granted';
|
|
||||||
} else {
|
|
||||||
permissions.microphone = 'granted';
|
|
||||||
permissions.screen = 'granted';
|
|
||||||
permissions.needsSetup = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[Permissions] System permissions status:', permissions);
|
|
||||||
return permissions;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Permissions] Error checking permissions:', error);
|
|
||||||
return {
|
|
||||||
microphone: 'unknown',
|
|
||||||
screen: 'unknown',
|
|
||||||
needsSetup: true,
|
|
||||||
error: error.message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('request-microphone-permission', async () => {
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
const { systemPreferences } = require('electron');
|
|
||||||
try {
|
|
||||||
const status = systemPreferences.getMediaAccessStatus('microphone');
|
|
||||||
console.log('[Permissions] Microphone status:', status);
|
|
||||||
if (status === 'granted') {
|
|
||||||
return { success: true, status: 'granted' };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Req mic permission
|
|
||||||
const granted = await systemPreferences.askForMediaAccess('microphone');
|
|
||||||
return {
|
|
||||||
success: granted,
|
|
||||||
status: granted ? 'granted' : 'denied'
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Permissions] Error requesting microphone permission:', error);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error.message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('open-system-preferences', async (event, section) => {
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
return { success: false, error: 'Not supported on this platform' };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (section === 'screen-recording') {
|
|
||||||
// First trigger screen capture request to register the app in system preferences
|
|
||||||
try {
|
|
||||||
console.log('[Permissions] Triggering screen capture request to register app...');
|
|
||||||
await desktopCapturer.getSources({
|
|
||||||
types: ['screen'],
|
|
||||||
thumbnailSize: { width: 1, height: 1 }
|
|
||||||
});
|
|
||||||
console.log('[Permissions] App registered for screen recording');
|
|
||||||
} catch (captureError) {
|
|
||||||
console.log('[Permissions] Screen capture request triggered (expected to fail):', captureError.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then open system preferences
|
|
||||||
// await shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture');
|
|
||||||
}
|
|
||||||
// if (section === 'microphone') {
|
|
||||||
// await shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone');
|
|
||||||
// }
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Permissions] Error opening system preferences:', error);
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('mark-permissions-completed', async () => {
|
|
||||||
try {
|
|
||||||
// This is a system-level setting, not user-specific.
|
|
||||||
await permissionRepository.markPermissionsAsCompleted();
|
|
||||||
console.log('[Permissions] Marked permissions as completed');
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Permissions] Error marking permissions as completed:', error);
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('check-permissions-completed', async () => {
|
|
||||||
try {
|
|
||||||
const completed = await permissionRepository.checkPermissionsCompleted();
|
|
||||||
console.log('[Permissions] Permissions completed status:', completed);
|
|
||||||
return completed;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Permissions] Error checking permissions completed status:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('toggle-all-windows-visibility', () => toggleAllWindowsVisibility());
|
|
||||||
|
|
||||||
// ipcMain.handle('toggle-feature', async (event, featureName) => {
|
|
||||||
// return toggleFeature(featureName);
|
|
||||||
// });
|
|
||||||
|
|
||||||
ipcMain.on('animation-finished', (event) => {
|
|
||||||
const win = BrowserWindow.fromWebContents(event.sender);
|
|
||||||
if (win && !win.isDestroyed()) {
|
if (win && !win.isDestroyed()) {
|
||||||
console.log(`[WindowManager] Hiding window after animation.`);
|
console.log(`[WindowManager] Hiding window after animation.`);
|
||||||
win.hide();
|
win.hide();
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const closeAskWindow = () => {
|
||||||
ipcMain.handle('ask:closeAskWindow', async () => {
|
|
||||||
const askWindow = windowPool.get('ask');
|
const askWindow = windowPool.get('ask');
|
||||||
if (askWindow) {
|
if (askWindow) {
|
||||||
askWindow.webContents.send('window-hide-animation');
|
askWindow.webContents.send('window-hide-animation');
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
async function ensureAskWindowVisible() {
|
||||||
// /**
|
if (currentHeaderState !== 'main') {
|
||||||
// *
|
console.log('[WindowManager] Not in main state, skipping ensureAskWindowVisible');
|
||||||
// * @param {'listen'|'ask'|'settings'} featureName
|
return;
|
||||||
// * @param {{
|
|
||||||
// * listen?: { targetVisibility?: 'show'|'hide' },
|
|
||||||
// * ask?: { targetVisibility?: 'show'|'hide', questionText?: string },
|
|
||||||
// * settings?: { targetVisibility?: 'show'|'hide' }
|
|
||||||
// * }} [options={}]
|
|
||||||
// */
|
|
||||||
// async function toggleFeature(featureName, options = {}) {
|
|
||||||
// if (!windowPool.get(featureName) && currentHeaderState === 'main') {
|
|
||||||
// createFeatureWindows(windowPool.get('header'));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (featureName === 'ask') {
|
|
||||||
// let askWindow = windowPool.get('ask');
|
|
||||||
|
|
||||||
// if (!askWindow || askWindow.isDestroyed()) {
|
|
||||||
// console.log('[WindowManager] Ask window not found, creating new one');
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const questionText = options?.ask?.questionText ?? null;
|
|
||||||
// const targetVisibility = options?.ask?.targetVisibility ?? null;
|
|
||||||
// if (askWindow.isVisible()) {
|
|
||||||
// if (questionText) {
|
|
||||||
// askWindow.webContents.send('ask:sendQuestionToRenderer', questionText);
|
|
||||||
// } else {
|
|
||||||
// updateLayout();
|
|
||||||
// if (targetVisibility === 'show') {
|
|
||||||
// askWindow.webContents.send('ask:showTextInput');
|
|
||||||
// } else {
|
|
||||||
// askWindow.webContents.send('window-hide-animation');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// console.log('[WindowManager] Showing hidden Ask window');
|
|
||||||
// askWindow.show();
|
|
||||||
// updateLayout();
|
|
||||||
// if (questionText) {
|
|
||||||
// askWindow.webContents.send('ask:sendQuestionToRenderer', questionText);
|
|
||||||
// }
|
|
||||||
// askWindow.webContents.send('window-show-animation');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
async function toggleFeature(featureName, options = {}) {
|
|
||||||
if (!windowPool.get(featureName) && currentHeaderState === 'main') {
|
|
||||||
createFeatureWindows(windowPool.get('header'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (featureName === 'ask') {
|
|
||||||
let askWindow = windowPool.get('ask');
|
let askWindow = windowPool.get('ask');
|
||||||
|
|
||||||
if (!askWindow || askWindow.isDestroyed()) {
|
if (!askWindow || askWindow.isDestroyed()) {
|
||||||
console.log('[WindowManager] Ask window not found, creating new one');
|
console.log('[WindowManager] Ask window not found, creating new one');
|
||||||
return;
|
createFeatureWindows(windowPool.get('header'), 'ask');
|
||||||
|
askWindow = windowPool.get('ask');
|
||||||
}
|
}
|
||||||
if (askWindow.isVisible()) {
|
|
||||||
askWindow.webContents.send('ask:showTextInput');
|
if (!askWindow.isVisible()) {
|
||||||
} else {
|
|
||||||
console.log('[WindowManager] Showing hidden Ask window');
|
console.log('[WindowManager] Showing hidden Ask window');
|
||||||
askWindow.show();
|
askWindow.show();
|
||||||
updateLayout();
|
updateLayout();
|
||||||
askWindow.webContents.send('window-show-animation');
|
askWindow.webContents.send('window-show-animation');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//////// after_modelStateService ////////
|
//////// after_modelStateService ////////
|
||||||
async function getStoredApiKey() {
|
|
||||||
if (global.modelStateService) {
|
|
||||||
const provider = await getStoredProvider();
|
|
||||||
return global.modelStateService.getApiKey(provider);
|
|
||||||
}
|
|
||||||
return null; // Fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getStoredProvider() {
|
|
||||||
if (global.modelStateService) {
|
|
||||||
return global.modelStateService.getCurrentProvider('llm');
|
|
||||||
}
|
|
||||||
return 'openai'; // Fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {IpcMainInvokeEvent} event
|
|
||||||
* @param {{type: 'llm' | 'stt'}}
|
|
||||||
*/
|
|
||||||
async function getCurrentModelInfo(event, { type }) {
|
|
||||||
if (global.modelStateService && (type === 'llm' || type === 'stt')) {
|
|
||||||
return global.modelStateService.getCurrentModelInfo(type);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupApiKeyIPC() {
|
|
||||||
const { ipcMain } = require('electron');
|
|
||||||
|
|
||||||
ipcMain.handle('get-stored-api-key', getStoredApiKey);
|
|
||||||
ipcMain.handle('get-ai-provider', getStoredProvider);
|
|
||||||
ipcMain.handle('get-current-model-info', getCurrentModelInfo);
|
|
||||||
|
|
||||||
ipcMain.handle('api-key-validated', async (event, data) => {
|
|
||||||
console.warn("[DEPRECATED] 'api-key-validated' IPC was called. This logic is now handled by 'model:validate-key'.");
|
|
||||||
return { success: true };
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('remove-api-key', async () => {
|
|
||||||
console.warn("[DEPRECATED] 'remove-api-key' IPC was called. This is now handled by 'model:remove-api-key'.");
|
|
||||||
return { success: true };
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('[WindowManager] API key related IPC handlers have been updated for ModelStateService.');
|
|
||||||
}
|
|
||||||
//////// after_modelStateService ////////
|
|
||||||
|
|
||||||
|
|
||||||
const closeWindow = (windowName) => {
|
const closeWindow = (windowName) => {
|
||||||
const win = windowPool.get(windowName);
|
const win = windowPool.get(windowName);
|
||||||
@ -985,10 +741,6 @@ module.exports = {
|
|||||||
createWindows,
|
createWindows,
|
||||||
windowPool,
|
windowPool,
|
||||||
fixedYPosition,
|
fixedYPosition,
|
||||||
getStoredApiKey,
|
|
||||||
getStoredProvider,
|
|
||||||
getCurrentModelInfo,
|
|
||||||
toggleFeature, // Export toggleFeature so shortcutsService can use it
|
|
||||||
toggleContentProtection,
|
toggleContentProtection,
|
||||||
resizeHeaderWindow,
|
resizeHeaderWindow,
|
||||||
getContentProtectionStatus,
|
getContentProtectionStatus,
|
||||||
@ -999,4 +751,14 @@ module.exports = {
|
|||||||
openLoginPage,
|
openLoginPage,
|
||||||
moveWindowStep,
|
moveWindowStep,
|
||||||
closeWindow,
|
closeWindow,
|
||||||
|
toggleAllWindowsVisibility,
|
||||||
|
handleHeaderStateChanged,
|
||||||
|
handleHeaderAnimationFinished,
|
||||||
|
getHeaderPosition,
|
||||||
|
moveHeader,
|
||||||
|
moveHeaderTo,
|
||||||
|
adjustWindowHeight,
|
||||||
|
handleAnimationFinished,
|
||||||
|
closeAskWindow,
|
||||||
|
ensureAskWindowVisible,
|
||||||
};
|
};
|
Loading…
x
Reference in New Issue
Block a user