Compare commits
14 Commits
main
...
refactor/0
Author | SHA1 | Date | |
---|---|---|---|
|
d18c4c9276 | ||
|
39b75da818 | ||
|
c948d4ed08 | ||
|
8402e7d296 | ||
|
5f007096d7 | ||
|
6faa5d7ec7 | ||
|
69053f4c0f | ||
|
d6ee8e07c5 | ||
|
8c5b10281a | ||
|
43a9ce154f | ||
|
9b409c58fe | ||
|
9eee95221e | ||
|
beedb909f9 | ||
|
1bdc5fd1bd |
2
aec
@ -1 +1 @@
|
||||
Subproject commit 3be088c6cff8021c74eca714150e68e2cc74bee0
|
||||
Subproject commit 9e11f4f95707714464194bdfc9db0222ec5c6163
|
4
build.js
@ -14,8 +14,8 @@ const baseConfig = {
|
||||
};
|
||||
|
||||
const entryPoints = [
|
||||
{ in: 'src/app/HeaderController.js', out: 'public/build/header' },
|
||||
{ in: 'src/app/PickleGlassApp.js', out: 'public/build/content' },
|
||||
{ in: 'src/ui/app/HeaderController.js', out: 'public/build/header' },
|
||||
{ in: 'src/ui/app/PickleGlassApp.js', out: 'public/build/content' },
|
||||
];
|
||||
|
||||
async function build() {
|
||||
|
@ -33,7 +33,7 @@ extraResources:
|
||||
to: out
|
||||
|
||||
asarUnpack:
|
||||
- "src/assets/SystemAudioDump"
|
||||
- "src/ui/assets/SystemAudioDump"
|
||||
- "**/node_modules/sharp/**/*"
|
||||
- "**/node_modules/@img/**/*"
|
||||
|
||||
|
77
src/bridge/featureBridge.js
Normal file
@ -0,0 +1,77 @@
|
||||
// src/bridge/featureBridge.js
|
||||
const { ipcMain } = require('electron');
|
||||
const settingsService = require('../features/settings/settingsService');
|
||||
const askService = require('../features/ask/askService');
|
||||
|
||||
module.exports = {
|
||||
// Renderer로부터의 요청을 수신
|
||||
initialize() {
|
||||
// Ask 관련 핸들러 추가
|
||||
ipcMain.handle('ask:sendMessage', async (event, userPrompt) => {
|
||||
return askService.sendMessage(userPrompt);
|
||||
});
|
||||
|
||||
// 기존 ask 핸들러 유지
|
||||
ipcMain.handle('feature:ask', (e, query) => {
|
||||
// 실제로는 여기서 Controller -> Service 로직 수행
|
||||
return `"${query}"에 대한 답변입니다.`;
|
||||
});
|
||||
|
||||
// settings 관련 핸들러 추가
|
||||
ipcMain.handle('settings:getSettings', async () => {
|
||||
return await settingsService.getSettings();
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:saveSettings', async (event, settings) => {
|
||||
return await settingsService.saveSettings(settings);
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:getPresets', async () => {
|
||||
return await settingsService.getPresets();
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:getPresetTemplates', async () => {
|
||||
return await settingsService.getPresetTemplates();
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:createPreset', async (event, title, prompt) => {
|
||||
return await settingsService.createPreset(title, prompt);
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:updatePreset', async (event, id, title, prompt) => {
|
||||
return await settingsService.updatePreset(id, title, prompt);
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:deletePreset', async (event, id) => {
|
||||
return await settingsService.deletePreset(id);
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:saveApiKey', async (event, apiKey, provider) => {
|
||||
return await settingsService.saveApiKey(apiKey, provider);
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:removeApiKey', async () => {
|
||||
return await settingsService.removeApiKey();
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:updateContentProtection', async (event, enabled) => {
|
||||
return await settingsService.updateContentProtection(enabled);
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:get-auto-update', async () => {
|
||||
return await settingsService.getAutoUpdateSetting();
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:set-auto-update', async (event, isEnabled) => {
|
||||
console.log('[SettingsService] Setting auto update setting:', isEnabled);
|
||||
return await settingsService.setAutoUpdateSetting(isEnabled);
|
||||
});
|
||||
|
||||
console.log('[FeatureBridge] Initialized with ask and settings handlers.');
|
||||
},
|
||||
|
||||
// Renderer로 상태를 전송
|
||||
sendAskProgress(win, progress) {
|
||||
win.webContents.send('feature:ask:progress', progress);
|
||||
},
|
||||
};
|
10
src/bridge/internalBridge.js
Normal file
@ -0,0 +1,10 @@
|
||||
// src/bridge/internalBridge.js
|
||||
const { EventEmitter } = require('events');
|
||||
|
||||
// FeatureCore와 WindowCore를 잇는 내부 이벤트 버스
|
||||
module.exports = new EventEmitter();
|
||||
|
||||
// 예시 이벤트
|
||||
internalBridge.on('content-protection-changed', (enabled) => {
|
||||
// windowManager에서 처리
|
||||
});
|
74
src/bridge/windowBridge.js
Normal file
@ -0,0 +1,74 @@
|
||||
// src/bridge/windowBridge.js
|
||||
const { ipcMain, BrowserWindow } = require('electron');
|
||||
const { windowPool, settingsHideTimer, app, shell } = require('../window/windowManager'); // 필요 변수 require
|
||||
|
||||
module.exports = {
|
||||
// Renderer로부터의 요청을 수신
|
||||
initialize() {
|
||||
// 기존
|
||||
ipcMain.on('window:hide', (e) => BrowserWindow.fromWebContents(e.sender)?.hide());
|
||||
|
||||
// windowManager 관련 추가
|
||||
ipcMain.handle('toggle-content-protection', () => {
|
||||
// windowManager의 toggle-content-protection 로직
|
||||
isContentProtectionOn = !isContentProtectionOn;
|
||||
windowPool.forEach(win => {
|
||||
if (win && !win.isDestroyed()) {
|
||||
win.setContentProtection(isContentProtectionOn);
|
||||
}
|
||||
});
|
||||
return isContentProtectionOn;
|
||||
});
|
||||
|
||||
ipcMain.handle('get-content-protection-status', () => {
|
||||
return isContentProtectionOn;
|
||||
});
|
||||
|
||||
ipcMain.handle('open-shortcut-editor', () => {
|
||||
// open-shortcut-editor 로직 (windowPool 등 필요시 require)
|
||||
const header = windowPool.get('header');
|
||||
if (!header) return;
|
||||
globalShortcut.unregisterAll();
|
||||
createFeatureWindows(header, 'shortcut-settings');
|
||||
});
|
||||
|
||||
// 다른 관련 핸들러 추가 (quit-application, etc.)
|
||||
ipcMain.handle('quit-application', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
// 추가: show-settings-window
|
||||
ipcMain.on('show-settings-window', (event, bounds) => {
|
||||
if (!bounds) return;
|
||||
const win = windowPool.get('settings');
|
||||
if (win && !win.isDestroyed()) {
|
||||
if (settingsHideTimer) clearTimeout(settingsHideTimer);
|
||||
// 위치 조정 로직 (기존 복사)
|
||||
const header = windowPool.get('header');
|
||||
const headerBounds = header?.getBounds() ?? { x: 0, y: 0 };
|
||||
const settingsBounds = win.getBounds();
|
||||
const disp = getCurrentDisplay(header);
|
||||
const { x: waX, y: waY, width: waW, height: waH } = disp.workArea;
|
||||
let x = Math.round(headerBounds.x + (bounds?.x ?? 0) + (bounds?.width ?? 0) / 2 - settingsBounds.width / 2);
|
||||
let y = Math.round(headerBounds.y + (bounds?.y ?? 0) + (bounds?.height ?? 0) + 31);
|
||||
x = Math.max(waX + 10, Math.min(waX + waW - settingsBounds.width - 10, x));
|
||||
y = Math.max(waY + 10, Math.min(waY + waH - settingsBounds.height - 10, y));
|
||||
win.setBounds({ x, y });
|
||||
win.__lockedByButton = true;
|
||||
win.show();
|
||||
win.moveTop();
|
||||
win.setAlwaysOnTop(true);
|
||||
}
|
||||
});
|
||||
|
||||
// 추가: hide-settings-window 등 다른 핸들러 복사
|
||||
// ... (hide-settings-window, cancel-hide-settings-window, quit-application, open-login-page, firebase-logout, move-window-step 등)
|
||||
|
||||
// 예: ipcMain.handle('open-login-page', () => { shell.openExternal(...); });
|
||||
},
|
||||
|
||||
// Renderer로 상태를 전송
|
||||
notifyFocusChange(win, isFocused) {
|
||||
win.webContents.send('window:focus-change', isFocused);
|
||||
},
|
||||
};
|
@ -1,10 +1,10 @@
|
||||
const { ipcMain, BrowserWindow } = require('electron');
|
||||
const { createStreamingLLM } = require('../../common/ai/factory');
|
||||
const { getStoredApiKey, getStoredProvider, getCurrentModelInfo, windowPool, captureScreenshot } = require('../../electron/windowManager');
|
||||
const authService = require('../../common/services/authService');
|
||||
const sessionRepository = require('../../common/repositories/session');
|
||||
const { createStreamingLLM } = require('../common/ai/factory');
|
||||
const { getStoredApiKey, getStoredProvider, getCurrentModelInfo, windowPool, captureScreenshot } = require('../../window/windowManager');
|
||||
const authService = require('../common/services/authService');
|
||||
const sessionRepository = require('../common/repositories/session');
|
||||
const askRepository = require('./repositories');
|
||||
const { getSystemPrompt } = require('../../common/prompts/promptBuilder');
|
||||
const { getSystemPrompt } = require('../common/prompts/promptBuilder');
|
||||
|
||||
function formatConversationForPrompt(conversationTexts) {
|
||||
if (!conversationTexts || conversationTexts.length === 0) return 'No conversation history available.';
|
||||
@ -22,16 +22,11 @@ async function sendMessage(userPrompt) {
|
||||
console.warn('[AskService] Cannot process empty message');
|
||||
return { success: false, error: 'Empty message' };
|
||||
}
|
||||
|
||||
const askWindow = windowPool.get('ask');
|
||||
if (askWindow && !askWindow.isDestroyed()) {
|
||||
askWindow.webContents.send('hide-text-input');
|
||||
}
|
||||
|
||||
let sessionId;
|
||||
|
||||
try {
|
||||
console.log(`[AskService] 🤖 Processing message: ${userPrompt.substring(0, 50)}...`);
|
||||
console.log(`[AskService] Processing message: ${userPrompt.substring(0, 50)}...`);
|
||||
|
||||
// --- Save user's message immediately ---
|
||||
// This ensures the user message is always timestamped before the assistant's response.
|
||||
@ -139,12 +134,11 @@ async function sendMessage(userPrompt) {
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
ipcMain.handle('ask:sendMessage', async (event, userPrompt) => {
|
||||
return sendMessage(userPrompt);
|
||||
});
|
||||
// IPC 핸들러는 featureBridge.js로 이동됨
|
||||
console.log('[AskService] Initialized and ready.');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initialize,
|
||||
sendMessage, // sendMessage 함수 export 추가
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
const { collection, addDoc, query, getDocs, orderBy, Timestamp } = require('firebase/firestore');
|
||||
const { getFirestoreInstance } = require('../../../common/services/firebaseClient');
|
||||
const { createEncryptedConverter } = require('../../../common/repositories/firestoreConverter');
|
||||
const { getFirestoreInstance } = require('../../common/services/firebaseClient');
|
||||
const { createEncryptedConverter } = require('../../common/repositories/firestoreConverter');
|
||||
|
||||
const aiMessageConverter = createEncryptedConverter(['content']);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
const sqliteRepository = require('./sqlite.repository');
|
||||
const firebaseRepository = require('./firebase.repository');
|
||||
const authService = require('../../../common/services/authService');
|
||||
const authService = require('../../common/services/authService');
|
||||
|
||||
function getBaseRepository() {
|
||||
const user = authService.getCurrentUser();
|
||||
|
@ -1,4 +1,4 @@
|
||||
const sqliteClient = require('../../../common/services/sqliteClient');
|
||||
const sqliteClient = require('../../common/services/sqliteClient');
|
||||
|
||||
function addAiMessage({ uid, sessionId, role, content, model = 'unknown' }) {
|
||||
// uid is ignored in the SQLite implementation
|
||||
|
@ -1,6 +1,6 @@
|
||||
const sqliteRepository = require('./sqlite.repository');
|
||||
const firebaseRepository = require('./firebase.repository');
|
||||
const authService = require('../../../common/services/authService');
|
||||
const authService = require('../../services/authService');
|
||||
|
||||
function getBaseRepository() {
|
||||
const user = authService.getCurrentUser();
|
@ -5,9 +5,9 @@ const encryptionService = require('../services/encryptionService');
|
||||
const sqliteSessionRepo = require('../repositories/session/sqlite.repository');
|
||||
const sqlitePresetRepo = require('../repositories/preset/sqlite.repository');
|
||||
const sqliteUserRepo = require('../repositories/user/sqlite.repository');
|
||||
const sqliteSttRepo = require('../../features/listen/stt/repositories/sqlite.repository');
|
||||
const sqliteSummaryRepo = require('../../features/listen/summary/repositories/sqlite.repository');
|
||||
const sqliteAiMessageRepo = require('../../features/ask/repositories/sqlite.repository');
|
||||
const sqliteSttRepo = require('../../listen/stt/repositories/sqlite.repository');
|
||||
const sqliteSummaryRepo = require('../../listen/summary/repositories/sqlite.repository');
|
||||
const sqliteAiMessageRepo = require('../../ask/repositories/sqlite.repository');
|
||||
|
||||
const MAX_BATCH_OPERATIONS = 500;
|
||||
|
@ -1,8 +1,8 @@
|
||||
const { BrowserWindow, app } = require('electron');
|
||||
const SttService = require('./stt/sttService');
|
||||
const SummaryService = require('./summary/summaryService');
|
||||
const authService = require('../../common/services/authService');
|
||||
const sessionRepository = require('../../common/repositories/session');
|
||||
const authService = require('../common/services/authService');
|
||||
const sessionRepository = require('../common/repositories/session');
|
||||
const sttRepository = require('./stt/repositories');
|
||||
|
||||
class ListenService {
|
||||
@ -193,8 +193,6 @@ class ListenService {
|
||||
this.currentSessionId = null;
|
||||
this.summaryService.resetConversationHistory();
|
||||
|
||||
this.sendToRenderer('session-did-close');
|
||||
|
||||
console.log('Listen service session closed.');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
const { collection, addDoc, query, getDocs, orderBy, Timestamp } = require('firebase/firestore');
|
||||
const { getFirestoreInstance } = require('../../../../common/services/firebaseClient');
|
||||
const { createEncryptedConverter } = require('../../../../common/repositories/firestoreConverter');
|
||||
const { getFirestoreInstance } = require('../../../common/services/firebaseClient');
|
||||
const { createEncryptedConverter } = require('../../../common/repositories/firestoreConverter');
|
||||
|
||||
const transcriptConverter = createEncryptedConverter(['text']);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
const sqliteRepository = require('./sqlite.repository');
|
||||
const firebaseRepository = require('./firebase.repository');
|
||||
const authService = require('../../../../common/services/authService');
|
||||
const authService = require('../../../common/services/authService');
|
||||
|
||||
function getBaseRepository() {
|
||||
const user = authService.getCurrentUser();
|
||||
|
@ -1,4 +1,4 @@
|
||||
const sqliteClient = require('../../../../common/services/sqliteClient');
|
||||
const sqliteClient = require('../../../common/services/sqliteClient');
|
||||
|
||||
function addTranscript({ uid, sessionId, speaker, text }) {
|
||||
// uid is ignored in the SQLite implementation
|
||||
|
@ -1,7 +1,7 @@
|
||||
const { BrowserWindow } = require('electron');
|
||||
const { spawn } = require('child_process');
|
||||
const { createSTT } = require('../../../common/ai/factory');
|
||||
const { getStoredApiKey, getStoredProvider, getCurrentModelInfo } = require('../../../electron/windowManager');
|
||||
const { createSTT } = require('../../common/ai/factory');
|
||||
const { getStoredApiKey, getStoredProvider, getCurrentModelInfo } = require('../../../window/windowManager');
|
||||
|
||||
const COMPLETION_DEBOUNCE_MS = 2000;
|
||||
|
||||
@ -367,11 +367,6 @@ class SttService {
|
||||
onclose: event => console.log('Their STT session closed:', event.reason),
|
||||
},
|
||||
};
|
||||
|
||||
// Determine auth options for providers that support it
|
||||
// const authService = require('../../../common/services/authService');
|
||||
// const userState = authService.getCurrentUser();
|
||||
// const loggedIn = userState.isLoggedIn;
|
||||
|
||||
const sttOptions = {
|
||||
apiKey: this.modelInfo.apiKey,
|
||||
@ -476,8 +471,8 @@ class SttService {
|
||||
const { app } = require('electron');
|
||||
const path = require('path');
|
||||
const systemAudioPath = app.isPackaged
|
||||
? path.join(process.resourcesPath, 'app.asar.unpacked', 'src', 'assets', 'SystemAudioDump')
|
||||
: path.join(app.getAppPath(), 'src', 'assets', 'SystemAudioDump');
|
||||
? path.join(process.resourcesPath, 'app.asar.unpacked', 'src', 'ui', 'assets', 'SystemAudioDump')
|
||||
: path.join(app.getAppPath(), 'src', 'ui', 'assets', 'SystemAudioDump');
|
||||
|
||||
console.log('SystemAudioDump path:', systemAudioPath);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
const { collection, doc, setDoc, getDoc, Timestamp } = require('firebase/firestore');
|
||||
const { getFirestoreInstance } = require('../../../../common/services/firebaseClient');
|
||||
const { createEncryptedConverter } = require('../../../../common/repositories/firestoreConverter');
|
||||
const encryptionService = require('../../../../common/services/encryptionService');
|
||||
const { getFirestoreInstance } = require('../../../common/services/firebaseClient');
|
||||
const { createEncryptedConverter } = require('../../../common/repositories/firestoreConverter');
|
||||
const encryptionService = require('../../../common/services/encryptionService');
|
||||
|
||||
const fieldsToEncrypt = ['tldr', 'text', 'bullet_json', 'action_json'];
|
||||
const summaryConverter = createEncryptedConverter(fieldsToEncrypt);
|
||||
|
@ -1,6 +1,6 @@
|
||||
const sqliteRepository = require('./sqlite.repository');
|
||||
const firebaseRepository = require('./firebase.repository');
|
||||
const authService = require('../../../../common/services/authService');
|
||||
const authService = require('../../../common/services/authService');
|
||||
|
||||
function getBaseRepository() {
|
||||
const user = authService.getCurrentUser();
|
||||
|
@ -1,4 +1,4 @@
|
||||
const sqliteClient = require('../../../../common/services/sqliteClient');
|
||||
const sqliteClient = require('../../../common/services/sqliteClient');
|
||||
|
||||
function saveSummary({ uid, sessionId, tldr, text, bullet_json, action_json, model = 'unknown' }) {
|
||||
// uid is ignored in the SQLite implementation
|
||||
|
@ -1,10 +1,9 @@
|
||||
const { BrowserWindow } = require('electron');
|
||||
const { getSystemPrompt } = require('../../../common/prompts/promptBuilder.js');
|
||||
const { createLLM } = require('../../../common/ai/factory');
|
||||
const authService = require('../../../common/services/authService');
|
||||
const sessionRepository = require('../../../common/repositories/session');
|
||||
const { getSystemPrompt } = require('../../common/prompts/promptBuilder.js');
|
||||
const { createLLM } = require('../../common/ai/factory');
|
||||
const sessionRepository = require('../../common/repositories/session');
|
||||
const summaryRepository = require('./repositories');
|
||||
const { getStoredApiKey, getStoredProvider, getCurrentModelInfo } = require('../../../electron/windowManager');
|
||||
const { getStoredApiKey, getStoredProvider, getCurrentModelInfo } = require('../../../window/windowManager.js');
|
||||
|
||||
class SummaryService {
|
||||
constructor() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
const { collection, doc, addDoc, getDoc, getDocs, updateDoc, deleteDoc, query, where, orderBy } = require('firebase/firestore');
|
||||
const { getFirestoreInstance } = require('../../../common/services/firebaseClient');
|
||||
const { createEncryptedConverter } = require('../../../common/repositories/firestoreConverter');
|
||||
const encryptionService = require('../../../common/services/encryptionService');
|
||||
const { getFirestoreInstance } = require('../../common/services/firebaseClient');
|
||||
const { createEncryptedConverter } = require('../../common/repositories/firestoreConverter');
|
||||
const encryptionService = require('../../common/services/encryptionService');
|
||||
|
||||
const userPresetConverter = createEncryptedConverter(['prompt', 'title']);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
const sqliteRepository = require('./sqlite.repository');
|
||||
const firebaseRepository = require('./firebase.repository');
|
||||
const authService = require('../../../common/services/authService');
|
||||
const authService = require('../../common/services/authService');
|
||||
|
||||
function getBaseRepository() {
|
||||
const user = authService.getCurrentUser();
|
||||
|
@ -1,4 +1,4 @@
|
||||
const sqliteClient = require('../../../common/services/sqliteClient');
|
||||
const sqliteClient = require('../../common/services/sqliteClient');
|
||||
|
||||
function getPresets(uid) {
|
||||
const db = sqliteClient.getDb();
|
||||
|
@ -1,8 +1,8 @@
|
||||
const { ipcMain, BrowserWindow } = require('electron');
|
||||
const Store = require('electron-store');
|
||||
const authService = require('../../common/services/authService');
|
||||
const authService = require('../common/services/authService');
|
||||
const settingsRepository = require('./repositories');
|
||||
const { getStoredApiKey, getStoredProvider, windowPool } = require('../../electron/windowManager');
|
||||
const { getStoredApiKey, getStoredProvider, windowPool } = require('../../window/windowManager');
|
||||
|
||||
const store = new Store({
|
||||
name: 'pickle-glass-settings',
|
||||
@ -373,56 +373,7 @@ function initialize() {
|
||||
// cleanup
|
||||
windowNotificationManager.cleanup();
|
||||
|
||||
// IPC handlers for settings
|
||||
ipcMain.handle('settings:getSettings', async () => {
|
||||
return await getSettings();
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:saveSettings', async (event, settings) => {
|
||||
return await saveSettings(settings);
|
||||
});
|
||||
|
||||
// IPC handlers for presets
|
||||
ipcMain.handle('settings:getPresets', async () => {
|
||||
return await getPresets();
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:getPresetTemplates', async () => {
|
||||
return await getPresetTemplates();
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:createPreset', async (event, title, prompt) => {
|
||||
return await createPreset(title, prompt);
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:updatePreset', async (event, id, title, prompt) => {
|
||||
return await updatePreset(id, title, prompt);
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:deletePreset', async (event, id) => {
|
||||
return await deletePreset(id);
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:saveApiKey', async (event, apiKey, provider) => {
|
||||
return await saveApiKey(apiKey, provider);
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:removeApiKey', async () => {
|
||||
return await removeApiKey();
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:updateContentProtection', async (event, enabled) => {
|
||||
return await updateContentProtection(enabled);
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:get-auto-update', async () => {
|
||||
return await getAutoUpdateSetting();
|
||||
});
|
||||
|
||||
ipcMain.handle('settings:set-auto-update', async (event, isEnabled) => {
|
||||
console.log('[SettingsService] Setting auto update setting:', isEnabled);
|
||||
return await setAutoUpdateSetting(isEnabled);
|
||||
});
|
||||
// IPC handlers 제거 (featureBridge로 이동)
|
||||
|
||||
console.log('[SettingsService] Initialized and ready.');
|
||||
}
|
||||
|
44
src/index.js
@ -12,11 +12,11 @@ if (require('electron-squirrel-startup')) {
|
||||
}
|
||||
|
||||
const { app, BrowserWindow, shell, ipcMain, dialog, desktopCapturer, session } = require('electron');
|
||||
const { createWindows } = require('./electron/windowManager.js');
|
||||
const { createWindows } = require('./window/windowManager.js');
|
||||
const ListenService = require('./features/listen/listenService');
|
||||
const { initializeFirebase } = require('./common/services/firebaseClient');
|
||||
const databaseInitializer = require('./common/services/databaseInitializer');
|
||||
const authService = require('./common/services/authService');
|
||||
const { initializeFirebase } = require('./features/common/services/firebaseClient');
|
||||
const databaseInitializer = require('./features/common/services/databaseInitializer');
|
||||
const authService = require('./features/common/services/authService');
|
||||
const path = require('node:path');
|
||||
const express = require('express');
|
||||
const fetch = require('node-fetch');
|
||||
@ -24,9 +24,10 @@ const { autoUpdater } = require('electron-updater');
|
||||
const { EventEmitter } = require('events');
|
||||
const askService = require('./features/ask/askService');
|
||||
const settingsService = require('./features/settings/settingsService');
|
||||
const sessionRepository = require('./common/repositories/session');
|
||||
const ModelStateService = require('./common/services/modelStateService');
|
||||
const sqliteClient = require('./common/services/sqliteClient');
|
||||
const sessionRepository = require('./features/common/repositories/session');
|
||||
const ModelStateService = require('./features/common/services/modelStateService');
|
||||
const sqliteClient = require('./features/common/services/sqliteClient');
|
||||
const featureBridge = require('./bridge/featureBridge');
|
||||
|
||||
// Global variables
|
||||
const eventBridge = new EventEmitter();
|
||||
@ -43,8 +44,8 @@ global.modelStateService = modelStateService;
|
||||
//////// after_modelStateService ////////
|
||||
|
||||
// Import and initialize OllamaService
|
||||
const ollamaService = require('./common/services/ollamaService');
|
||||
const ollamaModelRepository = require('./common/repositories/ollamaModel');
|
||||
const ollamaService = require('./features/common/services/ollamaService');
|
||||
const ollamaModelRepository = require('./features/common/repositories/ollamaModel');
|
||||
|
||||
// Native deep link handling - cross-platform compatible
|
||||
let pendingDeepLinkUrl = null;
|
||||
@ -123,7 +124,7 @@ function setupProtocolHandling() {
|
||||
}
|
||||
|
||||
function focusMainWindow() {
|
||||
const { windowPool } = require('./electron/windowManager');
|
||||
const { windowPool } = require('./window/windowManager.js');
|
||||
if (windowPool) {
|
||||
const header = windowPool.get('header');
|
||||
if (header && !header.isDestroyed()) {
|
||||
@ -205,6 +206,7 @@ app.whenReady().then(async () => {
|
||||
listenService.setupIpcHandlers();
|
||||
askService.initialize();
|
||||
settingsService.initialize();
|
||||
featureBridge.initialize(); // 추가: featureBridge 초기화
|
||||
setupGeneralIpcHandlers();
|
||||
setupOllamaIpcHandlers();
|
||||
setupWhisperIpcHandlers();
|
||||
@ -329,7 +331,7 @@ app.on('activate', () => {
|
||||
});
|
||||
|
||||
function setupWhisperIpcHandlers() {
|
||||
const { WhisperService } = require('./common/services/whisperService');
|
||||
const { WhisperService } = require('./features/common/services/whisperService');
|
||||
const whisperService = new WhisperService();
|
||||
|
||||
// Forward download progress events to renderer
|
||||
@ -393,8 +395,8 @@ function setupWhisperIpcHandlers() {
|
||||
}
|
||||
|
||||
function setupGeneralIpcHandlers() {
|
||||
const userRepository = require('./common/repositories/user');
|
||||
const presetRepository = require('./common/repositories/preset');
|
||||
const userRepository = require('./features/common/repositories/user');
|
||||
const presetRepository = require('./features/common/repositories/preset');
|
||||
|
||||
ipcMain.handle('get-user-presets', () => {
|
||||
// The adapter injects the UID.
|
||||
@ -625,12 +627,12 @@ function setupOllamaIpcHandlers() {
|
||||
}
|
||||
|
||||
function setupWebDataHandlers() {
|
||||
const sessionRepository = require('./common/repositories/session');
|
||||
const sessionRepository = require('./features/common/repositories/session');
|
||||
const sttRepository = require('./features/listen/stt/repositories');
|
||||
const summaryRepository = require('./features/listen/summary/repositories');
|
||||
const askRepository = require('./features/ask/repositories');
|
||||
const userRepository = require('./common/repositories/user');
|
||||
const presetRepository = require('./common/repositories/preset');
|
||||
const userRepository = require('./features/common/repositories/user');
|
||||
const presetRepository = require('./features/common/repositories/preset');
|
||||
|
||||
const handleRequest = async (channel, responseChannel, payload) => {
|
||||
let result;
|
||||
@ -788,7 +790,7 @@ async function handleCustomUrl(url) {
|
||||
handlePersonalizeFromUrl(params);
|
||||
break;
|
||||
default:
|
||||
const { windowPool } = require('./electron/windowManager');
|
||||
const { windowPool } = require('./window/windowManager.js');
|
||||
const header = windowPool.get('header');
|
||||
if (header) {
|
||||
if (header.isMinimized()) header.restore();
|
||||
@ -806,7 +808,7 @@ async function handleCustomUrl(url) {
|
||||
}
|
||||
|
||||
async function handleFirebaseAuthCallback(params) {
|
||||
const userRepository = require('./common/repositories/user');
|
||||
const userRepository = require('./features/common/repositories/user');
|
||||
const { token: idToken } = params;
|
||||
|
||||
if (!idToken) {
|
||||
@ -850,7 +852,7 @@ async function handleFirebaseAuthCallback(params) {
|
||||
console.log('[Auth] Main process sign-in initiated. Waiting for onAuthStateChanged...');
|
||||
|
||||
// 3. Focus the app window
|
||||
const { windowPool } = require('./electron/windowManager');
|
||||
const { windowPool } = require('./window/windowManager.js');
|
||||
const header = windowPool.get('header');
|
||||
if (header) {
|
||||
if (header.isMinimized()) header.restore();
|
||||
@ -863,7 +865,7 @@ async function handleFirebaseAuthCallback(params) {
|
||||
console.error('[Auth] Error during custom token exchange or sign-in:', error);
|
||||
// The UI will not change, and the user can try again.
|
||||
// Optionally, send a generic error event to the renderer.
|
||||
const { windowPool } = require('./electron/windowManager');
|
||||
const { windowPool } = require('./window/windowManager.js');
|
||||
const header = windowPool.get('header');
|
||||
if (header) {
|
||||
header.webContents.send('auth-failed', { message: error.message });
|
||||
@ -874,7 +876,7 @@ async function handleFirebaseAuthCallback(params) {
|
||||
function handlePersonalizeFromUrl(params) {
|
||||
console.log('[Custom URL] Personalize params:', params);
|
||||
|
||||
const { windowPool } = require('./electron/windowManager');
|
||||
const { windowPool } = require('./window/windowManager.js');
|
||||
const header = windowPool.get('header');
|
||||
|
||||
if (header) {
|
||||
|
334
src/preload.js
@ -1,2 +1,332 @@
|
||||
// See the Electron documentation for details on how to use preload scripts:
|
||||
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
|
||||
// src/preload.js
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
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: {
|
||||
// ask
|
||||
submitAsk: (query) => ipcRenderer.invoke('feature:ask', query),
|
||||
onAskProgress: (callback) => ipcRenderer.on('feature:ask:progress', (e, p) => callback(p)),
|
||||
|
||||
settings: {
|
||||
// invoke methods
|
||||
getCurrentUser: () => ipcRenderer.invoke('get-current-user'),
|
||||
getProviderConfig: () => ipcRenderer.invoke('model:get-provider-config'),
|
||||
getAllKeys: () => ipcRenderer.invoke('model:get-all-keys'),
|
||||
getAvailableModels: (type) => ipcRenderer.invoke('model:get-available-models', type),
|
||||
getSelectedModels: () => ipcRenderer.invoke('model:get-selected-models'),
|
||||
getPresets: () => ipcRenderer.invoke('settings:getPresets'),
|
||||
getContentProtectionStatus: () => ipcRenderer.invoke('get-content-protection-status'),
|
||||
getCurrentShortcuts: () => ipcRenderer.invoke('get-current-shortcuts'),
|
||||
getOllamaStatus: () => ipcRenderer.invoke('ollama:get-status'),
|
||||
getWhisperInstalledModels: () => ipcRenderer.invoke('whisper:get-installed-models'),
|
||||
ollamaEnsureReady: () => ipcRenderer.invoke('ollama:ensure-ready'),
|
||||
validateKey: (data) => ipcRenderer.invoke('model:validate-key', data),
|
||||
getAutoUpdate: () => ipcRenderer.invoke('settings:get-auto-update'),
|
||||
setAutoUpdate: (isEnabled) => ipcRenderer.invoke('settings:set-auto-update', isEnabled),
|
||||
removeApiKey: (provider) => ipcRenderer.invoke('model:remove-api-key', provider),
|
||||
setSelectedModel: (data) => ipcRenderer.invoke('model:set-selected-model', data),
|
||||
downloadWhisperModel: (modelId) => ipcRenderer.invoke('whisper:download-model', modelId),
|
||||
openLoginPage: () => ipcRenderer.invoke('open-login-page'),
|
||||
toggleContentProtection: () => ipcRenderer.invoke('toggle-content-protection'),
|
||||
openShortcutEditor: () => ipcRenderer.invoke('open-shortcut-editor'),
|
||||
quitApplication: () => ipcRenderer.invoke('quit-application'),
|
||||
firebaseLogout: () => ipcRenderer.invoke('firebase-logout'),
|
||||
ollamaShutdown: (graceful) => ipcRenderer.invoke('ollama:shutdown', graceful),
|
||||
startFirebaseAuth: () => ipcRenderer.invoke('start-firebase-auth'),
|
||||
|
||||
// on methods (listeners)
|
||||
onUserStateChanged: (callback) => ipcRenderer.on('user-state-changed', callback),
|
||||
removeOnUserStateChanged: (callback) => ipcRenderer.removeListener('user-state-changed', callback),
|
||||
onSettingsUpdated: (callback) => ipcRenderer.on('settings-updated', callback),
|
||||
removeOnSettingsUpdated: (callback) => ipcRenderer.removeListener('settings-updated', callback),
|
||||
onPresetsUpdated: (callback) => ipcRenderer.on('presets-updated', callback),
|
||||
removeOnPresetsUpdated: (callback) => ipcRenderer.removeListener('presets-updated', callback),
|
||||
onShortcutsUpdated: (callback) => ipcRenderer.on('shortcuts-updated', callback),
|
||||
removeOnShortcutsUpdated: (callback) => ipcRenderer.removeListener('shortcuts-updated', callback),
|
||||
onWhisperDownloadProgress: (callback) => ipcRenderer.on('whisper:download-progress', callback),
|
||||
removeOnWhisperDownloadProgress: (callback) => ipcRenderer.removeListener('whisper:download-progress', callback),
|
||||
|
||||
// send methods
|
||||
cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'),
|
||||
hideSettingsWindow: () => ipcRenderer.send('hide-settings-window')
|
||||
}
|
||||
},
|
||||
// window
|
||||
window: {
|
||||
// window
|
||||
hide: () => ipcRenderer.send('window:hide'),
|
||||
onFocusChange: (callback) => ipcRenderer.on('window:focus-change', (e, f) => callback(f)),
|
||||
|
||||
// settings window
|
||||
showSettingsWindow: (bounds) => ipcRenderer.send('show-settings-window', bounds),
|
||||
hideSettingsWindow: () => ipcRenderer.send('hide-settings-window'),
|
||||
cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'),
|
||||
moveWindowStep: (direction) => ipcRenderer.invoke('move-window-step', direction),
|
||||
openLoginPage: () => ipcRenderer.invoke('open-login-page'),
|
||||
firebaseLogout: () => ipcRenderer.invoke('firebase-logout'),
|
||||
ollamaShutdown: (graceful) => ipcRenderer.invoke('ollama:shutdown', graceful),
|
||||
startFirebaseAuth: () => ipcRenderer.invoke('start-firebase-auth'),
|
||||
|
||||
// event listener
|
||||
onUserStateChanged: (callback) => ipcRenderer.on('user-state-changed', callback),
|
||||
removeOnUserStateChanged: (callback) => ipcRenderer.removeListener('user-state-changed', callback),
|
||||
onSettingsUpdated: (callback) => ipcRenderer.on('settings-updated', callback),
|
||||
removeOnSettingsUpdated: (callback) => ipcRenderer.removeListener('settings-updated', callback),
|
||||
onPresetsUpdated: (callback) => ipcRenderer.on('presets-updated', callback),
|
||||
removeOnPresetsUpdated: (callback) => ipcRenderer.removeListener('presets-updated', callback),
|
||||
onShortcutsUpdated: (callback) => ipcRenderer.on('shortcuts-updated', callback),
|
||||
removeOnShortcutsUpdated: (callback) => ipcRenderer.removeListener('shortcuts-updated', callback),
|
||||
onWhisperDownloadProgress: (callback) => ipcRenderer.on('whisper:download-progress', callback),
|
||||
removeOnWhisperDownloadProgress: (callback) => ipcRenderer.removeListener('whisper:download-progress', callback),
|
||||
|
||||
// send
|
||||
cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'),
|
||||
hideSettingsWindow: () => ipcRenderer.send('hide-settings-window')
|
||||
}
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
import { html, css, LitElement } from "../assets/lit-core-2.7.4.min.js"
|
||||
import { getOllamaProgressTracker } from "../common/services/localProgressTracker.js"
|
||||
import { getOllamaProgressTracker } from "../../features/common/services/localProgressTracker.js"
|
||||
|
||||
export class ApiKeyHeader extends LitElement {
|
||||
//////// after_modelStateService ////////
|
||||
@ -370,13 +370,12 @@ export class ApiKeyHeader extends LitElement {
|
||||
}
|
||||
|
||||
async loadProviderConfig() {
|
||||
if (!window.require) return;
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
if (!window.api || !window.api.apikey) return;
|
||||
|
||||
try {
|
||||
const [config, ollamaStatus] = await Promise.all([
|
||||
ipcRenderer.invoke('model:get-provider-config'),
|
||||
ipcRenderer.invoke('ollama:get-status')
|
||||
window.api.apikey.getProviderConfig(),
|
||||
window.api.apikey.getOllamaStatus()
|
||||
]);
|
||||
|
||||
const llmProviders = [];
|
||||
@ -428,8 +427,8 @@ export class ApiKeyHeader extends LitElement {
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
const { ipcRenderer } = window.require("electron")
|
||||
const initialPosition = await ipcRenderer.invoke("get-header-position")
|
||||
if (!window.api || !window.api.apikey) return;
|
||||
const initialPosition = await window.api.apikey.getHeaderPosition()
|
||||
|
||||
this.dragState = {
|
||||
initialMouseX: e.screenX,
|
||||
@ -456,8 +455,9 @@ export class ApiKeyHeader extends LitElement {
|
||||
const newWindowX = this.dragState.initialWindowX + (e.screenX - this.dragState.initialMouseX)
|
||||
const newWindowY = this.dragState.initialWindowY + (e.screenY - this.dragState.initialMouseY)
|
||||
|
||||
const { ipcRenderer } = window.require("electron")
|
||||
ipcRenderer.invoke("move-header-to", newWindowX, newWindowY)
|
||||
if (window.api && window.api.apikey) {
|
||||
window.api.apikey.moveHeaderTo(newWindowX, newWindowY)
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseUp(e) {
|
||||
@ -652,9 +652,8 @@ export class ApiKeyHeader extends LitElement {
|
||||
try {
|
||||
// Lightweight health check - just ping the service
|
||||
const isHealthy = await this._executeOperation('health_check', async () => {
|
||||
if (!window.require) return false;
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
const result = await ipcRenderer.invoke('ollama:get-status');
|
||||
if (!window.api || !window.api.apikey) return false;
|
||||
const result = await window.api.apikey.getOllamaStatus();
|
||||
return result?.success && result?.running;
|
||||
}, { timeout: 5000, priority: 'low' });
|
||||
|
||||
@ -928,14 +927,13 @@ export class ApiKeyHeader extends LitElement {
|
||||
}
|
||||
|
||||
async refreshOllamaStatus() {
|
||||
if (!window.require) return;
|
||||
if (!window.api || !window.api.apikey) return;
|
||||
|
||||
try {
|
||||
this._updateConnectionState('connecting', 'Checking Ollama status');
|
||||
|
||||
const result = await this._executeOperation('ollama_status', async () => {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
return await ipcRenderer.invoke('ollama:get-status');
|
||||
return await window.api.apikey.getOllamaStatus();
|
||||
});
|
||||
|
||||
if (result?.success) {
|
||||
@ -960,12 +958,11 @@ export class ApiKeyHeader extends LitElement {
|
||||
}
|
||||
|
||||
async loadModelSuggestions() {
|
||||
if (!window.require) return;
|
||||
if (!window.api || !window.api.apikey) return;
|
||||
|
||||
try {
|
||||
const result = await this._executeOperation('model_suggestions', async () => {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
return await ipcRenderer.invoke('ollama:get-model-suggestions');
|
||||
return await window.api.apikey.getModelSuggestions();
|
||||
});
|
||||
|
||||
if (result?.success) {
|
||||
@ -988,14 +985,13 @@ export class ApiKeyHeader extends LitElement {
|
||||
}
|
||||
|
||||
async ensureOllamaReady() {
|
||||
if (!window.require) return false;
|
||||
if (!window.api || !window.api.apikey) return false;
|
||||
|
||||
try {
|
||||
this._updateConnectionState('connecting', 'Ensuring Ollama is ready');
|
||||
|
||||
const result = await this._executeOperation('ollama_ensure_ready', async () => {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
return await ipcRenderer.invoke('ollama:ensure-ready');
|
||||
return await window.api.apikey.ensureReady();
|
||||
}, { timeout: this.operationTimeout });
|
||||
|
||||
if (result?.success) {
|
||||
@ -1015,8 +1011,7 @@ export class ApiKeyHeader extends LitElement {
|
||||
}
|
||||
|
||||
async ensureOllamaReadyWithUI() {
|
||||
if (!window.require) return false;
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
if (!window.api || !window.api.apikey) return false;
|
||||
|
||||
this.installingModel = "Setting up Ollama";
|
||||
this.installProgress = 0;
|
||||
@ -1074,21 +1069,21 @@ export class ApiKeyHeader extends LitElement {
|
||||
operationCompleted = true;
|
||||
clearTimeout(completionTimeout);
|
||||
|
||||
ipcRenderer.removeListener("ollama:install-progress", progressHandler);
|
||||
window.api.apikey.removeOnOllamaInstallProgress(progressHandler);
|
||||
await this._handleOllamaSetupCompletion(result.success, result.error);
|
||||
};
|
||||
|
||||
ipcRenderer.once("ollama:install-complete", completionHandler);
|
||||
ipcRenderer.on("ollama:install-progress", progressHandler);
|
||||
window.api.apikey.onOllamaInstallComplete(completionHandler);
|
||||
window.api.apikey.onOllamaInstallProgress(progressHandler);
|
||||
|
||||
try {
|
||||
let result;
|
||||
if (!this.ollamaStatus.installed) {
|
||||
console.log("[ApiKeyHeader] Ollama not installed. Starting installation.");
|
||||
result = await ipcRenderer.invoke("ollama:install");
|
||||
result = await window.api.apikey.installOllama();
|
||||
} else {
|
||||
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
|
||||
@ -1106,8 +1101,8 @@ export class ApiKeyHeader extends LitElement {
|
||||
operationCompleted = true;
|
||||
clearTimeout(completionTimeout);
|
||||
console.error("[ApiKeyHeader] Ollama setup failed:", error);
|
||||
ipcRenderer.removeListener("ollama:install-progress", progressHandler);
|
||||
ipcRenderer.removeListener("ollama:install-complete", completionHandler);
|
||||
window.api.apikey.removeOnOllamaInstallProgress(progressHandler);
|
||||
window.api.apikey.removeOnOllamaInstallComplete(completionHandler);
|
||||
await this._handleOllamaSetupCompletion(false, error.message);
|
||||
}
|
||||
}
|
||||
@ -1229,7 +1224,7 @@ export class ApiKeyHeader extends LitElement {
|
||||
this.clearMessages();
|
||||
this.requestUpdate();
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
if (!window.api || !window.api.apikey) return;
|
||||
let progressHandler = null;
|
||||
|
||||
try {
|
||||
@ -1249,10 +1244,10 @@ export class ApiKeyHeader extends LitElement {
|
||||
};
|
||||
|
||||
// Set up progress tracking
|
||||
ipcRenderer.on('ollama:pull-progress', progressHandler);
|
||||
window.api.apikey.onOllamaPullProgress(progressHandler);
|
||||
|
||||
// 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) =>
|
||||
setTimeout(() => reject(new Error('Installation timeout after 10 minutes')), 600000)
|
||||
);
|
||||
@ -1281,7 +1276,7 @@ export class ApiKeyHeader extends LitElement {
|
||||
} finally {
|
||||
// Comprehensive cleanup
|
||||
if (progressHandler) {
|
||||
ipcRenderer.removeListener('ollama:pull-progress', progressHandler);
|
||||
window.api.apikey.removeOnOllamaPullProgress(progressHandler);
|
||||
}
|
||||
|
||||
this.installingModel = null;
|
||||
@ -1307,7 +1302,7 @@ export class ApiKeyHeader extends LitElement {
|
||||
this.clearMessages();
|
||||
this.requestUpdate();
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
if (!window.api || !window.api.apikey) return;
|
||||
let progressHandler = null;
|
||||
|
||||
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
|
||||
const downloadPromise = ipcRenderer.invoke('whisper:download-model', modelId);
|
||||
const downloadPromise = window.api.apikey.downloadModel(modelId);
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Download timeout after 10 minutes')), 600000)
|
||||
);
|
||||
@ -1351,7 +1346,7 @@ export class ApiKeyHeader extends LitElement {
|
||||
} finally {
|
||||
// Cleanup
|
||||
if (progressHandler) {
|
||||
ipcRenderer.removeListener('whisper:download-progress', progressHandler);
|
||||
window.api.apikey.removeOnWhisperDownloadProgress(progressHandler);
|
||||
}
|
||||
delete this.whisperInstallingModels[modelId];
|
||||
this.requestUpdate();
|
||||
@ -1412,7 +1407,7 @@ export class ApiKeyHeader extends LitElement {
|
||||
this.clearMessages();
|
||||
this.requestUpdate();
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
if (!window.api || !window.api.apikey) return;
|
||||
|
||||
try {
|
||||
// Handle LLM provider
|
||||
@ -1436,14 +1431,14 @@ export class ApiKeyHeader extends LitElement {
|
||||
}
|
||||
|
||||
// Validate Ollama is working
|
||||
llmResult = await ipcRenderer.invoke('model:validate-key', {
|
||||
llmResult = await window.api.apikey.validateKey({
|
||||
provider: 'ollama',
|
||||
key: 'local'
|
||||
});
|
||||
|
||||
if (llmResult.success) {
|
||||
// Set the selected model
|
||||
await ipcRenderer.invoke('model:set-selected-model', {
|
||||
await window.api.apikey.setSelectedModel({
|
||||
type: 'llm',
|
||||
modelId: this.selectedLlmModel
|
||||
});
|
||||
@ -1454,7 +1449,7 @@ export class ApiKeyHeader extends LitElement {
|
||||
throw new Error('Please enter LLM API key');
|
||||
}
|
||||
|
||||
llmResult = await ipcRenderer.invoke('model:validate-key', {
|
||||
llmResult = await window.api.apikey.validateKey({
|
||||
provider: this.llmProvider,
|
||||
key: this.llmApiKey.trim()
|
||||
});
|
||||
@ -1467,14 +1462,14 @@ export class ApiKeyHeader extends LitElement {
|
||||
sttResult = { success: true };
|
||||
} else if (this.sttProvider === 'whisper') {
|
||||
// For Whisper, just validate it's enabled (model download already handled in handleSttModelChange)
|
||||
sttResult = await ipcRenderer.invoke('model:validate-key', {
|
||||
sttResult = await window.api.apikey.validateKey({
|
||||
provider: 'whisper',
|
||||
key: 'local'
|
||||
});
|
||||
|
||||
if (sttResult.success && this.selectedSttModel) {
|
||||
// Set the selected model
|
||||
await ipcRenderer.invoke('model:set-selected-model', {
|
||||
await window.api.apikey.setSelectedModel({
|
||||
type: 'stt',
|
||||
modelId: this.selectedSttModel
|
||||
});
|
||||
@ -1485,7 +1480,7 @@ export class ApiKeyHeader extends LitElement {
|
||||
throw new Error('Please enter STT API key');
|
||||
}
|
||||
|
||||
sttResult = await ipcRenderer.invoke('model:validate-key', {
|
||||
sttResult = await window.api.apikey.validateKey({
|
||||
provider: this.sttProvider,
|
||||
key: this.sttApiKey.trim()
|
||||
});
|
||||
@ -1522,15 +1517,15 @@ export class ApiKeyHeader extends LitElement {
|
||||
e.preventDefault()
|
||||
|
||||
console.log("Requesting Firebase authentication from main process...")
|
||||
if (window.require) {
|
||||
window.require("electron").ipcRenderer.invoke("start-firebase-auth")
|
||||
if (window.api && window.api.apikey) {
|
||||
window.api.apikey.startFirebaseAuth()
|
||||
}
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
console.log("Close button clicked")
|
||||
if (window.require) {
|
||||
window.require("electron").ipcRenderer.invoke("quit-application")
|
||||
if (window.api && window.api.apikey) {
|
||||
window.api.apikey.quitApplication()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1543,8 +1538,8 @@ export class ApiKeyHeader extends LitElement {
|
||||
|
||||
console.log('[ApiKeyHeader] handleAnimationEnd: Animation completed, transitioning to next state...');
|
||||
|
||||
if (!window.require) {
|
||||
console.error('[ApiKeyHeader] handleAnimationEnd: window.require not available');
|
||||
if (!window.api || !window.api.apikey) {
|
||||
console.error('[ApiKeyHeader] handleAnimationEnd: window.api.apikey not available');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1553,14 +1548,12 @@ export class ApiKeyHeader extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
ipcRenderer.invoke('get-current-user')
|
||||
window.api.apikey.getCurrentUser()
|
||||
.then(userState => {
|
||||
console.log('[ApiKeyHeader] handleAnimationEnd: User state retrieved:', userState);
|
||||
|
||||
// Additional validation for local providers
|
||||
return ipcRenderer.invoke('model:are-providers-configured').then(isConfigured => {
|
||||
return window.api.apikey.areProvidersConfigured().then(isConfigured => {
|
||||
console.log('[ApiKeyHeader] handleAnimationEnd: Providers configured check:', isConfigured);
|
||||
|
||||
if (!isConfigured) {
|
||||
@ -1624,12 +1617,11 @@ export class ApiKeyHeader extends LitElement {
|
||||
}
|
||||
|
||||
// Cleanup event listeners
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.removeAllListeners('whisper:download-progress');
|
||||
ipcRenderer.removeAllListeners('ollama:install-progress');
|
||||
ipcRenderer.removeAllListeners('ollama:pull-progress');
|
||||
ipcRenderer.removeAllListeners('ollama:install-complete');
|
||||
if (window.api && window.api.apikey) {
|
||||
window.api.apikey.removeAllListeners('whisper:download-progress');
|
||||
window.api.apikey.removeAllListeners('ollama:install-progress');
|
||||
window.api.apikey.removeAllListeners('ollama:pull-progress');
|
||||
window.api.apikey.removeAllListeners('ollama:install-complete');
|
||||
}
|
||||
|
||||
// Cancel any ongoing downloads
|
@ -50,22 +50,20 @@ class HeaderTransitionManager {
|
||||
|
||||
this._bootstrap();
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
ipcRenderer.on('user-state-changed', (event, userState) => {
|
||||
if (window.api && window.api.controller) {
|
||||
window.api.controller.onUserStateChanged((event, userState) => {
|
||||
console.log('[HeaderController] Received user state change:', 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);
|
||||
if (this.apiKeyHeader) {
|
||||
this.apiKeyHeader.errorMessage = 'Authentication failed. Please try again.';
|
||||
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.');
|
||||
await this._resizeForApiKey();
|
||||
this.ensureHeader('apikey');
|
||||
@ -75,16 +73,16 @@ class HeaderTransitionManager {
|
||||
|
||||
notifyHeaderState(stateOverride) {
|
||||
const state = stateOverride || this.currentHeaderType || 'apikey';
|
||||
if (window.require) {
|
||||
window.require('electron').ipcRenderer.send('header-state-changed', state);
|
||||
if (window.api && window.api.controller) {
|
||||
window.api.controller.sendHeaderStateChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
async _bootstrap() {
|
||||
// The initial state will be sent by the main process via 'user-state-changed'
|
||||
// We just need to request it.
|
||||
if (window.require) {
|
||||
const userState = await window.require('electron').ipcRenderer.invoke('get-current-user');
|
||||
if (window.api && window.api.controller) {
|
||||
const userState = await window.api.controller.getCurrentUser();
|
||||
console.log('[HeaderController] Bootstrapping with initial user state:', userState);
|
||||
this.handleStateUpdate(userState);
|
||||
} else {
|
||||
@ -96,8 +94,8 @@ class HeaderTransitionManager {
|
||||
|
||||
//////// after_modelStateService ////////
|
||||
async handleStateUpdate(userState) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
const isConfigured = await ipcRenderer.invoke('model:are-providers-configured');
|
||||
if (!window.api || !window.api.controller) return;
|
||||
const isConfigured = await window.api.controller.areProvidersConfigured();
|
||||
|
||||
if (isConfigured) {
|
||||
const { isLoggedIn } = userState;
|
||||
@ -126,10 +124,9 @@ class HeaderTransitionManager {
|
||||
}
|
||||
|
||||
// Check if permissions were previously completed
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
if (window.api && window.api.controller) {
|
||||
try {
|
||||
const permissionsCompleted = await ipcRenderer.invoke('check-permissions-completed');
|
||||
const permissionsCompleted = await window.api.controller.checkPermissionsCompleted();
|
||||
if (permissionsCompleted) {
|
||||
console.log('[HeaderController] Permissions were previously completed, checking current status...');
|
||||
|
||||
@ -162,38 +159,30 @@ class HeaderTransitionManager {
|
||||
}
|
||||
|
||||
_resizeForMain() {
|
||||
if (!window.require) return;
|
||||
return window
|
||||
.require('electron')
|
||||
.ipcRenderer.invoke('resize-header-window', { width: 353, height: 47 })
|
||||
if (!window.api || !window.api.controller) return;
|
||||
return window.api.controller.resizeHeaderWindow({ width: 353, height: 47 })
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
async _resizeForApiKey() {
|
||||
if (!window.require) return;
|
||||
return window
|
||||
.require('electron')
|
||||
.ipcRenderer.invoke('resize-header-window', { width: 350, height: 300 })
|
||||
if (!window.api || !window.api.controller) return;
|
||||
return window.api.controller.resizeHeaderWindow({ width: 350, height: 300 })
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
async _resizeForPermissionHeader() {
|
||||
if (!window.require) return;
|
||||
return window
|
||||
.require('electron')
|
||||
.ipcRenderer.invoke('resize-header-window', { width: 285, height: 220 })
|
||||
if (!window.api || !window.api.controller) return;
|
||||
return window.api.controller.resizeHeaderWindow({ width: 285, height: 220 })
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
async checkPermissions() {
|
||||
if (!window.require) {
|
||||
if (!window.api || !window.api.controller) {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
try {
|
||||
const permissions = await ipcRenderer.invoke('check-system-permissions');
|
||||
const permissions = await window.api.controller.checkSystemPermissions();
|
||||
console.log('[HeaderController] Current permissions:', permissions);
|
||||
|
||||
if (!permissions.needsSetup) {
|
@ -362,8 +362,8 @@ export class MainHeader extends LitElement {
|
||||
async handleMouseDown(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
const initialPosition = await ipcRenderer.invoke('get-header-position');
|
||||
if (!window.api || !window.api.header) return;
|
||||
const initialPosition = await window.api.header.getHeaderPosition();
|
||||
|
||||
this.dragState = {
|
||||
initialMouseX: e.screenX,
|
||||
@ -390,8 +390,9 @@ export class MainHeader extends LitElement {
|
||||
const newWindowX = this.dragState.initialWindowX + (e.screenX - this.dragState.initialMouseX);
|
||||
const newWindowY = this.dragState.initialWindowY + (e.screenY - this.dragState.initialMouseY);
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.invoke('move-header-to', newWindowX, newWindowY);
|
||||
if (window.api && window.api.header) {
|
||||
window.api.header.moveHeaderTo(newWindowX, newWindowY);
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseUp(e) {
|
||||
@ -447,12 +448,12 @@ export class MainHeader extends LitElement {
|
||||
|
||||
if (this.classList.contains('hiding')) {
|
||||
this.classList.add('hidden');
|
||||
if (window.require) {
|
||||
window.require('electron').ipcRenderer.send('header-animation-finished', 'hidden');
|
||||
if (window.api && window.api.header) {
|
||||
window.api.header.sendAnimationFinished('hidden');
|
||||
}
|
||||
} else if (this.classList.contains('showing')) {
|
||||
if (window.require) {
|
||||
window.require('electron').ipcRenderer.send('header-animation-finished', 'visible');
|
||||
if (window.api && window.api.header) {
|
||||
window.api.header.sendAnimationFinished('visible');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -466,14 +467,12 @@ export class MainHeader extends LitElement {
|
||||
super.connectedCallback();
|
||||
this.addEventListener('animationend', this.handleAnimationEnd);
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
if (window.api && window.api.header) {
|
||||
this._sessionStateTextListener = (event, text) => {
|
||||
this.actionText = text;
|
||||
this.isTogglingSession = false;
|
||||
};
|
||||
ipcRenderer.on('session-state-text', this._sessionStateTextListener);
|
||||
window.api.header.onSessionStateText(this._sessionStateTextListener);
|
||||
|
||||
|
||||
// this._sessionStateListener = (event, { isActive }) => {
|
||||
@ -485,7 +484,7 @@ export class MainHeader extends LitElement {
|
||||
console.log('[MainHeader] Received updated 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;
|
||||
}
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
if (window.api && window.api.header) {
|
||||
if (this._sessionStateTextListener) {
|
||||
ipcRenderer.removeListener('session-state-text', this._sessionStateTextListener);
|
||||
window.api.header.removeOnSessionStateText(this._sessionStateTextListener);
|
||||
}
|
||||
// if (this._sessionStateListener) {
|
||||
// ipcRenderer.removeListener('session-state-changed', this._sessionStateListener);
|
||||
// }
|
||||
if (this._shortcutListener) {
|
||||
ipcRenderer.removeListener('shortcuts-updated', this._shortcutListener);
|
||||
window.api.header.removeOnShortcutsUpdated(this._shortcutListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
invoke(channel, ...args) {
|
||||
if (this.wasJustDragged) return;
|
||||
if (window.require) {
|
||||
window.require('electron').ipcRenderer.invoke(channel, ...args);
|
||||
if (window.api && window.api.header) {
|
||||
window.api.header.invoke(channel, ...args);
|
||||
}
|
||||
// return Promise.resolve();
|
||||
}
|
||||
|
||||
showSettingsWindow(element) {
|
||||
if (this.wasJustDragged) return;
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
if (window.api && window.api.header) {
|
||||
console.log(`[MainHeader] showSettingsWindow called at ${Date.now()}`);
|
||||
|
||||
ipcRenderer.send('cancel-hide-settings-window');
|
||||
window.api.header.cancelHideSettingsWindow();
|
||||
|
||||
if (element) {
|
||||
const { left, top, width, height } = element.getBoundingClientRect();
|
||||
ipcRenderer.send('show-settings-window', {
|
||||
window.api.header.showSettingsWindow({
|
||||
x: left,
|
||||
y: top,
|
||||
width,
|
||||
@ -542,9 +539,9 @@ export class MainHeader extends LitElement {
|
||||
|
||||
hideSettingsWindow() {
|
||||
if (this.wasJustDragged) return;
|
||||
if (window.require) {
|
||||
if (window.api && window.api.header) {
|
||||
console.log(`[MainHeader] hideSettingsWindow called at ${Date.now()}`);
|
||||
window.require('electron').ipcRenderer.send('hide-settings-window');
|
||||
window.api.header.hideSettingsWindow();
|
||||
}
|
||||
}
|
||||
|
@ -288,13 +288,12 @@ export class PermissionHeader extends LitElement {
|
||||
}
|
||||
|
||||
async checkPermissions() {
|
||||
if (!window.require || this.isChecking) return;
|
||||
|
||||
if (!window.api || !window.api.permissions || this.isChecking) return;
|
||||
|
||||
this.isChecking = true;
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
try {
|
||||
const permissions = await ipcRenderer.invoke('check-system-permissions');
|
||||
const permissions = await window.api.permissions.checkSystemPermissions();
|
||||
console.log('[PermissionHeader] Permission check result:', permissions);
|
||||
|
||||
const prevMic = this.microphoneGranted;
|
||||
@ -324,13 +323,12 @@ export class PermissionHeader extends LitElement {
|
||||
}
|
||||
|
||||
async handleMicrophoneClick() {
|
||||
if (!window.require || this.microphoneGranted === 'granted') return;
|
||||
if (!window.api || !window.api.permissions || this.microphoneGranted === 'granted') return;
|
||||
|
||||
console.log('[PermissionHeader] Requesting microphone permission...');
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
try {
|
||||
const result = await ipcRenderer.invoke('check-system-permissions');
|
||||
const result = await window.api.permissions.checkSystemPermissions();
|
||||
console.log('[PermissionHeader] Microphone permission result:', result);
|
||||
|
||||
if (result.microphone === 'granted') {
|
||||
@ -340,7 +338,7 @@ export class PermissionHeader extends LitElement {
|
||||
}
|
||||
|
||||
if (result.microphone === 'not-determined' || result.microphone === 'denied' || result.microphone === 'unknown' || result.microphone === 'restricted') {
|
||||
const res = await ipcRenderer.invoke('request-microphone-permission');
|
||||
const res = await window.api.permissions.requestMicrophonePermission();
|
||||
if (res.status === 'granted' || res.success === true) {
|
||||
this.microphoneGranted = 'granted';
|
||||
this.requestUpdate();
|
||||
@ -357,13 +355,12 @@ export class PermissionHeader extends LitElement {
|
||||
}
|
||||
|
||||
async handleScreenClick() {
|
||||
if (!window.require || this.screenGranted === 'granted') return;
|
||||
if (!window.api || !window.api.permissions || this.screenGranted === 'granted') return;
|
||||
|
||||
console.log('[PermissionHeader] Checking screen recording permission...');
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
try {
|
||||
const permissions = await ipcRenderer.invoke('check-system-permissions');
|
||||
const permissions = await window.api.permissions.checkSystemPermissions();
|
||||
console.log('[PermissionHeader] Screen permission check result:', permissions);
|
||||
|
||||
if (permissions.screen === 'granted') {
|
||||
@ -373,7 +370,7 @@ export class PermissionHeader extends LitElement {
|
||||
}
|
||||
if (permissions.screen === 'not-determined' || permissions.screen === 'denied' || permissions.screen === 'unknown' || permissions.screen === 'restricted') {
|
||||
console.log('[PermissionHeader] Opening screen recording preferences...');
|
||||
await ipcRenderer.invoke('open-system-preferences', 'screen-recording');
|
||||
await window.api.permissions.openSystemPreferences('screen-recording');
|
||||
}
|
||||
|
||||
// Check permissions again after a delay
|
||||
@ -389,10 +386,9 @@ export class PermissionHeader extends LitElement {
|
||||
this.microphoneGranted === 'granted' &&
|
||||
this.screenGranted === 'granted') {
|
||||
// Mark permissions as completed
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
if (window.api && window.api.permissions) {
|
||||
try {
|
||||
await ipcRenderer.invoke('mark-permissions-completed');
|
||||
await window.api.permissions.markPermissionsCompleted();
|
||||
console.log('[PermissionHeader] Marked permissions as completed');
|
||||
} catch (error) {
|
||||
console.error('[PermissionHeader] Error marking permissions as completed:', error);
|
||||
@ -405,8 +401,8 @@ export class PermissionHeader extends LitElement {
|
||||
|
||||
handleClose() {
|
||||
console.log('Close button clicked');
|
||||
if (window.require) {
|
||||
window.require('electron').ipcRenderer.invoke('quit-application');
|
||||
if (window.api && window.api.permissions) {
|
||||
window.api.permissions.quitApplication();
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { html, css, LitElement } from '../assets/lit-core-2.7.4.min.js';
|
||||
import { SettingsView } from '../features/settings/SettingsView.js';
|
||||
import { AssistantView } from '../features/listen/AssistantView.js';
|
||||
import { AskView } from '../features/ask/AskView.js';
|
||||
import { ShortcutSettingsView } from '../features/settings/ShortCutSettingsView.js';
|
||||
import { SettingsView } from '../settings/SettingsView.js';
|
||||
import { ListenView } from '../listen/ListenView.js';
|
||||
import { AskView } from '../ask/AskView.js';
|
||||
import { ShortcutSettingsView } from '../settings/ShortCutSettingsView.js';
|
||||
|
||||
import '../features/listen/renderer/renderer.js';
|
||||
import '../listen/audioCore/renderer.js';
|
||||
|
||||
export class PickleGlassApp extends LitElement {
|
||||
static styles = css`
|
||||
@ -17,7 +17,7 @@ export class PickleGlassApp extends LitElement {
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
assistant-view {
|
||||
listen-view {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -74,13 +74,11 @@ export class PickleGlassApp extends LitElement {
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
ipcRenderer.on('click-through-toggled', (_, isEnabled) => {
|
||||
if (window.api && window.api.app) {
|
||||
window.api.app.onClickThroughToggled((_, isEnabled) => {
|
||||
this._isClickThrough = isEnabled;
|
||||
});
|
||||
// ipcRenderer.on('start-listening-session', () => {
|
||||
// window.api.app.onStartListeningSession(() => {
|
||||
// console.log('Received start-listening-session command, calling handleListenClick.');
|
||||
// this.handleListenClick();
|
||||
// });
|
||||
@ -89,10 +87,9 @@ export class PickleGlassApp extends LitElement {
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.removeAllListeners('click-through-toggled');
|
||||
// ipcRenderer.removeAllListeners('start-listening-session');
|
||||
if (window.api && window.api.app) {
|
||||
window.api.app.removeAllListeners('click-through-toggled');
|
||||
// window.api.app.removeAllListeners('start-listening-session');
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,9 +157,8 @@ export class PickleGlassApp extends LitElement {
|
||||
// }
|
||||
|
||||
async handleClose() {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
await ipcRenderer.invoke('quit-application');
|
||||
if (window.api && window.api.app) {
|
||||
await window.api.app.quitApplication();
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,12 +168,12 @@ export class PickleGlassApp extends LitElement {
|
||||
render() {
|
||||
switch (this.currentView) {
|
||||
case 'listen':
|
||||
return html`<assistant-view
|
||||
return html`<listen-view
|
||||
.currentResponseIndex=${this.currentResponseIndex}
|
||||
.selectedProfile=${this.selectedProfile}
|
||||
.structuredData=${this.structuredData}
|
||||
@response-index-changed=${e => (this.currentResponseIndex = e.detail.index)}
|
||||
></assistant-view>`;
|
||||
></listen-view>`;
|
||||
case 'ask':
|
||||
return html`<ask-view></ask-view>`;
|
||||
case 'settings':
|
@ -230,7 +230,7 @@
|
||||
<body>
|
||||
<script src="../assets/marked-4.3.0.min.js"></script>
|
||||
|
||||
<script type="module" src="../../public/build/content.js"></script>
|
||||
<script type="module" src="../../../public/build/content.js"></script>
|
||||
|
||||
<pickle-glass-app id="pickle-glass"></pickle-glass-app>
|
||||
|
||||
@ -238,15 +238,13 @@
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const app = document.getElementById('pickle-glass');
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
if (window.api && window.api.animation) {
|
||||
// --- REFACTORED: Event-driven animation handling ---
|
||||
app.addEventListener('animationend', (event) => {
|
||||
// 숨김 애니메이션이 끝나면 main 프로세스에 알려 창을 실제로 숨깁니다.
|
||||
if (event.animationName === 'slideUpToHeader' || event.animationName === 'settingsCollapseToButton') {
|
||||
console.log(`Animation finished: ${event.animationName}. Notifying main process.`);
|
||||
ipcRenderer.send('animation-finished');
|
||||
window.api.animation.sendAnimationFinished();
|
||||
|
||||
// 완료 후 애니메이션 클래스 정리
|
||||
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');
|
||||
app.classList.remove('window-hidden', 'window-sliding-up', 'settings-window-hide');
|
||||
app.classList.add('window-sliding-down');
|
||||
});
|
||||
|
||||
ipcRenderer.on('window-hide-animation', () => {
|
||||
window.api.animation.onWindowHideAnimation(() => {
|
||||
console.log('Starting window hide animation');
|
||||
app.classList.remove('window-sliding-down', 'settings-window-show');
|
||||
app.classList.add('window-sliding-up');
|
||||
});
|
||||
|
||||
ipcRenderer.on('settings-window-hide-animation', () => {
|
||||
window.api.animation.onSettingsWindowHideAnimation(() => {
|
||||
console.log('Starting settings window hide animation');
|
||||
app.classList.remove('window-sliding-down', 'settings-window-show');
|
||||
app.classList.add('settings-window-hide');
|
||||
});
|
||||
|
||||
// --- UNCHANGED: Existing logic for listen window movement ---
|
||||
ipcRenderer.on('listen-window-move-to-center', () => {
|
||||
window.api.animation.onListenWindowMoveToCenter(() => {
|
||||
console.log('Moving listen window to center');
|
||||
app.classList.add('listen-window-moving');
|
||||
app.classList.remove('listen-window-left');
|
||||
@ -287,7 +285,7 @@
|
||||
}, 350);
|
||||
});
|
||||
|
||||
ipcRenderer.on('listen-window-move-to-left', () => {
|
||||
window.api.animation.onListenWindowMoveToLeft(() => {
|
||||
console.log('Moving listen window to left');
|
||||
app.classList.add('listen-window-moving');
|
||||
app.classList.remove('listen-window-center');
|
@ -17,7 +17,7 @@
|
||||
<div id="header-container" tabindex="0" style="outline: none;">
|
||||
</div>
|
||||
|
||||
<script type="module" src="../../public/build/header.js"></script>
|
||||
<script type="module" src="../../../public/build/header.js"></script>
|
||||
<script>
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get('glass') === 'true') {
|
@ -1,4 +1,4 @@
|
||||
import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js';
|
||||
import { html, css, LitElement } from '../../ui/assets/lit-core-2.7.4.min.js';
|
||||
|
||||
export class AskView extends LitElement {
|
||||
static properties = {
|
||||
@ -729,18 +729,13 @@ export class AskView extends LitElement {
|
||||
this.handleStreamChunk = this.handleStreamChunk.bind(this);
|
||||
this.handleStreamEnd = this.handleStreamEnd.bind(this);
|
||||
this.handleSendText = this.handleSendText.bind(this);
|
||||
this.handleGlobalSendRequest = this.handleGlobalSendRequest.bind(this);
|
||||
this.handleTextKeydown = this.handleTextKeydown.bind(this);
|
||||
this.closeResponsePanel = this.closeResponsePanel.bind(this);
|
||||
this.handleCopy = this.handleCopy.bind(this);
|
||||
this.clearResponseContent = this.clearResponseContent.bind(this);
|
||||
this.processAssistantQuestion = this.processAssistantQuestion.bind(this);
|
||||
this.handleToggleTextInput = this.handleToggleTextInput.bind(this);
|
||||
this.handleEscKey = this.handleEscKey.bind(this);
|
||||
this.handleDocumentClick = this.handleDocumentClick.bind(this);
|
||||
this.handleWindowBlur = this.handleWindowBlur.bind(this);
|
||||
|
||||
this.handleScroll = this.handleScroll.bind(this);
|
||||
this.handleCloseAskWindow = this.handleCloseAskWindow.bind(this);
|
||||
this.handleCloseIfNoContent = this.handleCloseIfNoContent.bind(this);
|
||||
|
||||
this.loadLibraries();
|
||||
|
||||
@ -748,6 +743,94 @@ export class AskView extends LitElement {
|
||||
this.isThrottled = false;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
console.log('📱 AskView connectedCallback - IPC 이벤트 리스너 설정');
|
||||
|
||||
document.addEventListener('keydown', this.handleEscKey);
|
||||
|
||||
this.resizeObserver = new ResizeObserver(entries => {
|
||||
for (const entry of entries) {
|
||||
const needed = entry.contentRect.height;
|
||||
const current = window.innerHeight;
|
||||
|
||||
if (needed > current - 4) {
|
||||
this.requestWindowResize(Math.ceil(needed));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const container = this.shadowRoot?.querySelector('.ask-container');
|
||||
if (container) this.resizeObserver.observe(container);
|
||||
|
||||
this.handleQuestionFromAssistant = (event, question) => {
|
||||
console.log('📨 AskView: Received question from ListenView:', question);
|
||||
this.handleSendText(null, question);
|
||||
};
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.on('ask:sendQuestionToRenderer', this.handleQuestionFromAssistant);
|
||||
ipcRenderer.on('hide-text-input', () => {
|
||||
console.log('📤 Hide text input signal received');
|
||||
this.showTextInput = false;
|
||||
this.requestUpdate();
|
||||
});
|
||||
ipcRenderer.on('ask:showTextInput', () => {
|
||||
console.log('📤 Show text input signal received');
|
||||
if (!this.showTextInput) {
|
||||
this.showTextInput = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on('ask-response-chunk', this.handleStreamChunk);
|
||||
ipcRenderer.on('ask-response-stream-end', this.handleStreamEnd);
|
||||
|
||||
ipcRenderer.on('scroll-response-up', () => this.handleScroll('up'));
|
||||
ipcRenderer.on('scroll-response-down', () => this.handleScroll('down'));
|
||||
console.log('✅ AskView: IPC 이벤트 리스너 등록 완료');
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.resizeObserver?.disconnect();
|
||||
|
||||
console.log('📱 AskView disconnectedCallback - IPC 이벤트 리스너 제거');
|
||||
|
||||
document.removeEventListener('keydown', this.handleEscKey);
|
||||
|
||||
if (this.copyTimeout) {
|
||||
clearTimeout(this.copyTimeout);
|
||||
}
|
||||
|
||||
if (this.headerAnimationTimeout) {
|
||||
clearTimeout(this.headerAnimationTimeout);
|
||||
}
|
||||
|
||||
if (this.streamingTimeout) {
|
||||
clearTimeout(this.streamingTimeout);
|
||||
}
|
||||
|
||||
Object.values(this.lineCopyTimeouts).forEach(timeout => clearTimeout(timeout));
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.removeListener('hide-text-input', () => { });
|
||||
ipcRenderer.removeListener('ask:showTextInput', () => { });
|
||||
|
||||
ipcRenderer.removeListener('ask-response-chunk', this.handleStreamChunk);
|
||||
ipcRenderer.removeListener('ask-response-stream-end', this.handleStreamEnd);
|
||||
|
||||
ipcRenderer.removeListener('scroll-response-up', () => this.handleScroll('up'));
|
||||
ipcRenderer.removeListener('scroll-response-down', () => this.handleScroll('down'));
|
||||
console.log('✅ AskView: IPC 이벤트 리스너 제거 완료');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async loadLibraries() {
|
||||
try {
|
||||
if (!window.marked) {
|
||||
@ -804,38 +887,49 @@ export class AskView extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
handleDocumentClick(e) {
|
||||
handleCloseAskWindow() {
|
||||
this.clearResponseContent();
|
||||
ipcRenderer.invoke('ask:closeAskWindow');
|
||||
}
|
||||
|
||||
handleCloseIfNoContent() {
|
||||
if (!this.currentResponse && !this.isLoading && !this.isStreaming) {
|
||||
const askContainer = this.shadowRoot?.querySelector('.ask-container');
|
||||
if (askContainer && !e.composedPath().includes(askContainer)) {
|
||||
this.closeIfNoContent();
|
||||
}
|
||||
this.handleCloseAskWindow();
|
||||
}
|
||||
}
|
||||
|
||||
handleEscKey(e) {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
this.closeResponsePanel();
|
||||
this.handleCloseIfNoContent();
|
||||
}
|
||||
}
|
||||
|
||||
handleWindowBlur() {
|
||||
if (!this.currentResponse && !this.isLoading && !this.isStreaming) {
|
||||
// If there's no active content, ask the main process to close this window.
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.invoke('close-ask-window-if-empty');
|
||||
clearResponseContent() {
|
||||
this.currentResponse = '';
|
||||
this.currentQuestion = '';
|
||||
this.isLoading = false;
|
||||
this.isStreaming = false;
|
||||
this.headerText = 'AI Response';
|
||||
this.showTextInput = true;
|
||||
this.accumulatedResponse = '';
|
||||
this.requestUpdate();
|
||||
this.renderContent();
|
||||
}
|
||||
|
||||
handleInputFocus() {
|
||||
this.isInputFocused = true;
|
||||
}
|
||||
|
||||
focusTextInput() {
|
||||
requestAnimationFrame(() => {
|
||||
const textInput = this.shadowRoot?.getElementById('textInput');
|
||||
if (textInput) {
|
||||
textInput.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
closeIfNoContent() {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.invoke('force-close-window', 'ask');
|
||||
}
|
||||
}
|
||||
|
||||
loadScript(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -875,125 +969,6 @@ export class AskView extends LitElement {
|
||||
return text;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
console.log('📱 AskView connectedCallback - IPC 이벤트 리스너 설정');
|
||||
|
||||
document.addEventListener('click', this.handleDocumentClick, true);
|
||||
document.addEventListener('keydown', this.handleEscKey);
|
||||
|
||||
this.resizeObserver = new ResizeObserver(entries => {
|
||||
for (const entry of entries) {
|
||||
const needed = entry.contentRect.height;
|
||||
const current = window.innerHeight;
|
||||
|
||||
if (needed > current - 4) {
|
||||
this.requestWindowResize(Math.ceil(needed));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const container = this.shadowRoot?.querySelector('.ask-container');
|
||||
if (container) this.resizeObserver.observe(container);
|
||||
|
||||
this.handleQuestionFromAssistant = (event, question) => {
|
||||
console.log('📨 AskView: Received question from AssistantView:', question);
|
||||
this.currentResponse = '';
|
||||
this.isStreaming = false;
|
||||
this.requestUpdate();
|
||||
|
||||
this.currentQuestion = question;
|
||||
this.isLoading = true;
|
||||
this.showTextInput = false;
|
||||
this.headerText = 'analyzing screen...';
|
||||
this.startHeaderAnimation();
|
||||
this.requestUpdate();
|
||||
|
||||
this.processAssistantQuestion(question);
|
||||
};
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.on('ask-global-send', this.handleGlobalSendRequest);
|
||||
ipcRenderer.on('toggle-text-input', this.handleToggleTextInput);
|
||||
ipcRenderer.on('receive-question-from-assistant', this.handleQuestionFromAssistant);
|
||||
ipcRenderer.on('hide-text-input', () => {
|
||||
console.log('📤 Hide text input signal received');
|
||||
this.showTextInput = false;
|
||||
this.requestUpdate();
|
||||
});
|
||||
ipcRenderer.on('clear-ask-response', () => {
|
||||
console.log('📤 Clear response signal received');
|
||||
this.currentResponse = '';
|
||||
this.isStreaming = false;
|
||||
this.isLoading = false;
|
||||
this.headerText = 'AI Response';
|
||||
this.requestUpdate();
|
||||
});
|
||||
ipcRenderer.on('window-hide-animation', () => {
|
||||
console.log('📤 Ask window hiding - clearing response content');
|
||||
setTimeout(() => {
|
||||
this.clearResponseContent();
|
||||
}, 250);
|
||||
});
|
||||
ipcRenderer.on('window-blur', this.handleWindowBlur);
|
||||
ipcRenderer.on('window-did-show', () => {
|
||||
if (!this.currentResponse && !this.isLoading && !this.isStreaming) {
|
||||
this.focusTextInput();
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on('ask-response-chunk', this.handleStreamChunk);
|
||||
ipcRenderer.on('ask-response-stream-end', this.handleStreamEnd);
|
||||
|
||||
ipcRenderer.on('scroll-response-up', () => this.handleScroll('up'));
|
||||
ipcRenderer.on('scroll-response-down', () => this.handleScroll('down'));
|
||||
console.log('✅ AskView: IPC 이벤트 리스너 등록 완료');
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.resizeObserver?.disconnect();
|
||||
|
||||
console.log('📱 AskView disconnectedCallback - IPC 이벤트 리스너 제거');
|
||||
|
||||
document.removeEventListener('click', this.handleDocumentClick, true);
|
||||
document.removeEventListener('keydown', this.handleEscKey);
|
||||
|
||||
if (this.copyTimeout) {
|
||||
clearTimeout(this.copyTimeout);
|
||||
}
|
||||
|
||||
if (this.headerAnimationTimeout) {
|
||||
clearTimeout(this.headerAnimationTimeout);
|
||||
}
|
||||
|
||||
if (this.streamingTimeout) {
|
||||
clearTimeout(this.streamingTimeout);
|
||||
}
|
||||
|
||||
Object.values(this.lineCopyTimeouts).forEach(timeout => clearTimeout(timeout));
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.removeListener('ask-global-send', this.handleGlobalSendRequest);
|
||||
ipcRenderer.removeListener('toggle-text-input', this.handleToggleTextInput);
|
||||
ipcRenderer.removeListener('clear-ask-response', () => { });
|
||||
ipcRenderer.removeListener('hide-text-input', () => { });
|
||||
ipcRenderer.removeListener('window-hide-animation', () => { });
|
||||
ipcRenderer.removeListener('window-blur', this.handleWindowBlur);
|
||||
|
||||
ipcRenderer.removeListener('ask-response-chunk', this.handleStreamChunk);
|
||||
ipcRenderer.removeListener('ask-response-stream-end', this.handleStreamEnd);
|
||||
|
||||
ipcRenderer.removeListener('scroll-response-up', () => this.handleScroll('up'));
|
||||
ipcRenderer.removeListener('scroll-response-down', () => this.handleScroll('down'));
|
||||
console.log('✅ AskView: IPC 이벤트 리스너 제거 완료');
|
||||
}
|
||||
}
|
||||
|
||||
handleScroll(direction) {
|
||||
const scrollableElement = this.shadowRoot.querySelector('#responseContainer');
|
||||
if (scrollableElement) {
|
||||
@ -1136,27 +1111,10 @@ export class AskView extends LitElement {
|
||||
this.adjustWindowHeightThrottled();
|
||||
}
|
||||
|
||||
clearResponseContent() {
|
||||
this.currentResponse = '';
|
||||
this.currentQuestion = '';
|
||||
this.isLoading = false;
|
||||
this.isStreaming = false;
|
||||
this.headerText = 'AI Response';
|
||||
this.showTextInput = true;
|
||||
this.accumulatedResponse = '';
|
||||
this.requestUpdate();
|
||||
this.renderContent(); // 👈 updateResponseContent() 대신 renderContent() 호출
|
||||
}
|
||||
|
||||
handleToggleTextInput() {
|
||||
this.showTextInput = !this.showTextInput;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
requestWindowResize(targetHeight) {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.invoke('adjust-window-height', targetHeight);
|
||||
if (window.api && window.api.ask) {
|
||||
window.api.ask.adjustWindowHeight(targetHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1196,13 +1154,6 @@ export class AskView extends LitElement {
|
||||
.replace(/`(.*?)`/g, '<code>$1</code>');
|
||||
}
|
||||
|
||||
closeResponsePanel() {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.invoke('force-close-window', 'ask');
|
||||
}
|
||||
}
|
||||
|
||||
fixIncompleteMarkdown(text) {
|
||||
if (!text) return text;
|
||||
|
||||
@ -1240,29 +1191,6 @@ export class AskView extends LitElement {
|
||||
return text;
|
||||
}
|
||||
|
||||
// ✨ processAssistantQuestion 수정
|
||||
async processAssistantQuestion(question) {
|
||||
this.currentQuestion = question;
|
||||
this.showTextInput = false;
|
||||
this.isLoading = true;
|
||||
this.isStreaming = false;
|
||||
this.currentResponse = '';
|
||||
this.accumulatedResponse = '';
|
||||
this.startHeaderAnimation();
|
||||
this.requestUpdate();
|
||||
this.renderContent();
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.invoke('ask:sendMessage', question).catch(error => {
|
||||
console.error('Error processing assistant question:', error);
|
||||
this.isLoading = false;
|
||||
this.isStreaming = false;
|
||||
this.currentResponse = `Error: ${error.message}`;
|
||||
this.renderContent();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async handleCopy() {
|
||||
if (this.copyState === 'copied') return;
|
||||
@ -1332,10 +1260,9 @@ export class AskView extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
async handleSendText() {
|
||||
async handleSendText(e, overridingText = '') {
|
||||
const textInput = this.shadowRoot?.getElementById('textInput');
|
||||
if (!textInput) return;
|
||||
const text = textInput.value.trim();
|
||||
const text = (overridingText || textInput?.value || '').trim();
|
||||
if (!text) return;
|
||||
|
||||
textInput.value = '';
|
||||
@ -1351,9 +1278,8 @@ export class AskView extends LitElement {
|
||||
this.requestUpdate();
|
||||
this.renderContent();
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.invoke('ask:sendMessage', text).catch(error => {
|
||||
if (window.api && window.api.ask) {
|
||||
window.api.ask.sendMessage(text).catch(error => {
|
||||
console.error('Error sending text:', error);
|
||||
this.isLoading = false;
|
||||
this.isStreaming = false;
|
||||
@ -1393,37 +1319,10 @@ export class AskView extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
focusTextInput() {
|
||||
requestAnimationFrame(() => {
|
||||
const textInput = this.shadowRoot?.getElementById('textInput');
|
||||
if (textInput) {
|
||||
textInput.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
setTimeout(() => this.adjustWindowHeight(), 200);
|
||||
}
|
||||
|
||||
handleGlobalSendRequest() {
|
||||
const textInput = this.shadowRoot?.getElementById('textInput');
|
||||
|
||||
if (!this.showTextInput) {
|
||||
this.showTextInput = true;
|
||||
this.requestUpdate();
|
||||
this.focusTextInput();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!textInput) return;
|
||||
|
||||
textInput.focus();
|
||||
|
||||
if (!textInput.value.trim()) return;
|
||||
|
||||
this.handleSendText();
|
||||
}
|
||||
|
||||
getTruncatedQuestion(question, maxLength = 30) {
|
||||
if (!question) return '';
|
||||
@ -1431,24 +1330,7 @@ export class AskView extends LitElement {
|
||||
return question.substring(0, maxLength) + '...';
|
||||
}
|
||||
|
||||
handleInputFocus() {
|
||||
this.isInputFocused = true;
|
||||
}
|
||||
|
||||
handleInputBlur(e) {
|
||||
this.isInputFocused = false;
|
||||
|
||||
// 잠시 후 포커스가 다른 곳으로 갔는지 확인
|
||||
setTimeout(() => {
|
||||
const activeElement = this.shadowRoot?.activeElement || document.activeElement;
|
||||
const textInput = this.shadowRoot?.getElementById('textInput');
|
||||
|
||||
// 포커스가 AskView 내부가 아니고, 응답이 없는 경우
|
||||
if (!this.currentResponse && !this.isLoading && !this.isStreaming && activeElement !== textInput && !this.isInputFocused) {
|
||||
this.closeIfNoContent();
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasResponse = this.isLoading || this.currentResponse || this.isStreaming;
|
||||
@ -1486,7 +1368,7 @@ export class AskView extends LitElement {
|
||||
<path d="M20 6L9 17l-5-5" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="close-button" @click=${this.closeResponsePanel}>
|
||||
<button class="close-button" @click=${this.handleCloseAskWindow}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
@ -1509,7 +1391,6 @@ export class AskView extends LitElement {
|
||||
placeholder="Ask about your screen or audio"
|
||||
@keydown=${this.handleTextKeydown}
|
||||
@focus=${this.handleInputFocus}
|
||||
@blur=${this.handleInputBlur}
|
||||
/>
|
||||
<button
|
||||
class="submit-btn"
|
||||
@ -1527,7 +1408,7 @@ export class AskView extends LitElement {
|
||||
|
||||
// Dynamically resize the BrowserWindow to fit current content
|
||||
adjustWindowHeight() {
|
||||
if (!window.require) return;
|
||||
if (!window.api || !window.api.ask) return;
|
||||
|
||||
this.updateComplete.then(() => {
|
||||
const headerEl = this.shadowRoot.querySelector('.response-header');
|
||||
@ -1544,8 +1425,7 @@ export class AskView extends LitElement {
|
||||
|
||||
const targetHeight = Math.min(700, idealHeight);
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.invoke('adjust-window-height', targetHeight);
|
||||
window.api.ask.adjustWindowHeight(targetHeight);
|
||||
|
||||
}).catch(err => console.error('AskView adjustWindowHeight error:', err));
|
||||
}
|
Before Width: | Height: | Size: 877 B After Width: | Height: | Size: 877 B |
Before Width: | Height: | Size: 226 B After Width: | Height: | Size: 226 B |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@ -1,8 +1,8 @@
|
||||
import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js';
|
||||
import { html, css, LitElement } from '../assets/lit-core-2.7.4.min.js';
|
||||
import './stt/SttView.js';
|
||||
import './summary/SummaryView.js';
|
||||
|
||||
export class AssistantView extends LitElement {
|
||||
export class ListenView extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
@ -453,9 +453,8 @@ export class AssistantView extends LitElement {
|
||||
if (this.isSessionActive) {
|
||||
this.startTimer();
|
||||
}
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.on('session-state-changed', (event, { isActive }) => {
|
||||
if (window.api && window.api.listen) {
|
||||
window.api.listen.onSessionStateChanged((event, { isActive }) => {
|
||||
const wasActive = this.isSessionActive;
|
||||
this.isSessionActive = isActive;
|
||||
|
||||
@ -514,7 +513,7 @@ export class AssistantView extends LitElement {
|
||||
}
|
||||
|
||||
adjustWindowHeight() {
|
||||
if (!window.require) return;
|
||||
if (!window.api || !window.api.listen) return;
|
||||
|
||||
this.updateComplete
|
||||
.then(() => {
|
||||
@ -537,8 +536,7 @@ export class AssistantView extends LitElement {
|
||||
`[Height Adjusted] Mode: ${this.viewMode}, TopBar: ${topBarHeight}px, Content: ${contentHeight}px, Ideal: ${idealHeight}px, Target: ${targetHeight}px`
|
||||
);
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.invoke('adjust-window-height', targetHeight);
|
||||
window.api.listen.adjustWindowHeight(targetHeight);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error in adjustWindowHeight:', error);
|
||||
@ -689,4 +687,4 @@ export class AssistantView extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('assistant-view', AssistantView);
|
||||
customElements.define('listen-view', ListenView);
|
@ -1,5 +1,5 @@
|
||||
const { ipcRenderer } = require('electron');
|
||||
const createAecModule = require('../../../assets/aec.js');
|
||||
const createAecModule = require('./aec.js');
|
||||
|
||||
let aecModPromise = null; // 한 번만 로드
|
||||
let aecMod = null;
|
||||
@ -198,7 +198,7 @@ function runAecSync(micF32, sysF32) {
|
||||
|
||||
|
||||
// System audio data handler
|
||||
ipcRenderer.on('system-audio-data', (event, { data }) => {
|
||||
window.api.audio.onSystemAudioData((event, { data }) => {
|
||||
systemAudioBuffer.push({
|
||||
data: data,
|
||||
timestamp: Date.now(),
|
||||
@ -336,7 +336,7 @@ async function setupMicProcessing(micStream) {
|
||||
const pcm16 = convertFloat32ToInt16(processedChunk);
|
||||
const b64 = arrayBufferToBase64(pcm16.buffer);
|
||||
|
||||
ipcRenderer.invoke('send-audio-content', {
|
||||
window.api.audio.sendAudioContent({
|
||||
data: b64,
|
||||
mimeType: 'audio/pcm;rate=24000',
|
||||
});
|
||||
@ -369,7 +369,7 @@ function setupLinuxMicProcessing(micStream) {
|
||||
const pcmData16 = convertFloat32ToInt16(chunk);
|
||||
const base64Data = arrayBufferToBase64(pcmData16.buffer);
|
||||
|
||||
await ipcRenderer.invoke('send-audio-content', {
|
||||
await window.api.audio.sendAudioContent({
|
||||
data: base64Data,
|
||||
mimeType: 'audio/pcm;rate=24000',
|
||||
});
|
||||
@ -403,7 +403,7 @@ function setupSystemAudioProcessing(systemStream) {
|
||||
const base64Data = arrayBufferToBase64(pcmData16.buffer);
|
||||
|
||||
try {
|
||||
await ipcRenderer.invoke('send-system-audio-content', {
|
||||
await window.api.audio.sendSystemAudioContent({
|
||||
data: base64Data,
|
||||
mimeType: 'audio/pcm;rate=24000',
|
||||
});
|
||||
@ -433,7 +433,7 @@ async function captureScreenshot(imageQuality = 'medium', isManual = false) {
|
||||
|
||||
try {
|
||||
// Request screenshot from main process
|
||||
const result = await ipcRenderer.invoke('capture-screenshot', {
|
||||
const result = await window.api.audio.captureScreenshot({
|
||||
quality: imageQuality,
|
||||
});
|
||||
|
||||
@ -470,7 +470,7 @@ async function captureManualScreenshot(imageQuality = null) {
|
||||
async function getCurrentScreenshot() {
|
||||
try {
|
||||
// First try to get a fresh screenshot from main process
|
||||
const result = await ipcRenderer.invoke('get-current-screenshot');
|
||||
const result = await window.api.audio.getCurrentScreenshot();
|
||||
|
||||
if (result.success && result.base64) {
|
||||
console.log('📸 Got fresh screenshot from main process');
|
||||
@ -479,7 +479,7 @@ async function getCurrentScreenshot() {
|
||||
|
||||
// If no screenshot available, capture one now
|
||||
console.log('📸 No screenshot available, capturing new one');
|
||||
const captureResult = await ipcRenderer.invoke('capture-screenshot', {
|
||||
const captureResult = await window.api.audio.captureScreenshot({
|
||||
quality: currentImageQuality,
|
||||
});
|
||||
|
||||
@ -518,15 +518,15 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
|
||||
console.log('Starting macOS capture with SystemAudioDump...');
|
||||
|
||||
// Start macOS audio capture
|
||||
const audioResult = await ipcRenderer.invoke('start-macos-audio');
|
||||
const audioResult = await window.api.audio.startMacosAudio();
|
||||
if (!audioResult.success) {
|
||||
console.warn('[listenCapture] macOS audio start failed:', audioResult.error);
|
||||
|
||||
// 이미 실행 중 → stop 후 재시도
|
||||
if (audioResult.error === 'already_running') {
|
||||
await ipcRenderer.invoke('stop-macos-audio');
|
||||
await window.api.audio.stopMacosAudio();
|
||||
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) {
|
||||
throw new Error('Retry failed: ' + retry.error);
|
||||
}
|
||||
@ -536,7 +536,7 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
|
||||
}
|
||||
|
||||
// Initialize screen capture in main process
|
||||
const screenResult = await ipcRenderer.invoke('start-screen-capture');
|
||||
const screenResult = await window.api.audio.startScreenCapture();
|
||||
if (!screenResult.success) {
|
||||
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...');
|
||||
|
||||
// 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) {
|
||||
throw new Error('Failed to start screen capture: ' + screenResult.error);
|
||||
}
|
||||
|
||||
// Ensure STT sessions are initialized before starting audio capture
|
||||
const sessionActive = await ipcRenderer.invoke('is-session-active');
|
||||
const sessionActive = await window.api.audio.isSessionActive();
|
||||
if (!sessionActive) {
|
||||
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
|
||||
ipcRenderer.invoke('stop-screen-capture').catch(err => {
|
||||
window.api.audio.stopScreenCapture().catch(err => {
|
||||
console.error('Error stopping screen capture:', err);
|
||||
});
|
||||
|
||||
// Stop macOS audio capture if running
|
||||
if (isMacOS) {
|
||||
ipcRenderer.invoke('stop-macos-audio').catch(err => {
|
||||
window.api.audio.stopMacosAudio().catch(err => {
|
||||
console.error('Error stopping macOS audio:', err);
|
||||
});
|
||||
}
|
@ -15,7 +15,7 @@ window.pickleGlass = {
|
||||
};
|
||||
|
||||
|
||||
ipcRenderer.on('change-listen-capture-state', (_event, { status }) => {
|
||||
window.api.audio.onChangeListenCaptureState((_event, { status }) => {
|
||||
if (!isListenView) {
|
||||
console.log('[Renderer] Non-listen view: ignoring capture-state change');
|
||||
return;
|
@ -1,4 +1,4 @@
|
||||
import { html, css, LitElement } from '../../../assets/lit-core-2.7.4.min.js';
|
||||
import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js';
|
||||
|
||||
export class SttView extends LitElement {
|
||||
static styles = css`
|
||||
@ -95,17 +95,15 @@ export class SttView extends LitElement {
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.on('stt-update', this.handleSttUpdate);
|
||||
if (window.api && window.api.listen) {
|
||||
window.api.listen.onSttUpdate(this.handleSttUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.removeListener('stt-update', this.handleSttUpdate);
|
||||
if (window.api && window.api.listen) {
|
||||
window.api.listen.removeOnSttUpdate(this.handleSttUpdate);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { html, css, LitElement } from '../../../assets/lit-core-2.7.4.min.js';
|
||||
import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js';
|
||||
|
||||
export class SummaryView extends LitElement {
|
||||
static styles = css`
|
||||
@ -262,9 +262,8 @@ export class SummaryView extends LitElement {
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.on('summary-update', (event, data) => {
|
||||
if (window.api && window.api.listen) {
|
||||
window.api.listen.onSummaryUpdate((event, data) => {
|
||||
this.structuredData = data;
|
||||
this.requestUpdate();
|
||||
});
|
||||
@ -273,9 +272,8 @@ export class SummaryView extends LitElement {
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.removeAllListeners('summary-update');
|
||||
if (window.api && window.api.listen) {
|
||||
window.api.listen.removeOnSummaryUpdate(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
@ -408,18 +406,9 @@ export class SummaryView extends LitElement {
|
||||
async handleRequestClick(requestText) {
|
||||
console.log('🔥 Analysis request clicked:', requestText);
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
if (window.api && window.api.listen) {
|
||||
try {
|
||||
const isAskViewVisible = await ipcRenderer.invoke('is-ask-window-visible', 'ask');
|
||||
|
||||
if (!isAskViewVisible) {
|
||||
await ipcRenderer.invoke('toggle-feature', 'ask');
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
const result = await ipcRenderer.invoke('send-question-to-ask', requestText);
|
||||
const result = await ipcRenderer.invoke('ask:sendQuestionToMain', requestText);
|
||||
|
||||
if (result.success) {
|
||||
console.log('✅ Question sent to AskView successfully');
|
@ -1,5 +1,5 @@
|
||||
import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js';
|
||||
import { getOllamaProgressTracker } from '../../common/services/localProgressTracker.js';
|
||||
import { html, css, LitElement } from '../assets/lit-core-2.7.4.min.js';
|
||||
import { getOllamaProgressTracker } from '../../features/common/services/localProgressTracker.js';
|
||||
|
||||
export class SettingsView extends LitElement {
|
||||
static styles = css`
|
||||
@ -543,11 +543,9 @@ export class SettingsView extends LitElement {
|
||||
}
|
||||
|
||||
async loadAutoUpdateSetting() {
|
||||
if (!window.require) return;
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
this.autoUpdateLoading = true;
|
||||
try {
|
||||
const enabled = await ipcRenderer.invoke('settings:get-auto-update');
|
||||
const enabled = await window.api.feature.settings.getAutoUpdate();
|
||||
this.autoUpdateEnabled = enabled;
|
||||
console.log('Auto-update setting loaded:', enabled);
|
||||
} catch (e) {
|
||||
@ -559,13 +557,12 @@ export class SettingsView extends LitElement {
|
||||
}
|
||||
|
||||
async handleToggleAutoUpdate() {
|
||||
if (!window.require || this.autoUpdateLoading) return;
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
if (this.autoUpdateLoading) return;
|
||||
this.autoUpdateLoading = true;
|
||||
this.requestUpdate();
|
||||
try {
|
||||
const newValue = !this.autoUpdateEnabled;
|
||||
const result = await ipcRenderer.invoke('settings:set-auto-update', newValue);
|
||||
const result = await window.api.feature.settings.setAutoUpdate(newValue);
|
||||
if (result && result.success) {
|
||||
this.autoUpdateEnabled = newValue;
|
||||
} else {
|
||||
@ -580,22 +577,20 @@ export class SettingsView extends LitElement {
|
||||
|
||||
//////// after_modelStateService ////////
|
||||
async loadInitialData() {
|
||||
if (!window.require) return;
|
||||
this.isLoading = true;
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
try {
|
||||
const [userState, config, storedKeys, availableLlm, availableStt, selectedModels, presets, contentProtection, shortcuts, ollamaStatus, whisperModelsResult] = await Promise.all([
|
||||
ipcRenderer.invoke('get-current-user'),
|
||||
ipcRenderer.invoke('model:get-provider-config'), // Provider 설정 로드
|
||||
ipcRenderer.invoke('model:get-all-keys'),
|
||||
ipcRenderer.invoke('model:get-available-models', { type: 'llm' }),
|
||||
ipcRenderer.invoke('model:get-available-models', { type: 'stt' }),
|
||||
ipcRenderer.invoke('model:get-selected-models'),
|
||||
ipcRenderer.invoke('settings:getPresets'),
|
||||
ipcRenderer.invoke('get-content-protection-status'),
|
||||
ipcRenderer.invoke('get-current-shortcuts'),
|
||||
ipcRenderer.invoke('ollama:get-status'),
|
||||
ipcRenderer.invoke('whisper:get-installed-models')
|
||||
window.api.feature.settings.getCurrentUser(),
|
||||
window.api.feature.settings.getProviderConfig(), // Provider 설정 로드
|
||||
window.api.feature.settings.getAllKeys(),
|
||||
window.api.feature.settings.getAvailableModels({ type: 'llm' }),
|
||||
window.api.feature.settings.getAvailableModels({ type: 'stt' }),
|
||||
window.api.feature.settings.getSelectedModels(),
|
||||
window.api.feature.settings.getPresets(),
|
||||
window.api.feature.settings.getContentProtectionStatus(),
|
||||
window.api.feature.settings.getCurrentShortcuts(),
|
||||
window.api.feature.settings.getOllamaStatus(),
|
||||
window.api.feature.settings.getWhisperInstalledModels()
|
||||
]);
|
||||
|
||||
if (userState && userState.isLoggedIn) this.firebaseUser = userState;
|
||||
@ -644,10 +639,9 @@ export class SettingsView extends LitElement {
|
||||
// For Ollama, we need to ensure it's ready first
|
||||
if (provider === 'ollama') {
|
||||
this.saving = true;
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
// First ensure Ollama is installed and running
|
||||
const ensureResult = await ipcRenderer.invoke('ollama:ensure-ready');
|
||||
const ensureResult = await window.api.feature.settings.ensureOllamaReady();
|
||||
if (!ensureResult.success) {
|
||||
alert(`Failed to setup Ollama: ${ensureResult.error}`);
|
||||
this.saving = false;
|
||||
@ -655,7 +649,7 @@ export class SettingsView extends LitElement {
|
||||
}
|
||||
|
||||
// Now validate (which will check if service is running)
|
||||
const result = await ipcRenderer.invoke('model:validate-key', { provider, key: 'local' });
|
||||
const result = await window.api.feature.settings.validateKey({ provider, key: 'local' });
|
||||
|
||||
if (result.success) {
|
||||
this.apiKeys = { ...this.apiKeys, [provider]: 'local' };
|
||||
@ -671,8 +665,7 @@ export class SettingsView extends LitElement {
|
||||
// For Whisper, just enable it
|
||||
if (provider === 'whisper') {
|
||||
this.saving = true;
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
const result = await ipcRenderer.invoke('model:validate-key', { provider, key: 'local' });
|
||||
const result = await window.api.feature.settings.validateKey({ provider, key: 'local' });
|
||||
|
||||
if (result.success) {
|
||||
this.apiKeys = { ...this.apiKeys, [provider]: 'local' };
|
||||
@ -686,8 +679,7 @@ export class SettingsView extends LitElement {
|
||||
|
||||
// For other providers, use the normal flow
|
||||
this.saving = true;
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
const result = await ipcRenderer.invoke('model:validate-key', { provider, key });
|
||||
const result = await window.api.feature.settings.validateKey({ provider, key });
|
||||
|
||||
if (result.success) {
|
||||
this.apiKeys = { ...this.apiKeys, [provider]: key };
|
||||
@ -701,20 +693,18 @@ export class SettingsView extends LitElement {
|
||||
|
||||
async handleClearKey(provider) {
|
||||
this.saving = true;
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
await ipcRenderer.invoke('model:remove-api-key', { provider });
|
||||
await window.api.feature.settings.removeApiKey({ provider });
|
||||
this.apiKeys = { ...this.apiKeys, [provider]: '' };
|
||||
await this.refreshModelData();
|
||||
this.saving = false;
|
||||
}
|
||||
|
||||
async refreshModelData() {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
const [availableLlm, availableStt, selected, storedKeys] = await Promise.all([
|
||||
ipcRenderer.invoke('model:get-available-models', { type: 'llm' }),
|
||||
ipcRenderer.invoke('model:get-available-models', { type: 'stt' }),
|
||||
ipcRenderer.invoke('model:get-selected-models'),
|
||||
ipcRenderer.invoke('model:get-all-keys')
|
||||
window.api.feature.settings.getAvailableModels({ type: 'llm' }),
|
||||
window.api.feature.settings.getAvailableModels({ type: 'stt' }),
|
||||
window.api.feature.settings.getSelectedModels(),
|
||||
window.api.feature.settings.getAllKeys()
|
||||
]);
|
||||
this.availableLlmModels = availableLlm;
|
||||
this.availableSttModels = availableStt;
|
||||
@ -765,8 +755,7 @@ export class SettingsView extends LitElement {
|
||||
}
|
||||
|
||||
this.saving = true;
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
await ipcRenderer.invoke('model:set-selected-model', { type, modelId });
|
||||
await window.api.feature.settings.setSelectedModel({ type, modelId });
|
||||
if (type === 'llm') this.selectedLlm = modelId;
|
||||
if (type === 'stt') this.selectedStt = modelId;
|
||||
this.isLlmListVisible = false;
|
||||
@ -776,8 +765,7 @@ export class SettingsView extends LitElement {
|
||||
}
|
||||
|
||||
async refreshOllamaStatus() {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
const ollamaStatus = await ipcRenderer.invoke('ollama:get-status');
|
||||
const ollamaStatus = await window.api.feature.settings.getOllamaStatus();
|
||||
if (ollamaStatus?.success) {
|
||||
this.ollamaStatus = { installed: ollamaStatus.installed, running: ollamaStatus.running };
|
||||
this.ollamaModels = ollamaStatus.models || [];
|
||||
@ -821,8 +809,6 @@ export class SettingsView extends LitElement {
|
||||
this.requestUpdate();
|
||||
|
||||
try {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
// Set up progress listener
|
||||
const progressHandler = (event, { modelId: id, progress }) => {
|
||||
if (id === modelId) {
|
||||
@ -831,10 +817,10 @@ export class SettingsView extends LitElement {
|
||||
}
|
||||
};
|
||||
|
||||
ipcRenderer.on('whisper:download-progress', progressHandler);
|
||||
window.api.feature.settings.onWhisperDownloadProgress(progressHandler);
|
||||
|
||||
// Start download
|
||||
const result = await ipcRenderer.invoke('whisper:download-model', modelId);
|
||||
const result = await window.api.feature.settings.downloadWhisperModel(modelId);
|
||||
|
||||
if (result.success) {
|
||||
// Auto-select the model after download
|
||||
@ -844,7 +830,7 @@ export class SettingsView extends LitElement {
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
ipcRenderer.removeListener('whisper:download-progress', progressHandler);
|
||||
window.api.feature.settings.removeOnWhisperDownloadProgress(progressHandler);
|
||||
} catch (error) {
|
||||
console.error(`[SettingsView] Error downloading Whisper model ${modelId}:`, error);
|
||||
alert(`Error downloading ${modelId}: ${error.message}`);
|
||||
@ -876,17 +862,12 @@ export class SettingsView extends LitElement {
|
||||
if (this.wasJustDragged) return
|
||||
|
||||
console.log("Requesting Firebase authentication from main process...")
|
||||
if (window.require) {
|
||||
window.require("electron").ipcRenderer.invoke("start-firebase-auth")
|
||||
}
|
||||
}
|
||||
window.api.feature.settings.startFirebaseAuth();
|
||||
}
|
||||
//////// after_modelStateService ////////
|
||||
|
||||
openShortcutEditor() {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.invoke('open-shortcut-editor');
|
||||
}
|
||||
window.api.feature.settings.openShortcutEditor();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
@ -924,10 +905,6 @@ export class SettingsView extends LitElement {
|
||||
}
|
||||
|
||||
setupIpcListeners() {
|
||||
if (!window.require) return;
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
this._userStateListener = (event, userState) => {
|
||||
console.log('[SettingsView] Received user-state-changed:', userState);
|
||||
if (userState && userState.isLoggedIn) {
|
||||
@ -949,7 +926,7 @@ export class SettingsView extends LitElement {
|
||||
this._presetsUpdatedListener = async (event) => {
|
||||
console.log('[SettingsView] Received presets-updated, refreshing presets');
|
||||
try {
|
||||
const presets = await ipcRenderer.invoke('settings:getPresets');
|
||||
const presets = await window.api.feature.settings.getPresets();
|
||||
this.presets = presets || [];
|
||||
|
||||
// 현재 선택된 프리셋이 삭제되었는지 확인 (사용자 프리셋만 고려)
|
||||
@ -968,28 +945,24 @@ export class SettingsView extends LitElement {
|
||||
this.shortcuts = keybinds;
|
||||
};
|
||||
|
||||
ipcRenderer.on('user-state-changed', this._userStateListener);
|
||||
ipcRenderer.on('settings-updated', this._settingsUpdatedListener);
|
||||
ipcRenderer.on('presets-updated', this._presetsUpdatedListener);
|
||||
ipcRenderer.on('shortcuts-updated', this._shortcutListener);
|
||||
window.api.feature.settings.onUserStateChanged(this._userStateListener);
|
||||
window.api.feature.settings.onSettingsUpdated(this._settingsUpdatedListener);
|
||||
window.api.feature.settings.onPresetsUpdated(this._presetsUpdatedListener);
|
||||
window.api.feature.settings.onShortcutsUpdated(this._shortcutListener);
|
||||
}
|
||||
|
||||
cleanupIpcListeners() {
|
||||
if (!window.require) return;
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
if (this._userStateListener) {
|
||||
ipcRenderer.removeListener('user-state-changed', this._userStateListener);
|
||||
window.api.feature.settings.removeOnUserStateChanged(this._userStateListener);
|
||||
}
|
||||
if (this._settingsUpdatedListener) {
|
||||
ipcRenderer.removeListener('settings-updated', this._settingsUpdatedListener);
|
||||
window.api.feature.settings.removeOnSettingsUpdated(this._settingsUpdatedListener);
|
||||
}
|
||||
if (this._presetsUpdatedListener) {
|
||||
ipcRenderer.removeListener('presets-updated', this._presetsUpdatedListener);
|
||||
window.api.feature.settings.removeOnPresetsUpdated(this._presetsUpdatedListener);
|
||||
}
|
||||
if (this._shortcutListener) {
|
||||
ipcRenderer.removeListener('shortcuts-updated', this._shortcutListener);
|
||||
window.api.feature.settings.removeOnShortcutsUpdated(this._shortcutListener);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1023,17 +996,11 @@ export class SettingsView extends LitElement {
|
||||
}
|
||||
|
||||
handleMouseEnter = () => {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.send('cancel-hide-settings-window');
|
||||
}
|
||||
window.api.window.cancelHideSettingsWindow();
|
||||
}
|
||||
|
||||
handleMouseLeave = () => {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.send('hide-settings-window');
|
||||
}
|
||||
window.api.window.hideSettingsWindow();
|
||||
}
|
||||
|
||||
// getMainShortcuts() {
|
||||
@ -1083,39 +1050,27 @@ export class SettingsView extends LitElement {
|
||||
|
||||
handleMoveLeft() {
|
||||
console.log('Move Left clicked');
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.invoke('move-window-step', 'left');
|
||||
}
|
||||
window.api.feature.settings.moveWindowStep('left');
|
||||
}
|
||||
|
||||
handleMoveRight() {
|
||||
console.log('Move Right clicked');
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.invoke('move-window-step', 'right');
|
||||
}
|
||||
window.api.feature.settings.moveWindowStep('right');
|
||||
}
|
||||
|
||||
async handlePersonalize() {
|
||||
console.log('Personalize clicked');
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
try {
|
||||
await ipcRenderer.invoke('open-login-page');
|
||||
} catch (error) {
|
||||
console.error('Failed to open personalize page:', error);
|
||||
}
|
||||
try {
|
||||
await window.api.window.openLoginPage();
|
||||
} catch (error) {
|
||||
console.error('Failed to open personalize page:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async handleToggleInvisibility() {
|
||||
console.log('Toggle Invisibility clicked');
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
this.isContentProtectionOn = await ipcRenderer.invoke('toggle-content-protection');
|
||||
this.requestUpdate();
|
||||
}
|
||||
this.isContentProtectionOn = await window.api.window.toggleContentProtection();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
async handleSaveApiKey() {
|
||||
@ -1123,62 +1078,46 @@ export class SettingsView extends LitElement {
|
||||
if (!input || !input.value) return;
|
||||
|
||||
const newApiKey = input.value;
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
try {
|
||||
const result = await ipcRenderer.invoke('settings:saveApiKey', newApiKey);
|
||||
if (result.success) {
|
||||
console.log('API Key saved successfully via IPC.');
|
||||
this.apiKey = newApiKey;
|
||||
this.requestUpdate();
|
||||
} else {
|
||||
console.error('Failed to save API Key via IPC:', result.error);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Error invoking save-api-key IPC:', e);
|
||||
try {
|
||||
const result = await window.api.feature.settings.saveApiKey(newApiKey);
|
||||
if (result.success) {
|
||||
console.log('API Key saved successfully via IPC.');
|
||||
this.apiKey = newApiKey;
|
||||
this.requestUpdate();
|
||||
} else {
|
||||
console.error('Failed to save API Key via IPC:', result.error);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Error invoking save-api-key IPC:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async handleClearApiKey() {
|
||||
console.log('Clear API Key clicked');
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
await ipcRenderer.invoke('settings:removeApiKey');
|
||||
this.apiKey = null;
|
||||
this.requestUpdate();
|
||||
}
|
||||
await window.api.feature.settings.removeApiKey();
|
||||
this.apiKey = null;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
handleQuit() {
|
||||
console.log('Quit clicked');
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.invoke('quit-application');
|
||||
}
|
||||
window.api.window.quitApplication();
|
||||
}
|
||||
|
||||
handleFirebaseLogout() {
|
||||
console.log('Firebase Logout clicked');
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.invoke('firebase-logout');
|
||||
}
|
||||
window.api.window.firebaseLogout();
|
||||
}
|
||||
|
||||
async handleOllamaShutdown() {
|
||||
console.log('[SettingsView] Shutting down Ollama service...');
|
||||
|
||||
if (!window.require) return;
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
try {
|
||||
// Show loading state
|
||||
this.ollamaStatus = { ...this.ollamaStatus, running: false };
|
||||
this.requestUpdate();
|
||||
|
||||
const result = await ipcRenderer.invoke('ollama:shutdown', false); // Graceful shutdown
|
||||
const result = await window.api.feature.settings.shutdownOllama(false); // Graceful shutdown
|
||||
|
||||
if (result.success) {
|
||||
console.log('[SettingsView] Ollama shut down successfully');
|
||||
@ -1330,329 +1269,6 @@ export class SettingsView extends LitElement {
|
||||
//////// before_modelStateService ////////
|
||||
|
||||
//////// after_modelStateService ////////
|
||||
render() {
|
||||
if (this.isLoading) {
|
||||
return html`
|
||||
<div class="settings-container">
|
||||
<div class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const loggedIn = !!this.firebaseUser;
|
||||
|
||||
const apiKeyManagementHTML = html`
|
||||
<div class="api-key-section">
|
||||
${Object.entries(this.providerConfig)
|
||||
.filter(([id, config]) => !id.includes('-glass'))
|
||||
.map(([id, config]) => {
|
||||
if (id === 'ollama') {
|
||||
// Special UI for Ollama
|
||||
return html`
|
||||
<div class="provider-key-group">
|
||||
<label>${config.name} (Local)</label>
|
||||
${this.ollamaStatus.installed && this.ollamaStatus.running ? html`
|
||||
<div style="padding: 8px; background: rgba(0,255,0,0.1); border-radius: 4px; font-size: 11px; color: rgba(0,255,0,0.8);">
|
||||
✓ Ollama is running
|
||||
</div>
|
||||
<button class="settings-button full-width danger" @click=${this.handleOllamaShutdown}>
|
||||
Stop Ollama Service
|
||||
</button>
|
||||
` : this.ollamaStatus.installed ? html`
|
||||
<div style="padding: 8px; background: rgba(255,200,0,0.1); border-radius: 4px; font-size: 11px; color: rgba(255,200,0,0.8);">
|
||||
⚠ Ollama installed but not running
|
||||
</div>
|
||||
<button class="settings-button full-width" @click=${() => this.handleSaveKey(id)}>
|
||||
Start Ollama
|
||||
</button>
|
||||
` : html`
|
||||
<div style="padding: 8px; background: rgba(255,100,100,0.1); border-radius: 4px; font-size: 11px; color: rgba(255,100,100,0.8);">
|
||||
✗ Ollama not installed
|
||||
</div>
|
||||
<button class="settings-button full-width" @click=${() => this.handleSaveKey(id)}>
|
||||
Install & Setup Ollama
|
||||
</button>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (id === 'whisper') {
|
||||
// Special UI for Whisper with model selection
|
||||
const whisperModels = config.sttModels || [];
|
||||
const selectedWhisperModel = this.selectedStt && this.getProviderForModel('stt', this.selectedStt) === 'whisper'
|
||||
? this.selectedStt
|
||||
: null;
|
||||
|
||||
return html`
|
||||
<div class="provider-key-group">
|
||||
<label>${config.name} (Local STT)</label>
|
||||
${this.apiKeys[id] === 'local' ? html`
|
||||
<div style="padding: 8px; background: rgba(0,255,0,0.1); border-radius: 4px; font-size: 11px; color: rgba(0,255,0,0.8); margin-bottom: 8px;">
|
||||
✓ Whisper is enabled
|
||||
</div>
|
||||
|
||||
<!-- Whisper Model Selection Dropdown -->
|
||||
<label style="font-size: 10px; margin-top: 8px;">Select Model:</label>
|
||||
<select
|
||||
class="model-dropdown"
|
||||
style="width: 100%; padding: 6px; background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.2); color: white; border-radius: 4px; font-size: 11px; margin-bottom: 8px;"
|
||||
@change=${(e) => this.handleWhisperModelSelect(e.target.value)}
|
||||
.value=${selectedWhisperModel || ''}
|
||||
>
|
||||
<option value="">Choose a model...</option>
|
||||
${whisperModels.map(model => {
|
||||
const isInstalling = this.installingModels[model.id] !== undefined;
|
||||
const progress = this.installingModels[model.id] || 0;
|
||||
|
||||
let statusText = '';
|
||||
if (isInstalling) {
|
||||
statusText = ` (Downloading ${progress}%)`;
|
||||
} else if (model.installed) {
|
||||
statusText = ' (Installed)';
|
||||
}
|
||||
|
||||
return html`
|
||||
<option value="${model.id}" ?disabled=${isInstalling}>
|
||||
${model.name}${statusText}
|
||||
</option>
|
||||
`;
|
||||
})}
|
||||
</select>
|
||||
|
||||
${Object.entries(this.installingModels).map(([modelId, progress]) => {
|
||||
if (modelId.startsWith('whisper-') && progress !== undefined) {
|
||||
return html`
|
||||
<div style="margin: 8px 0;">
|
||||
<div style="font-size: 10px; color: rgba(255,255,255,0.7); margin-bottom: 4px;">
|
||||
Downloading ${modelId}...
|
||||
</div>
|
||||
<div class="install-progress" style="height: 4px; background: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden;">
|
||||
<div class="install-progress-bar" style="height: 100%; background: rgba(0, 122, 255, 0.8); width: ${progress}%; transition: width 0.3s ease;"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
|
||||
<button class="settings-button full-width danger" @click=${() => this.handleClearKey(id)}>
|
||||
Disable Whisper
|
||||
</button>
|
||||
` : html`
|
||||
<button class="settings-button full-width" @click=${() => this.handleSaveKey(id)}>
|
||||
Enable Whisper STT
|
||||
</button>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Regular providers
|
||||
return html`
|
||||
<div class="provider-key-group">
|
||||
<label for="key-input-${id}">${config.name} API Key</label>
|
||||
<input type="password" id="key-input-${id}"
|
||||
placeholder=${loggedIn ? "Using Pickle's Key" : `Enter ${config.name} API Key`}
|
||||
.value=${this.apiKeys[id] || ''}
|
||||
>
|
||||
<div class="key-buttons">
|
||||
<button class="settings-button" @click=${() => this.handleSaveKey(id)} >Save</button>
|
||||
<button class="settings-button danger" @click=${() => this.handleClearKey(id)} }>Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
|
||||
const getModelName = (type, id) => {
|
||||
const models = type === 'llm' ? this.availableLlmModels : this.availableSttModels;
|
||||
const model = models.find(m => m.id === id);
|
||||
return model ? model.name : id;
|
||||
}
|
||||
|
||||
const modelSelectionHTML = html`
|
||||
<div class="model-selection-section">
|
||||
<div class="model-select-group">
|
||||
<label>LLM Model: <strong>${getModelName('llm', this.selectedLlm) || 'Not Set'}</strong></label>
|
||||
<button class="settings-button full-width" @click=${() => this.toggleModelList('llm')} ?disabled=${this.saving || this.availableLlmModels.length === 0}>
|
||||
Change LLM Model
|
||||
</button>
|
||||
${this.isLlmListVisible ? html`
|
||||
<div class="model-list">
|
||||
${this.availableLlmModels.map(model => {
|
||||
const isOllama = this.getProviderForModel('llm', model.id) === 'ollama';
|
||||
const ollamaModel = isOllama ? this.ollamaModels.find(m => m.name === model.id) : null;
|
||||
const isInstalling = this.installingModels[model.id] !== undefined;
|
||||
const installProgress = this.installingModels[model.id] || 0;
|
||||
|
||||
return html`
|
||||
<div class="model-item ${this.selectedLlm === model.id ? 'selected' : ''}"
|
||||
@click=${() => this.selectModel('llm', model.id)}>
|
||||
<span>${model.name}</span>
|
||||
${isOllama ? html`
|
||||
${isInstalling ? html`
|
||||
<div class="install-progress">
|
||||
<div class="install-progress-bar" style="width: ${installProgress}%"></div>
|
||||
</div>
|
||||
` : ollamaModel?.installed ? html`
|
||||
<span class="model-status installed">✓ Installed</span>
|
||||
` : html`
|
||||
<span class="model-status not-installed">Click to install</span>
|
||||
`}
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="model-select-group">
|
||||
<label>STT Model: <strong>${getModelName('stt', this.selectedStt) || 'Not Set'}</strong></label>
|
||||
<button class="settings-button full-width" @click=${() => this.toggleModelList('stt')} ?disabled=${this.saving || this.availableSttModels.length === 0}>
|
||||
Change STT Model
|
||||
</button>
|
||||
${this.isSttListVisible ? html`
|
||||
<div class="model-list">
|
||||
${this.availableSttModels.map(model => {
|
||||
const isWhisper = this.getProviderForModel('stt', model.id) === 'whisper';
|
||||
const isInstalling = this.installingModels[model.id] !== undefined;
|
||||
const installProgress = this.installingModels[model.id] || 0;
|
||||
|
||||
return html`
|
||||
<div class="model-item ${this.selectedStt === model.id ? 'selected' : ''}"
|
||||
@click=${() => this.selectModel('stt', model.id)}>
|
||||
<span>${model.name}</span>
|
||||
${isWhisper && isInstalling ? html`
|
||||
<div class="install-progress">
|
||||
<div class="install-progress-bar" style="width: ${installProgress}%"></div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html`
|
||||
<div class="settings-container">
|
||||
<div class="header-section">
|
||||
<div>
|
||||
<h1 class="app-title">Pickle Glass</h1>
|
||||
<div class="account-info">
|
||||
${this.firebaseUser
|
||||
? html`Account: ${this.firebaseUser.email || 'Logged In'}`
|
||||
: `Account: Not Logged In`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="invisibility-icon ${this.isContentProtectionOn ? 'visible' : ''}" title="Invisibility is On">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.785 7.41787C8.7 7.41787 7.79 8.19371 7.55667 9.22621C7.0025 8.98704 6.495 9.05121 6.11 9.22037C5.87083 8.18204 4.96083 7.41787 3.88167 7.41787C2.61583 7.41787 1.58333 8.46204 1.58333 9.75121C1.58333 11.0404 2.61583 12.0845 3.88167 12.0845C5.08333 12.0845 6.06333 11.1395 6.15667 9.93787C6.355 9.79787 6.87417 9.53537 7.51 9.94954C7.615 11.1454 8.58333 12.0845 9.785 12.0845C11.0508 12.0845 12.0833 11.0404 12.0833 9.75121C12.0833 8.46204 11.0508 7.41787 9.785 7.41787ZM3.88167 11.4195C2.97167 11.4195 2.2425 10.6729 2.2425 9.75121C2.2425 8.82954 2.9775 8.08287 3.88167 8.08287C4.79167 8.08287 5.52083 8.82954 5.52083 9.75121C5.52083 10.6729 4.79167 11.4195 3.88167 11.4195ZM9.785 11.4195C8.875 11.4195 8.14583 10.6729 8.14583 9.75121C8.14583 8.82954 8.875 8.08287 9.785 8.08287C10.695 8.08287 11.43 8.82954 11.43 9.75121C11.43 10.6729 10.6892 11.4195 9.785 11.4195ZM12.6667 5.95954H1V6.83454H12.6667V5.95954ZM8.8925 1.36871C8.76417 1.08287 8.4375 0.931207 8.12833 1.03037L6.83333 1.46204L5.5325 1.03037L5.50333 1.02454C5.19417 0.93704 4.8675 1.10037 4.75083 1.39787L3.33333 5.08454H10.3333L8.91 1.39787L8.8925 1.36871Z" fill="white"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${apiKeyManagementHTML}
|
||||
${modelSelectionHTML}
|
||||
|
||||
<div class="buttons-section" style="border-top: 1px solid rgba(255, 255, 255, 0.1); padding-top: 6px; margin-top: 6px;">
|
||||
<button class="settings-button full-width" @click=${this.openShortcutEditor}>
|
||||
Edit Shortcuts
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="shortcuts-section">
|
||||
${this.getMainShortcuts().map(shortcut => html`
|
||||
<div class="shortcut-item">
|
||||
<span class="shortcut-name">${shortcut.name}</span>
|
||||
<div class="shortcut-keys">
|
||||
${this.renderShortcutKeys(shortcut.accelerator)}
|
||||
</div>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
|
||||
<div class="preset-section">
|
||||
<div class="preset-header">
|
||||
<span class="preset-title">
|
||||
My Presets
|
||||
<span class="preset-count">(${this.presets.filter(p => p.is_default === 0).length})</span>
|
||||
</span>
|
||||
<span class="preset-toggle" @click=${this.togglePresets}>
|
||||
${this.showPresets ? '▼' : '▶'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="preset-list ${this.showPresets ? '' : 'hidden'}">
|
||||
${this.presets.filter(p => p.is_default === 0).length === 0 ? html`
|
||||
<div class="no-presets-message">
|
||||
No custom presets yet.<br>
|
||||
<span class="web-link" @click=${this.handlePersonalize}>
|
||||
Create your first preset
|
||||
</span>
|
||||
</div>
|
||||
` : this.presets.filter(p => p.is_default === 0).map(preset => html`
|
||||
<div class="preset-item ${this.selectedPreset?.id === preset.id ? 'selected' : ''}"
|
||||
@click=${() => this.handlePresetSelect(preset)}>
|
||||
<span class="preset-name">${preset.title}</span>
|
||||
${this.selectedPreset?.id === preset.id ? html`<span class="preset-status">Selected</span>` : ''}
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="buttons-section">
|
||||
<button class="settings-button full-width" @click=${this.handlePersonalize}>
|
||||
<span>Personalize / Meeting Notes</span>
|
||||
</button>
|
||||
<button class="settings-button full-width" @click=${this.handleToggleAutoUpdate} ?disabled=${this.autoUpdateLoading}>
|
||||
<span>Automatic Updates: ${this.autoUpdateEnabled ? 'On' : 'Off'}</span>
|
||||
</button>
|
||||
|
||||
<div class="move-buttons">
|
||||
<button class="settings-button half-width" @click=${this.handleMoveLeft}>
|
||||
<span>← Move</span>
|
||||
</button>
|
||||
<button class="settings-button half-width" @click=${this.handleMoveRight}>
|
||||
<span>Move →</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button class="settings-button full-width" @click=${this.handleToggleInvisibility}>
|
||||
<span>${this.isContentProtectionOn ? 'Disable Invisibility' : 'Enable Invisibility'}</span>
|
||||
</button>
|
||||
|
||||
<div class="bottom-buttons">
|
||||
${this.firebaseUser
|
||||
? html`
|
||||
<button class="settings-button half-width danger" @click=${this.handleFirebaseLogout}>
|
||||
<span>Logout</span>
|
||||
</button>
|
||||
`
|
||||
: html`
|
||||
<button class="settings-button half-width" @click=${this.handleUsePicklesKey}>
|
||||
<span>Login</span>
|
||||
</button>
|
||||
`
|
||||
}
|
||||
<button class="settings-button half-width danger" @click=${this.handleQuit}>
|
||||
<span>Quit</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
//////// after_modelStateService ////////
|
||||
}
|
||||
|
||||
customElements.define('settings-view', SettingsView);
|
@ -1,4 +1,4 @@
|
||||
import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js';
|
||||
import { html, css, LitElement } from '../../ui/assets/lit-core-2.7.4.min.js';
|
||||
|
||||
const commonSystemShortcuts = new Set([
|
||||
'Cmd+Q', 'Cmd+W', 'Cmd+A', 'Cmd+S', 'Cmd+Z', 'Cmd+X', 'Cmd+C', 'Cmd+V', 'Cmd+P', 'Cmd+F', 'Cmd+G', 'Cmd+H', 'Cmd+M', 'Cmd+N', 'Cmd+O', 'Cmd+T',
|
||||
@ -102,23 +102,23 @@ export class ShortcutSettingsView extends LitElement {
|
||||
this.feedback = {};
|
||||
this.isLoading = true;
|
||||
this.capturingKey = null;
|
||||
this.ipcRenderer = window.require ? window.require('electron').ipcRenderer : null;
|
||||
this.hasAPI = window.api && window.api.settings;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (!this.ipcRenderer) return;
|
||||
if (!this.hasAPI) return;
|
||||
this.loadShortcutsHandler = (event, keybinds) => {
|
||||
this.shortcuts = keybinds;
|
||||
this.isLoading = false;
|
||||
};
|
||||
this.ipcRenderer.on('load-shortcuts', this.loadShortcutsHandler);
|
||||
window.api.settings.onLoadShortcuts(this.loadShortcutsHandler);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this.ipcRenderer && this.loadShortcutsHandler) {
|
||||
this.ipcRenderer.removeListener('load-shortcuts', this.loadShortcutsHandler);
|
||||
if (this.hasAPI && this.loadShortcutsHandler) {
|
||||
window.api.settings.removeOnLoadShortcuts(this.loadShortcutsHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,25 +171,25 @@ export class ShortcutSettingsView extends LitElement {
|
||||
}
|
||||
|
||||
async handleSave() {
|
||||
if (!this.ipcRenderer) return;
|
||||
const result = await this.ipcRenderer.invoke('save-shortcuts', this.shortcuts);
|
||||
if (!this.hasAPI) return;
|
||||
const result = await window.api.settings.saveShortcuts(this.shortcuts);
|
||||
if (!result.success) {
|
||||
alert('Failed to save shortcuts: ' + result.error);
|
||||
}
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
if (!this.ipcRenderer) return;
|
||||
this.ipcRenderer.send('close-shortcut-editor');
|
||||
if (!this.hasAPI) return;
|
||||
window.api.settings.closeShortcutEditor();
|
||||
}
|
||||
|
||||
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?");
|
||||
if (!confirmation) return;
|
||||
|
||||
try {
|
||||
const defaultShortcuts = await this.ipcRenderer.invoke('get-default-shortcuts');
|
||||
const defaultShortcuts = await window.api.settings.getDefaultShortcuts();
|
||||
this.shortcuts = defaultShortcuts;
|
||||
} catch (error) {
|
||||
alert('Failed to load default settings.');
|