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 = [
|
const entryPoints = [
|
||||||
{ in: 'src/app/HeaderController.js', out: 'public/build/header' },
|
{ in: 'src/ui/app/HeaderController.js', out: 'public/build/header' },
|
||||||
{ in: 'src/app/PickleGlassApp.js', out: 'public/build/content' },
|
{ in: 'src/ui/app/PickleGlassApp.js', out: 'public/build/content' },
|
||||||
];
|
];
|
||||||
|
|
||||||
async function build() {
|
async function build() {
|
||||||
|
@ -33,7 +33,7 @@ extraResources:
|
|||||||
to: out
|
to: out
|
||||||
|
|
||||||
asarUnpack:
|
asarUnpack:
|
||||||
- "src/assets/SystemAudioDump"
|
- "src/ui/assets/SystemAudioDump"
|
||||||
- "**/node_modules/sharp/**/*"
|
- "**/node_modules/sharp/**/*"
|
||||||
- "**/node_modules/@img/**/*"
|
- "**/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 { ipcMain, BrowserWindow } = require('electron');
|
||||||
const { createStreamingLLM } = require('../../common/ai/factory');
|
const { createStreamingLLM } = require('../common/ai/factory');
|
||||||
const { getStoredApiKey, getStoredProvider, getCurrentModelInfo, windowPool, captureScreenshot } = require('../../electron/windowManager');
|
const { getStoredApiKey, getStoredProvider, getCurrentModelInfo, windowPool, captureScreenshot } = require('../../window/windowManager');
|
||||||
const authService = require('../../common/services/authService');
|
const authService = require('../common/services/authService');
|
||||||
const sessionRepository = require('../../common/repositories/session');
|
const sessionRepository = require('../common/repositories/session');
|
||||||
const askRepository = require('./repositories');
|
const askRepository = require('./repositories');
|
||||||
const { getSystemPrompt } = require('../../common/prompts/promptBuilder');
|
const { getSystemPrompt } = require('../common/prompts/promptBuilder');
|
||||||
|
|
||||||
function formatConversationForPrompt(conversationTexts) {
|
function formatConversationForPrompt(conversationTexts) {
|
||||||
if (!conversationTexts || conversationTexts.length === 0) return 'No conversation history available.';
|
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');
|
console.warn('[AskService] Cannot process empty message');
|
||||||
return { success: false, error: '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;
|
let sessionId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[AskService] 🤖 Processing message: ${userPrompt.substring(0, 50)}...`);
|
console.log(`[AskService] Processing message: ${userPrompt.substring(0, 50)}...`);
|
||||||
|
|
||||||
// --- Save user's message immediately ---
|
// --- Save user's message immediately ---
|
||||||
// This ensures the user message is always timestamped before the assistant's response.
|
// This ensures the user message is always timestamped before the assistant's response.
|
||||||
@ -139,12 +134,11 @@ async function sendMessage(userPrompt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initialize() {
|
function initialize() {
|
||||||
ipcMain.handle('ask:sendMessage', async (event, userPrompt) => {
|
// IPC 핸들러는 featureBridge.js로 이동됨
|
||||||
return sendMessage(userPrompt);
|
|
||||||
});
|
|
||||||
console.log('[AskService] Initialized and ready.');
|
console.log('[AskService] Initialized and ready.');
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
initialize,
|
initialize,
|
||||||
|
sendMessage, // sendMessage 함수 export 추가
|
||||||
};
|
};
|
@ -1,6 +1,6 @@
|
|||||||
const { collection, addDoc, query, getDocs, orderBy, Timestamp } = require('firebase/firestore');
|
const { collection, addDoc, query, getDocs, orderBy, Timestamp } = require('firebase/firestore');
|
||||||
const { getFirestoreInstance } = require('../../../common/services/firebaseClient');
|
const { getFirestoreInstance } = require('../../common/services/firebaseClient');
|
||||||
const { createEncryptedConverter } = require('../../../common/repositories/firestoreConverter');
|
const { createEncryptedConverter } = require('../../common/repositories/firestoreConverter');
|
||||||
|
|
||||||
const aiMessageConverter = createEncryptedConverter(['content']);
|
const aiMessageConverter = createEncryptedConverter(['content']);
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const sqliteRepository = require('./sqlite.repository');
|
const sqliteRepository = require('./sqlite.repository');
|
||||||
const firebaseRepository = require('./firebase.repository');
|
const firebaseRepository = require('./firebase.repository');
|
||||||
const authService = require('../../../common/services/authService');
|
const authService = require('../../common/services/authService');
|
||||||
|
|
||||||
function getBaseRepository() {
|
function getBaseRepository() {
|
||||||
const user = authService.getCurrentUser();
|
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' }) {
|
function addAiMessage({ uid, sessionId, role, content, model = 'unknown' }) {
|
||||||
// uid is ignored in the SQLite implementation
|
// uid is ignored in the SQLite implementation
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const sqliteRepository = require('./sqlite.repository');
|
const sqliteRepository = require('./sqlite.repository');
|
||||||
const firebaseRepository = require('./firebase.repository');
|
const firebaseRepository = require('./firebase.repository');
|
||||||
const authService = require('../../../common/services/authService');
|
const authService = require('../../services/authService');
|
||||||
|
|
||||||
function getBaseRepository() {
|
function getBaseRepository() {
|
||||||
const user = authService.getCurrentUser();
|
const user = authService.getCurrentUser();
|
@ -5,9 +5,9 @@ const encryptionService = require('../services/encryptionService');
|
|||||||
const sqliteSessionRepo = require('../repositories/session/sqlite.repository');
|
const sqliteSessionRepo = require('../repositories/session/sqlite.repository');
|
||||||
const sqlitePresetRepo = require('../repositories/preset/sqlite.repository');
|
const sqlitePresetRepo = require('../repositories/preset/sqlite.repository');
|
||||||
const sqliteUserRepo = require('../repositories/user/sqlite.repository');
|
const sqliteUserRepo = require('../repositories/user/sqlite.repository');
|
||||||
const sqliteSttRepo = require('../../features/listen/stt/repositories/sqlite.repository');
|
const sqliteSttRepo = require('../../listen/stt/repositories/sqlite.repository');
|
||||||
const sqliteSummaryRepo = require('../../features/listen/summary/repositories/sqlite.repository');
|
const sqliteSummaryRepo = require('../../listen/summary/repositories/sqlite.repository');
|
||||||
const sqliteAiMessageRepo = require('../../features/ask/repositories/sqlite.repository');
|
const sqliteAiMessageRepo = require('../../ask/repositories/sqlite.repository');
|
||||||
|
|
||||||
const MAX_BATCH_OPERATIONS = 500;
|
const MAX_BATCH_OPERATIONS = 500;
|
||||||
|
|
@ -1,8 +1,8 @@
|
|||||||
const { BrowserWindow, app } = require('electron');
|
const { BrowserWindow, app } = require('electron');
|
||||||
const SttService = require('./stt/sttService');
|
const SttService = require('./stt/sttService');
|
||||||
const SummaryService = require('./summary/summaryService');
|
const SummaryService = require('./summary/summaryService');
|
||||||
const authService = require('../../common/services/authService');
|
const authService = require('../common/services/authService');
|
||||||
const sessionRepository = require('../../common/repositories/session');
|
const sessionRepository = require('../common/repositories/session');
|
||||||
const sttRepository = require('./stt/repositories');
|
const sttRepository = require('./stt/repositories');
|
||||||
|
|
||||||
class ListenService {
|
class ListenService {
|
||||||
@ -193,8 +193,6 @@ class ListenService {
|
|||||||
this.currentSessionId = null;
|
this.currentSessionId = null;
|
||||||
this.summaryService.resetConversationHistory();
|
this.summaryService.resetConversationHistory();
|
||||||
|
|
||||||
this.sendToRenderer('session-did-close');
|
|
||||||
|
|
||||||
console.log('Listen service session closed.');
|
console.log('Listen service session closed.');
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const { collection, addDoc, query, getDocs, orderBy, Timestamp } = require('firebase/firestore');
|
const { collection, addDoc, query, getDocs, orderBy, Timestamp } = require('firebase/firestore');
|
||||||
const { getFirestoreInstance } = require('../../../../common/services/firebaseClient');
|
const { getFirestoreInstance } = require('../../../common/services/firebaseClient');
|
||||||
const { createEncryptedConverter } = require('../../../../common/repositories/firestoreConverter');
|
const { createEncryptedConverter } = require('../../../common/repositories/firestoreConverter');
|
||||||
|
|
||||||
const transcriptConverter = createEncryptedConverter(['text']);
|
const transcriptConverter = createEncryptedConverter(['text']);
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const sqliteRepository = require('./sqlite.repository');
|
const sqliteRepository = require('./sqlite.repository');
|
||||||
const firebaseRepository = require('./firebase.repository');
|
const firebaseRepository = require('./firebase.repository');
|
||||||
const authService = require('../../../../common/services/authService');
|
const authService = require('../../../common/services/authService');
|
||||||
|
|
||||||
function getBaseRepository() {
|
function getBaseRepository() {
|
||||||
const user = authService.getCurrentUser();
|
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 }) {
|
function addTranscript({ uid, sessionId, speaker, text }) {
|
||||||
// uid is ignored in the SQLite implementation
|
// uid is ignored in the SQLite implementation
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const { BrowserWindow } = require('electron');
|
const { BrowserWindow } = require('electron');
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
const { createSTT } = require('../../../common/ai/factory');
|
const { createSTT } = require('../../common/ai/factory');
|
||||||
const { getStoredApiKey, getStoredProvider, getCurrentModelInfo } = require('../../../electron/windowManager');
|
const { getStoredApiKey, getStoredProvider, getCurrentModelInfo } = require('../../../window/windowManager');
|
||||||
|
|
||||||
const COMPLETION_DEBOUNCE_MS = 2000;
|
const COMPLETION_DEBOUNCE_MS = 2000;
|
||||||
|
|
||||||
@ -367,11 +367,6 @@ class SttService {
|
|||||||
onclose: event => console.log('Their STT session closed:', event.reason),
|
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 = {
|
const sttOptions = {
|
||||||
apiKey: this.modelInfo.apiKey,
|
apiKey: this.modelInfo.apiKey,
|
||||||
@ -476,8 +471,8 @@ class SttService {
|
|||||||
const { app } = require('electron');
|
const { app } = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const systemAudioPath = app.isPackaged
|
const systemAudioPath = app.isPackaged
|
||||||
? path.join(process.resourcesPath, 'app.asar.unpacked', 'src', 'assets', 'SystemAudioDump')
|
? path.join(process.resourcesPath, 'app.asar.unpacked', 'src', 'ui', 'assets', 'SystemAudioDump')
|
||||||
: path.join(app.getAppPath(), 'src', 'assets', 'SystemAudioDump');
|
: path.join(app.getAppPath(), 'src', 'ui', 'assets', 'SystemAudioDump');
|
||||||
|
|
||||||
console.log('SystemAudioDump path:', systemAudioPath);
|
console.log('SystemAudioDump path:', systemAudioPath);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const { collection, doc, setDoc, getDoc, Timestamp } = require('firebase/firestore');
|
const { collection, doc, setDoc, getDoc, Timestamp } = require('firebase/firestore');
|
||||||
const { getFirestoreInstance } = require('../../../../common/services/firebaseClient');
|
const { getFirestoreInstance } = require('../../../common/services/firebaseClient');
|
||||||
const { createEncryptedConverter } = require('../../../../common/repositories/firestoreConverter');
|
const { createEncryptedConverter } = require('../../../common/repositories/firestoreConverter');
|
||||||
const encryptionService = require('../../../../common/services/encryptionService');
|
const encryptionService = require('../../../common/services/encryptionService');
|
||||||
|
|
||||||
const fieldsToEncrypt = ['tldr', 'text', 'bullet_json', 'action_json'];
|
const fieldsToEncrypt = ['tldr', 'text', 'bullet_json', 'action_json'];
|
||||||
const summaryConverter = createEncryptedConverter(fieldsToEncrypt);
|
const summaryConverter = createEncryptedConverter(fieldsToEncrypt);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const sqliteRepository = require('./sqlite.repository');
|
const sqliteRepository = require('./sqlite.repository');
|
||||||
const firebaseRepository = require('./firebase.repository');
|
const firebaseRepository = require('./firebase.repository');
|
||||||
const authService = require('../../../../common/services/authService');
|
const authService = require('../../../common/services/authService');
|
||||||
|
|
||||||
function getBaseRepository() {
|
function getBaseRepository() {
|
||||||
const user = authService.getCurrentUser();
|
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' }) {
|
function saveSummary({ uid, sessionId, tldr, text, bullet_json, action_json, model = 'unknown' }) {
|
||||||
// uid is ignored in the SQLite implementation
|
// uid is ignored in the SQLite implementation
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
const { BrowserWindow } = require('electron');
|
const { BrowserWindow } = require('electron');
|
||||||
const { getSystemPrompt } = require('../../../common/prompts/promptBuilder.js');
|
const { getSystemPrompt } = require('../../common/prompts/promptBuilder.js');
|
||||||
const { createLLM } = require('../../../common/ai/factory');
|
const { createLLM } = require('../../common/ai/factory');
|
||||||
const authService = require('../../../common/services/authService');
|
const sessionRepository = require('../../common/repositories/session');
|
||||||
const sessionRepository = require('../../../common/repositories/session');
|
|
||||||
const summaryRepository = require('./repositories');
|
const summaryRepository = require('./repositories');
|
||||||
const { getStoredApiKey, getStoredProvider, getCurrentModelInfo } = require('../../../electron/windowManager');
|
const { getStoredApiKey, getStoredProvider, getCurrentModelInfo } = require('../../../window/windowManager.js');
|
||||||
|
|
||||||
class SummaryService {
|
class SummaryService {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const { collection, doc, addDoc, getDoc, getDocs, updateDoc, deleteDoc, query, where, orderBy } = require('firebase/firestore');
|
const { collection, doc, addDoc, getDoc, getDocs, updateDoc, deleteDoc, query, where, orderBy } = require('firebase/firestore');
|
||||||
const { getFirestoreInstance } = require('../../../common/services/firebaseClient');
|
const { getFirestoreInstance } = require('../../common/services/firebaseClient');
|
||||||
const { createEncryptedConverter } = require('../../../common/repositories/firestoreConverter');
|
const { createEncryptedConverter } = require('../../common/repositories/firestoreConverter');
|
||||||
const encryptionService = require('../../../common/services/encryptionService');
|
const encryptionService = require('../../common/services/encryptionService');
|
||||||
|
|
||||||
const userPresetConverter = createEncryptedConverter(['prompt', 'title']);
|
const userPresetConverter = createEncryptedConverter(['prompt', 'title']);
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const sqliteRepository = require('./sqlite.repository');
|
const sqliteRepository = require('./sqlite.repository');
|
||||||
const firebaseRepository = require('./firebase.repository');
|
const firebaseRepository = require('./firebase.repository');
|
||||||
const authService = require('../../../common/services/authService');
|
const authService = require('../../common/services/authService');
|
||||||
|
|
||||||
function getBaseRepository() {
|
function getBaseRepository() {
|
||||||
const user = authService.getCurrentUser();
|
const user = authService.getCurrentUser();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const sqliteClient = require('../../../common/services/sqliteClient');
|
const sqliteClient = require('../../common/services/sqliteClient');
|
||||||
|
|
||||||
function getPresets(uid) {
|
function getPresets(uid) {
|
||||||
const db = sqliteClient.getDb();
|
const db = sqliteClient.getDb();
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
const { ipcMain, BrowserWindow } = require('electron');
|
const { ipcMain, BrowserWindow } = require('electron');
|
||||||
const Store = require('electron-store');
|
const Store = require('electron-store');
|
||||||
const authService = require('../../common/services/authService');
|
const authService = require('../common/services/authService');
|
||||||
const settingsRepository = require('./repositories');
|
const settingsRepository = require('./repositories');
|
||||||
const { getStoredApiKey, getStoredProvider, windowPool } = require('../../electron/windowManager');
|
const { getStoredApiKey, getStoredProvider, windowPool } = require('../../window/windowManager');
|
||||||
|
|
||||||
const store = new Store({
|
const store = new Store({
|
||||||
name: 'pickle-glass-settings',
|
name: 'pickle-glass-settings',
|
||||||
@ -373,56 +373,7 @@ function initialize() {
|
|||||||
// cleanup
|
// cleanup
|
||||||
windowNotificationManager.cleanup();
|
windowNotificationManager.cleanup();
|
||||||
|
|
||||||
// IPC handlers for settings
|
// IPC handlers 제거 (featureBridge로 이동)
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('[SettingsService] Initialized and ready.');
|
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 { 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 ListenService = require('./features/listen/listenService');
|
||||||
const { initializeFirebase } = require('./common/services/firebaseClient');
|
const { initializeFirebase } = require('./features/common/services/firebaseClient');
|
||||||
const databaseInitializer = require('./common/services/databaseInitializer');
|
const databaseInitializer = require('./features/common/services/databaseInitializer');
|
||||||
const authService = require('./common/services/authService');
|
const authService = require('./features/common/services/authService');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
@ -24,9 +24,10 @@ const { autoUpdater } = require('electron-updater');
|
|||||||
const { EventEmitter } = require('events');
|
const { EventEmitter } = require('events');
|
||||||
const askService = require('./features/ask/askService');
|
const askService = require('./features/ask/askService');
|
||||||
const settingsService = require('./features/settings/settingsService');
|
const settingsService = require('./features/settings/settingsService');
|
||||||
const sessionRepository = require('./common/repositories/session');
|
const sessionRepository = require('./features/common/repositories/session');
|
||||||
const ModelStateService = require('./common/services/modelStateService');
|
const ModelStateService = require('./features/common/services/modelStateService');
|
||||||
const sqliteClient = require('./common/services/sqliteClient');
|
const sqliteClient = require('./features/common/services/sqliteClient');
|
||||||
|
const featureBridge = require('./bridge/featureBridge');
|
||||||
|
|
||||||
// Global variables
|
// Global variables
|
||||||
const eventBridge = new EventEmitter();
|
const eventBridge = new EventEmitter();
|
||||||
@ -43,8 +44,8 @@ global.modelStateService = modelStateService;
|
|||||||
//////// after_modelStateService ////////
|
//////// after_modelStateService ////////
|
||||||
|
|
||||||
// Import and initialize OllamaService
|
// Import and initialize OllamaService
|
||||||
const ollamaService = require('./common/services/ollamaService');
|
const ollamaService = require('./features/common/services/ollamaService');
|
||||||
const ollamaModelRepository = require('./common/repositories/ollamaModel');
|
const ollamaModelRepository = require('./features/common/repositories/ollamaModel');
|
||||||
|
|
||||||
// Native deep link handling - cross-platform compatible
|
// Native deep link handling - cross-platform compatible
|
||||||
let pendingDeepLinkUrl = null;
|
let pendingDeepLinkUrl = null;
|
||||||
@ -123,7 +124,7 @@ function setupProtocolHandling() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function focusMainWindow() {
|
function focusMainWindow() {
|
||||||
const { windowPool } = require('./electron/windowManager');
|
const { windowPool } = require('./window/windowManager.js');
|
||||||
if (windowPool) {
|
if (windowPool) {
|
||||||
const header = windowPool.get('header');
|
const header = windowPool.get('header');
|
||||||
if (header && !header.isDestroyed()) {
|
if (header && !header.isDestroyed()) {
|
||||||
@ -205,6 +206,7 @@ app.whenReady().then(async () => {
|
|||||||
listenService.setupIpcHandlers();
|
listenService.setupIpcHandlers();
|
||||||
askService.initialize();
|
askService.initialize();
|
||||||
settingsService.initialize();
|
settingsService.initialize();
|
||||||
|
featureBridge.initialize(); // 추가: featureBridge 초기화
|
||||||
setupGeneralIpcHandlers();
|
setupGeneralIpcHandlers();
|
||||||
setupOllamaIpcHandlers();
|
setupOllamaIpcHandlers();
|
||||||
setupWhisperIpcHandlers();
|
setupWhisperIpcHandlers();
|
||||||
@ -329,7 +331,7 @@ app.on('activate', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function setupWhisperIpcHandlers() {
|
function setupWhisperIpcHandlers() {
|
||||||
const { WhisperService } = require('./common/services/whisperService');
|
const { WhisperService } = require('./features/common/services/whisperService');
|
||||||
const whisperService = new WhisperService();
|
const whisperService = new WhisperService();
|
||||||
|
|
||||||
// Forward download progress events to renderer
|
// Forward download progress events to renderer
|
||||||
@ -393,8 +395,8 @@ function setupWhisperIpcHandlers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupGeneralIpcHandlers() {
|
function setupGeneralIpcHandlers() {
|
||||||
const userRepository = require('./common/repositories/user');
|
const userRepository = require('./features/common/repositories/user');
|
||||||
const presetRepository = require('./common/repositories/preset');
|
const presetRepository = require('./features/common/repositories/preset');
|
||||||
|
|
||||||
ipcMain.handle('get-user-presets', () => {
|
ipcMain.handle('get-user-presets', () => {
|
||||||
// The adapter injects the UID.
|
// The adapter injects the UID.
|
||||||
@ -625,12 +627,12 @@ function setupOllamaIpcHandlers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupWebDataHandlers() {
|
function setupWebDataHandlers() {
|
||||||
const sessionRepository = require('./common/repositories/session');
|
const sessionRepository = require('./features/common/repositories/session');
|
||||||
const sttRepository = require('./features/listen/stt/repositories');
|
const sttRepository = require('./features/listen/stt/repositories');
|
||||||
const summaryRepository = require('./features/listen/summary/repositories');
|
const summaryRepository = require('./features/listen/summary/repositories');
|
||||||
const askRepository = require('./features/ask/repositories');
|
const askRepository = require('./features/ask/repositories');
|
||||||
const userRepository = require('./common/repositories/user');
|
const userRepository = require('./features/common/repositories/user');
|
||||||
const presetRepository = require('./common/repositories/preset');
|
const presetRepository = require('./features/common/repositories/preset');
|
||||||
|
|
||||||
const handleRequest = async (channel, responseChannel, payload) => {
|
const handleRequest = async (channel, responseChannel, payload) => {
|
||||||
let result;
|
let result;
|
||||||
@ -788,7 +790,7 @@ async function handleCustomUrl(url) {
|
|||||||
handlePersonalizeFromUrl(params);
|
handlePersonalizeFromUrl(params);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
const { windowPool } = require('./electron/windowManager');
|
const { windowPool } = require('./window/windowManager.js');
|
||||||
const header = windowPool.get('header');
|
const header = windowPool.get('header');
|
||||||
if (header) {
|
if (header) {
|
||||||
if (header.isMinimized()) header.restore();
|
if (header.isMinimized()) header.restore();
|
||||||
@ -806,7 +808,7 @@ async function handleCustomUrl(url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleFirebaseAuthCallback(params) {
|
async function handleFirebaseAuthCallback(params) {
|
||||||
const userRepository = require('./common/repositories/user');
|
const userRepository = require('./features/common/repositories/user');
|
||||||
const { token: idToken } = params;
|
const { token: idToken } = params;
|
||||||
|
|
||||||
if (!idToken) {
|
if (!idToken) {
|
||||||
@ -850,7 +852,7 @@ async function handleFirebaseAuthCallback(params) {
|
|||||||
console.log('[Auth] Main process sign-in initiated. Waiting for onAuthStateChanged...');
|
console.log('[Auth] Main process sign-in initiated. Waiting for onAuthStateChanged...');
|
||||||
|
|
||||||
// 3. Focus the app window
|
// 3. Focus the app window
|
||||||
const { windowPool } = require('./electron/windowManager');
|
const { windowPool } = require('./window/windowManager.js');
|
||||||
const header = windowPool.get('header');
|
const header = windowPool.get('header');
|
||||||
if (header) {
|
if (header) {
|
||||||
if (header.isMinimized()) header.restore();
|
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);
|
console.error('[Auth] Error during custom token exchange or sign-in:', error);
|
||||||
// The UI will not change, and the user can try again.
|
// The UI will not change, and the user can try again.
|
||||||
// Optionally, send a generic error event to the renderer.
|
// 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');
|
const header = windowPool.get('header');
|
||||||
if (header) {
|
if (header) {
|
||||||
header.webContents.send('auth-failed', { message: error.message });
|
header.webContents.send('auth-failed', { message: error.message });
|
||||||
@ -874,7 +876,7 @@ async function handleFirebaseAuthCallback(params) {
|
|||||||
function handlePersonalizeFromUrl(params) {
|
function handlePersonalizeFromUrl(params) {
|
||||||
console.log('[Custom URL] Personalize params:', params);
|
console.log('[Custom URL] Personalize params:', params);
|
||||||
|
|
||||||
const { windowPool } = require('./electron/windowManager');
|
const { windowPool } = require('./window/windowManager.js');
|
||||||
const header = windowPool.get('header');
|
const header = windowPool.get('header');
|
||||||
|
|
||||||
if (header) {
|
if (header) {
|
||||||
|
334
src/preload.js
@ -1,2 +1,332 @@
|
|||||||
// See the Electron documentation for details on how to use preload scripts:
|
// src/preload.js
|
||||||
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
|
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 { 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 {
|
export class ApiKeyHeader extends LitElement {
|
||||||
//////// after_modelStateService ////////
|
//////// after_modelStateService ////////
|
||||||
@ -370,13 +370,12 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadProviderConfig() {
|
async loadProviderConfig() {
|
||||||
if (!window.require) return;
|
if (!window.api || !window.api.apikey) return;
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [config, ollamaStatus] = await Promise.all([
|
const [config, ollamaStatus] = await Promise.all([
|
||||||
ipcRenderer.invoke('model:get-provider-config'),
|
window.api.apikey.getProviderConfig(),
|
||||||
ipcRenderer.invoke('ollama:get-status')
|
window.api.apikey.getOllamaStatus()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const llmProviders = [];
|
const llmProviders = [];
|
||||||
@ -428,8 +427,8 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const { ipcRenderer } = window.require("electron")
|
if (!window.api || !window.api.apikey) return;
|
||||||
const initialPosition = await ipcRenderer.invoke("get-header-position")
|
const initialPosition = await window.api.apikey.getHeaderPosition()
|
||||||
|
|
||||||
this.dragState = {
|
this.dragState = {
|
||||||
initialMouseX: e.screenX,
|
initialMouseX: e.screenX,
|
||||||
@ -456,8 +455,9 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
const newWindowX = this.dragState.initialWindowX + (e.screenX - this.dragState.initialMouseX)
|
const newWindowX = this.dragState.initialWindowX + (e.screenX - this.dragState.initialMouseX)
|
||||||
const newWindowY = this.dragState.initialWindowY + (e.screenY - this.dragState.initialMouseY)
|
const newWindowY = this.dragState.initialWindowY + (e.screenY - this.dragState.initialMouseY)
|
||||||
|
|
||||||
const { ipcRenderer } = window.require("electron")
|
if (window.api && window.api.apikey) {
|
||||||
ipcRenderer.invoke("move-header-to", newWindowX, newWindowY)
|
window.api.apikey.moveHeaderTo(newWindowX, newWindowY)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp(e) {
|
handleMouseUp(e) {
|
||||||
@ -652,9 +652,8 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
try {
|
try {
|
||||||
// Lightweight health check - just ping the service
|
// Lightweight health check - just ping the service
|
||||||
const isHealthy = await this._executeOperation('health_check', async () => {
|
const isHealthy = await this._executeOperation('health_check', async () => {
|
||||||
if (!window.require) return false;
|
if (!window.api || !window.api.apikey) return false;
|
||||||
const { ipcRenderer } = window.require('electron');
|
const result = await window.api.apikey.getOllamaStatus();
|
||||||
const result = await ipcRenderer.invoke('ollama:get-status');
|
|
||||||
return result?.success && result?.running;
|
return result?.success && result?.running;
|
||||||
}, { timeout: 5000, priority: 'low' });
|
}, { timeout: 5000, priority: 'low' });
|
||||||
|
|
||||||
@ -928,14 +927,13 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async refreshOllamaStatus() {
|
async refreshOllamaStatus() {
|
||||||
if (!window.require) return;
|
if (!window.api || !window.api.apikey) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._updateConnectionState('connecting', 'Checking Ollama status');
|
this._updateConnectionState('connecting', 'Checking Ollama status');
|
||||||
|
|
||||||
const result = await this._executeOperation('ollama_status', async () => {
|
const result = await this._executeOperation('ollama_status', async () => {
|
||||||
const { ipcRenderer } = window.require('electron');
|
return await window.api.apikey.getOllamaStatus();
|
||||||
return await ipcRenderer.invoke('ollama:get-status');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.success) {
|
if (result?.success) {
|
||||||
@ -960,12 +958,11 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadModelSuggestions() {
|
async loadModelSuggestions() {
|
||||||
if (!window.require) return;
|
if (!window.api || !window.api.apikey) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await this._executeOperation('model_suggestions', async () => {
|
const result = await this._executeOperation('model_suggestions', async () => {
|
||||||
const { ipcRenderer } = window.require('electron');
|
return await window.api.apikey.getModelSuggestions();
|
||||||
return await ipcRenderer.invoke('ollama:get-model-suggestions');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.success) {
|
if (result?.success) {
|
||||||
@ -988,14 +985,13 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ensureOllamaReady() {
|
async ensureOllamaReady() {
|
||||||
if (!window.require) return false;
|
if (!window.api || !window.api.apikey) return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._updateConnectionState('connecting', 'Ensuring Ollama is ready');
|
this._updateConnectionState('connecting', 'Ensuring Ollama is ready');
|
||||||
|
|
||||||
const result = await this._executeOperation('ollama_ensure_ready', async () => {
|
const result = await this._executeOperation('ollama_ensure_ready', async () => {
|
||||||
const { ipcRenderer } = window.require('electron');
|
return await window.api.apikey.ensureReady();
|
||||||
return await ipcRenderer.invoke('ollama:ensure-ready');
|
|
||||||
}, { timeout: this.operationTimeout });
|
}, { timeout: this.operationTimeout });
|
||||||
|
|
||||||
if (result?.success) {
|
if (result?.success) {
|
||||||
@ -1015,8 +1011,7 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ensureOllamaReadyWithUI() {
|
async ensureOllamaReadyWithUI() {
|
||||||
if (!window.require) return false;
|
if (!window.api || !window.api.apikey) return false;
|
||||||
const { ipcRenderer } = window.require("electron");
|
|
||||||
|
|
||||||
this.installingModel = "Setting up Ollama";
|
this.installingModel = "Setting up Ollama";
|
||||||
this.installProgress = 0;
|
this.installProgress = 0;
|
||||||
@ -1074,21 +1069,21 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
operationCompleted = true;
|
operationCompleted = true;
|
||||||
clearTimeout(completionTimeout);
|
clearTimeout(completionTimeout);
|
||||||
|
|
||||||
ipcRenderer.removeListener("ollama:install-progress", progressHandler);
|
window.api.apikey.removeOnOllamaInstallProgress(progressHandler);
|
||||||
await this._handleOllamaSetupCompletion(result.success, result.error);
|
await this._handleOllamaSetupCompletion(result.success, result.error);
|
||||||
};
|
};
|
||||||
|
|
||||||
ipcRenderer.once("ollama:install-complete", completionHandler);
|
window.api.apikey.onOllamaInstallComplete(completionHandler);
|
||||||
ipcRenderer.on("ollama:install-progress", progressHandler);
|
window.api.apikey.onOllamaInstallProgress(progressHandler);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let result;
|
let result;
|
||||||
if (!this.ollamaStatus.installed) {
|
if (!this.ollamaStatus.installed) {
|
||||||
console.log("[ApiKeyHeader] Ollama not installed. Starting installation.");
|
console.log("[ApiKeyHeader] Ollama not installed. Starting installation.");
|
||||||
result = await ipcRenderer.invoke("ollama:install");
|
result = await window.api.apikey.installOllama();
|
||||||
} else {
|
} else {
|
||||||
console.log("[ApiKeyHeader] Ollama installed. Starting service.");
|
console.log("[ApiKeyHeader] Ollama installed. Starting service.");
|
||||||
result = await ipcRenderer.invoke("ollama:start-service");
|
result = await window.api.apikey.startService();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If IPC call succeeds but no event received, handle completion manually
|
// If IPC call succeeds but no event received, handle completion manually
|
||||||
@ -1106,8 +1101,8 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
operationCompleted = true;
|
operationCompleted = true;
|
||||||
clearTimeout(completionTimeout);
|
clearTimeout(completionTimeout);
|
||||||
console.error("[ApiKeyHeader] Ollama setup failed:", error);
|
console.error("[ApiKeyHeader] Ollama setup failed:", error);
|
||||||
ipcRenderer.removeListener("ollama:install-progress", progressHandler);
|
window.api.apikey.removeOnOllamaInstallProgress(progressHandler);
|
||||||
ipcRenderer.removeListener("ollama:install-complete", completionHandler);
|
window.api.apikey.removeOnOllamaInstallComplete(completionHandler);
|
||||||
await this._handleOllamaSetupCompletion(false, error.message);
|
await this._handleOllamaSetupCompletion(false, error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1229,7 +1224,7 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
this.clearMessages();
|
this.clearMessages();
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
if (!window.api || !window.api.apikey) return;
|
||||||
let progressHandler = null;
|
let progressHandler = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1249,10 +1244,10 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Set up progress tracking
|
// Set up progress tracking
|
||||||
ipcRenderer.on('ollama:pull-progress', progressHandler);
|
window.api.apikey.onOllamaPullProgress(progressHandler);
|
||||||
|
|
||||||
// Execute the model pull with timeout
|
// Execute the model pull with timeout
|
||||||
const installPromise = ipcRenderer.invoke('ollama:pull-model', modelName);
|
const installPromise = window.api.apikey.pullModel(modelName);
|
||||||
const timeoutPromise = new Promise((_, reject) =>
|
const timeoutPromise = new Promise((_, reject) =>
|
||||||
setTimeout(() => reject(new Error('Installation timeout after 10 minutes')), 600000)
|
setTimeout(() => reject(new Error('Installation timeout after 10 minutes')), 600000)
|
||||||
);
|
);
|
||||||
@ -1281,7 +1276,7 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
} finally {
|
} finally {
|
||||||
// Comprehensive cleanup
|
// Comprehensive cleanup
|
||||||
if (progressHandler) {
|
if (progressHandler) {
|
||||||
ipcRenderer.removeListener('ollama:pull-progress', progressHandler);
|
window.api.apikey.removeOnOllamaPullProgress(progressHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.installingModel = null;
|
this.installingModel = null;
|
||||||
@ -1307,7 +1302,7 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
this.clearMessages();
|
this.clearMessages();
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
if (!window.api || !window.api.apikey) return;
|
||||||
let progressHandler = null;
|
let progressHandler = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1321,10 +1316,10 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ipcRenderer.on('whisper:download-progress', progressHandler);
|
window.api.apikey.onWhisperDownloadProgress(progressHandler);
|
||||||
|
|
||||||
// Start download with timeout protection
|
// Start download with timeout protection
|
||||||
const downloadPromise = ipcRenderer.invoke('whisper:download-model', modelId);
|
const downloadPromise = window.api.apikey.downloadModel(modelId);
|
||||||
const timeoutPromise = new Promise((_, reject) =>
|
const timeoutPromise = new Promise((_, reject) =>
|
||||||
setTimeout(() => reject(new Error('Download timeout after 10 minutes')), 600000)
|
setTimeout(() => reject(new Error('Download timeout after 10 minutes')), 600000)
|
||||||
);
|
);
|
||||||
@ -1351,7 +1346,7 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
} finally {
|
} finally {
|
||||||
// Cleanup
|
// Cleanup
|
||||||
if (progressHandler) {
|
if (progressHandler) {
|
||||||
ipcRenderer.removeListener('whisper:download-progress', progressHandler);
|
window.api.apikey.removeOnWhisperDownloadProgress(progressHandler);
|
||||||
}
|
}
|
||||||
delete this.whisperInstallingModels[modelId];
|
delete this.whisperInstallingModels[modelId];
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
@ -1412,7 +1407,7 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
this.clearMessages();
|
this.clearMessages();
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
if (!window.api || !window.api.apikey) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Handle LLM provider
|
// Handle LLM provider
|
||||||
@ -1436,14 +1431,14 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate Ollama is working
|
// Validate Ollama is working
|
||||||
llmResult = await ipcRenderer.invoke('model:validate-key', {
|
llmResult = await window.api.apikey.validateKey({
|
||||||
provider: 'ollama',
|
provider: 'ollama',
|
||||||
key: 'local'
|
key: 'local'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (llmResult.success) {
|
if (llmResult.success) {
|
||||||
// Set the selected model
|
// Set the selected model
|
||||||
await ipcRenderer.invoke('model:set-selected-model', {
|
await window.api.apikey.setSelectedModel({
|
||||||
type: 'llm',
|
type: 'llm',
|
||||||
modelId: this.selectedLlmModel
|
modelId: this.selectedLlmModel
|
||||||
});
|
});
|
||||||
@ -1454,7 +1449,7 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
throw new Error('Please enter LLM API key');
|
throw new Error('Please enter LLM API key');
|
||||||
}
|
}
|
||||||
|
|
||||||
llmResult = await ipcRenderer.invoke('model:validate-key', {
|
llmResult = await window.api.apikey.validateKey({
|
||||||
provider: this.llmProvider,
|
provider: this.llmProvider,
|
||||||
key: this.llmApiKey.trim()
|
key: this.llmApiKey.trim()
|
||||||
});
|
});
|
||||||
@ -1467,14 +1462,14 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
sttResult = { success: true };
|
sttResult = { success: true };
|
||||||
} else if (this.sttProvider === 'whisper') {
|
} else if (this.sttProvider === 'whisper') {
|
||||||
// For Whisper, just validate it's enabled (model download already handled in handleSttModelChange)
|
// For Whisper, just validate it's enabled (model download already handled in handleSttModelChange)
|
||||||
sttResult = await ipcRenderer.invoke('model:validate-key', {
|
sttResult = await window.api.apikey.validateKey({
|
||||||
provider: 'whisper',
|
provider: 'whisper',
|
||||||
key: 'local'
|
key: 'local'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sttResult.success && this.selectedSttModel) {
|
if (sttResult.success && this.selectedSttModel) {
|
||||||
// Set the selected model
|
// Set the selected model
|
||||||
await ipcRenderer.invoke('model:set-selected-model', {
|
await window.api.apikey.setSelectedModel({
|
||||||
type: 'stt',
|
type: 'stt',
|
||||||
modelId: this.selectedSttModel
|
modelId: this.selectedSttModel
|
||||||
});
|
});
|
||||||
@ -1485,7 +1480,7 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
throw new Error('Please enter STT API key');
|
throw new Error('Please enter STT API key');
|
||||||
}
|
}
|
||||||
|
|
||||||
sttResult = await ipcRenderer.invoke('model:validate-key', {
|
sttResult = await window.api.apikey.validateKey({
|
||||||
provider: this.sttProvider,
|
provider: this.sttProvider,
|
||||||
key: this.sttApiKey.trim()
|
key: this.sttApiKey.trim()
|
||||||
});
|
});
|
||||||
@ -1522,15 +1517,15 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
console.log("Requesting Firebase authentication from main process...")
|
console.log("Requesting Firebase authentication from main process...")
|
||||||
if (window.require) {
|
if (window.api && window.api.apikey) {
|
||||||
window.require("electron").ipcRenderer.invoke("start-firebase-auth")
|
window.api.apikey.startFirebaseAuth()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClose() {
|
handleClose() {
|
||||||
console.log("Close button clicked")
|
console.log("Close button clicked")
|
||||||
if (window.require) {
|
if (window.api && window.api.apikey) {
|
||||||
window.require("electron").ipcRenderer.invoke("quit-application")
|
window.api.apikey.quitApplication()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1543,8 +1538,8 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
|
|
||||||
console.log('[ApiKeyHeader] handleAnimationEnd: Animation completed, transitioning to next state...');
|
console.log('[ApiKeyHeader] handleAnimationEnd: Animation completed, transitioning to next state...');
|
||||||
|
|
||||||
if (!window.require) {
|
if (!window.api || !window.api.apikey) {
|
||||||
console.error('[ApiKeyHeader] handleAnimationEnd: window.require not available');
|
console.error('[ApiKeyHeader] handleAnimationEnd: window.api.apikey not available');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1553,14 +1548,12 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
window.api.apikey.getCurrentUser()
|
||||||
|
|
||||||
ipcRenderer.invoke('get-current-user')
|
|
||||||
.then(userState => {
|
.then(userState => {
|
||||||
console.log('[ApiKeyHeader] handleAnimationEnd: User state retrieved:', userState);
|
console.log('[ApiKeyHeader] handleAnimationEnd: User state retrieved:', userState);
|
||||||
|
|
||||||
// Additional validation for local providers
|
// Additional validation for local providers
|
||||||
return ipcRenderer.invoke('model:are-providers-configured').then(isConfigured => {
|
return window.api.apikey.areProvidersConfigured().then(isConfigured => {
|
||||||
console.log('[ApiKeyHeader] handleAnimationEnd: Providers configured check:', isConfigured);
|
console.log('[ApiKeyHeader] handleAnimationEnd: Providers configured check:', isConfigured);
|
||||||
|
|
||||||
if (!isConfigured) {
|
if (!isConfigured) {
|
||||||
@ -1624,12 +1617,11 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup event listeners
|
// Cleanup event listeners
|
||||||
if (window.require) {
|
if (window.api && window.api.apikey) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
window.api.apikey.removeAllListeners('whisper:download-progress');
|
||||||
ipcRenderer.removeAllListeners('whisper:download-progress');
|
window.api.apikey.removeAllListeners('ollama:install-progress');
|
||||||
ipcRenderer.removeAllListeners('ollama:install-progress');
|
window.api.apikey.removeAllListeners('ollama:pull-progress');
|
||||||
ipcRenderer.removeAllListeners('ollama:pull-progress');
|
window.api.apikey.removeAllListeners('ollama:install-complete');
|
||||||
ipcRenderer.removeAllListeners('ollama:install-complete');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel any ongoing downloads
|
// Cancel any ongoing downloads
|
@ -50,22 +50,20 @@ class HeaderTransitionManager {
|
|||||||
|
|
||||||
this._bootstrap();
|
this._bootstrap();
|
||||||
|
|
||||||
if (window.require) {
|
if (window.api && window.api.controller) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
window.api.controller.onUserStateChanged((event, userState) => {
|
||||||
|
|
||||||
ipcRenderer.on('user-state-changed', (event, userState) => {
|
|
||||||
console.log('[HeaderController] Received user state change:', userState);
|
console.log('[HeaderController] Received user state change:', userState);
|
||||||
this.handleStateUpdate(userState);
|
this.handleStateUpdate(userState);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('auth-failed', (event, { message }) => {
|
window.api.controller.onAuthFailed((event, { message }) => {
|
||||||
console.error('[HeaderController] Received auth failure from main process:', message);
|
console.error('[HeaderController] Received auth failure from main process:', message);
|
||||||
if (this.apiKeyHeader) {
|
if (this.apiKeyHeader) {
|
||||||
this.apiKeyHeader.errorMessage = 'Authentication failed. Please try again.';
|
this.apiKeyHeader.errorMessage = 'Authentication failed. Please try again.';
|
||||||
this.apiKeyHeader.isLoading = false;
|
this.apiKeyHeader.isLoading = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ipcRenderer.on('force-show-apikey-header', async () => {
|
window.api.controller.onForceShowApiKeyHeader(async () => {
|
||||||
console.log('[HeaderController] Received broadcast to show apikey header. Switching now.');
|
console.log('[HeaderController] Received broadcast to show apikey header. Switching now.');
|
||||||
await this._resizeForApiKey();
|
await this._resizeForApiKey();
|
||||||
this.ensureHeader('apikey');
|
this.ensureHeader('apikey');
|
||||||
@ -75,16 +73,16 @@ class HeaderTransitionManager {
|
|||||||
|
|
||||||
notifyHeaderState(stateOverride) {
|
notifyHeaderState(stateOverride) {
|
||||||
const state = stateOverride || this.currentHeaderType || 'apikey';
|
const state = stateOverride || this.currentHeaderType || 'apikey';
|
||||||
if (window.require) {
|
if (window.api && window.api.controller) {
|
||||||
window.require('electron').ipcRenderer.send('header-state-changed', state);
|
window.api.controller.sendHeaderStateChanged(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _bootstrap() {
|
async _bootstrap() {
|
||||||
// The initial state will be sent by the main process via 'user-state-changed'
|
// The initial state will be sent by the main process via 'user-state-changed'
|
||||||
// We just need to request it.
|
// We just need to request it.
|
||||||
if (window.require) {
|
if (window.api && window.api.controller) {
|
||||||
const userState = await window.require('electron').ipcRenderer.invoke('get-current-user');
|
const userState = await window.api.controller.getCurrentUser();
|
||||||
console.log('[HeaderController] Bootstrapping with initial user state:', userState);
|
console.log('[HeaderController] Bootstrapping with initial user state:', userState);
|
||||||
this.handleStateUpdate(userState);
|
this.handleStateUpdate(userState);
|
||||||
} else {
|
} else {
|
||||||
@ -96,8 +94,8 @@ class HeaderTransitionManager {
|
|||||||
|
|
||||||
//////// after_modelStateService ////////
|
//////// after_modelStateService ////////
|
||||||
async handleStateUpdate(userState) {
|
async handleStateUpdate(userState) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
if (!window.api || !window.api.controller) return;
|
||||||
const isConfigured = await ipcRenderer.invoke('model:are-providers-configured');
|
const isConfigured = await window.api.controller.areProvidersConfigured();
|
||||||
|
|
||||||
if (isConfigured) {
|
if (isConfigured) {
|
||||||
const { isLoggedIn } = userState;
|
const { isLoggedIn } = userState;
|
||||||
@ -126,10 +124,9 @@ class HeaderTransitionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if permissions were previously completed
|
// Check if permissions were previously completed
|
||||||
if (window.require) {
|
if (window.api && window.api.controller) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
try {
|
try {
|
||||||
const permissionsCompleted = await ipcRenderer.invoke('check-permissions-completed');
|
const permissionsCompleted = await window.api.controller.checkPermissionsCompleted();
|
||||||
if (permissionsCompleted) {
|
if (permissionsCompleted) {
|
||||||
console.log('[HeaderController] Permissions were previously completed, checking current status...');
|
console.log('[HeaderController] Permissions were previously completed, checking current status...');
|
||||||
|
|
||||||
@ -162,38 +159,30 @@ class HeaderTransitionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_resizeForMain() {
|
_resizeForMain() {
|
||||||
if (!window.require) return;
|
if (!window.api || !window.api.controller) return;
|
||||||
return window
|
return window.api.controller.resizeHeaderWindow({ width: 353, height: 47 })
|
||||||
.require('electron')
|
|
||||||
.ipcRenderer.invoke('resize-header-window', { width: 353, height: 47 })
|
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _resizeForApiKey() {
|
async _resizeForApiKey() {
|
||||||
if (!window.require) return;
|
if (!window.api || !window.api.controller) return;
|
||||||
return window
|
return window.api.controller.resizeHeaderWindow({ width: 350, height: 300 })
|
||||||
.require('electron')
|
|
||||||
.ipcRenderer.invoke('resize-header-window', { width: 350, height: 300 })
|
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _resizeForPermissionHeader() {
|
async _resizeForPermissionHeader() {
|
||||||
if (!window.require) return;
|
if (!window.api || !window.api.controller) return;
|
||||||
return window
|
return window.api.controller.resizeHeaderWindow({ width: 285, height: 220 })
|
||||||
.require('electron')
|
|
||||||
.ipcRenderer.invoke('resize-header-window', { width: 285, height: 220 })
|
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkPermissions() {
|
async checkPermissions() {
|
||||||
if (!window.require) {
|
if (!window.api || !window.api.controller) {
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const permissions = await ipcRenderer.invoke('check-system-permissions');
|
const permissions = await window.api.controller.checkSystemPermissions();
|
||||||
console.log('[HeaderController] Current permissions:', permissions);
|
console.log('[HeaderController] Current permissions:', permissions);
|
||||||
|
|
||||||
if (!permissions.needsSetup) {
|
if (!permissions.needsSetup) {
|
@ -362,8 +362,8 @@ export class MainHeader extends LitElement {
|
|||||||
async handleMouseDown(e) {
|
async handleMouseDown(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
if (!window.api || !window.api.header) return;
|
||||||
const initialPosition = await ipcRenderer.invoke('get-header-position');
|
const initialPosition = await window.api.header.getHeaderPosition();
|
||||||
|
|
||||||
this.dragState = {
|
this.dragState = {
|
||||||
initialMouseX: e.screenX,
|
initialMouseX: e.screenX,
|
||||||
@ -390,8 +390,9 @@ export class MainHeader extends LitElement {
|
|||||||
const newWindowX = this.dragState.initialWindowX + (e.screenX - this.dragState.initialMouseX);
|
const newWindowX = this.dragState.initialWindowX + (e.screenX - this.dragState.initialMouseX);
|
||||||
const newWindowY = this.dragState.initialWindowY + (e.screenY - this.dragState.initialMouseY);
|
const newWindowY = this.dragState.initialWindowY + (e.screenY - this.dragState.initialMouseY);
|
||||||
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
if (window.api && window.api.header) {
|
||||||
ipcRenderer.invoke('move-header-to', newWindowX, newWindowY);
|
window.api.header.moveHeaderTo(newWindowX, newWindowY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp(e) {
|
handleMouseUp(e) {
|
||||||
@ -447,12 +448,12 @@ export class MainHeader extends LitElement {
|
|||||||
|
|
||||||
if (this.classList.contains('hiding')) {
|
if (this.classList.contains('hiding')) {
|
||||||
this.classList.add('hidden');
|
this.classList.add('hidden');
|
||||||
if (window.require) {
|
if (window.api && window.api.header) {
|
||||||
window.require('electron').ipcRenderer.send('header-animation-finished', 'hidden');
|
window.api.header.sendAnimationFinished('hidden');
|
||||||
}
|
}
|
||||||
} else if (this.classList.contains('showing')) {
|
} else if (this.classList.contains('showing')) {
|
||||||
if (window.require) {
|
if (window.api && window.api.header) {
|
||||||
window.require('electron').ipcRenderer.send('header-animation-finished', 'visible');
|
window.api.header.sendAnimationFinished('visible');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -466,14 +467,12 @@ export class MainHeader extends LitElement {
|
|||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.addEventListener('animationend', this.handleAnimationEnd);
|
this.addEventListener('animationend', this.handleAnimationEnd);
|
||||||
|
|
||||||
if (window.require) {
|
if (window.api && window.api.header) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
|
|
||||||
this._sessionStateTextListener = (event, text) => {
|
this._sessionStateTextListener = (event, text) => {
|
||||||
this.actionText = text;
|
this.actionText = text;
|
||||||
this.isTogglingSession = false;
|
this.isTogglingSession = false;
|
||||||
};
|
};
|
||||||
ipcRenderer.on('session-state-text', this._sessionStateTextListener);
|
window.api.header.onSessionStateText(this._sessionStateTextListener);
|
||||||
|
|
||||||
|
|
||||||
// this._sessionStateListener = (event, { isActive }) => {
|
// this._sessionStateListener = (event, { isActive }) => {
|
||||||
@ -485,7 +484,7 @@ export class MainHeader extends LitElement {
|
|||||||
console.log('[MainHeader] Received updated shortcuts:', keybinds);
|
console.log('[MainHeader] Received updated shortcuts:', keybinds);
|
||||||
this.shortcuts = keybinds;
|
this.shortcuts = keybinds;
|
||||||
};
|
};
|
||||||
ipcRenderer.on('shortcuts-updated', this._shortcutListener);
|
window.api.header.onShortcutsUpdated(this._shortcutListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -498,39 +497,37 @@ export class MainHeader extends LitElement {
|
|||||||
this.animationEndTimer = null;
|
this.animationEndTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.require) {
|
if (window.api && window.api.header) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
if (this._sessionStateTextListener) {
|
if (this._sessionStateTextListener) {
|
||||||
ipcRenderer.removeListener('session-state-text', this._sessionStateTextListener);
|
window.api.header.removeOnSessionStateText(this._sessionStateTextListener);
|
||||||
}
|
}
|
||||||
// if (this._sessionStateListener) {
|
// if (this._sessionStateListener) {
|
||||||
// ipcRenderer.removeListener('session-state-changed', this._sessionStateListener);
|
// ipcRenderer.removeListener('session-state-changed', this._sessionStateListener);
|
||||||
// }
|
// }
|
||||||
if (this._shortcutListener) {
|
if (this._shortcutListener) {
|
||||||
ipcRenderer.removeListener('shortcuts-updated', this._shortcutListener);
|
window.api.header.removeOnShortcutsUpdated(this._shortcutListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
invoke(channel, ...args) {
|
invoke(channel, ...args) {
|
||||||
if (this.wasJustDragged) return;
|
if (this.wasJustDragged) return;
|
||||||
if (window.require) {
|
if (window.api && window.api.header) {
|
||||||
window.require('electron').ipcRenderer.invoke(channel, ...args);
|
window.api.header.invoke(channel, ...args);
|
||||||
}
|
}
|
||||||
// return Promise.resolve();
|
// return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
showSettingsWindow(element) {
|
showSettingsWindow(element) {
|
||||||
if (this.wasJustDragged) return;
|
if (this.wasJustDragged) return;
|
||||||
if (window.require) {
|
if (window.api && window.api.header) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
console.log(`[MainHeader] showSettingsWindow called at ${Date.now()}`);
|
console.log(`[MainHeader] showSettingsWindow called at ${Date.now()}`);
|
||||||
|
|
||||||
ipcRenderer.send('cancel-hide-settings-window');
|
window.api.header.cancelHideSettingsWindow();
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
const { left, top, width, height } = element.getBoundingClientRect();
|
const { left, top, width, height } = element.getBoundingClientRect();
|
||||||
ipcRenderer.send('show-settings-window', {
|
window.api.header.showSettingsWindow({
|
||||||
x: left,
|
x: left,
|
||||||
y: top,
|
y: top,
|
||||||
width,
|
width,
|
||||||
@ -542,9 +539,9 @@ export class MainHeader extends LitElement {
|
|||||||
|
|
||||||
hideSettingsWindow() {
|
hideSettingsWindow() {
|
||||||
if (this.wasJustDragged) return;
|
if (this.wasJustDragged) return;
|
||||||
if (window.require) {
|
if (window.api && window.api.header) {
|
||||||
console.log(`[MainHeader] hideSettingsWindow called at ${Date.now()}`);
|
console.log(`[MainHeader] hideSettingsWindow called at ${Date.now()}`);
|
||||||
window.require('electron').ipcRenderer.send('hide-settings-window');
|
window.api.header.hideSettingsWindow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -288,13 +288,12 @@ export class PermissionHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkPermissions() {
|
async checkPermissions() {
|
||||||
if (!window.require || this.isChecking) return;
|
if (!window.api || !window.api.permissions || this.isChecking) return;
|
||||||
|
|
||||||
this.isChecking = true;
|
this.isChecking = true;
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const permissions = await ipcRenderer.invoke('check-system-permissions');
|
const permissions = await window.api.permissions.checkSystemPermissions();
|
||||||
console.log('[PermissionHeader] Permission check result:', permissions);
|
console.log('[PermissionHeader] Permission check result:', permissions);
|
||||||
|
|
||||||
const prevMic = this.microphoneGranted;
|
const prevMic = this.microphoneGranted;
|
||||||
@ -324,13 +323,12 @@ export class PermissionHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleMicrophoneClick() {
|
async handleMicrophoneClick() {
|
||||||
if (!window.require || this.microphoneGranted === 'granted') return;
|
if (!window.api || !window.api.permissions || this.microphoneGranted === 'granted') return;
|
||||||
|
|
||||||
console.log('[PermissionHeader] Requesting microphone permission...');
|
console.log('[PermissionHeader] Requesting microphone permission...');
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await ipcRenderer.invoke('check-system-permissions');
|
const result = await window.api.permissions.checkSystemPermissions();
|
||||||
console.log('[PermissionHeader] Microphone permission result:', result);
|
console.log('[PermissionHeader] Microphone permission result:', result);
|
||||||
|
|
||||||
if (result.microphone === 'granted') {
|
if (result.microphone === 'granted') {
|
||||||
@ -340,7 +338,7 @@ export class PermissionHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result.microphone === 'not-determined' || result.microphone === 'denied' || result.microphone === 'unknown' || result.microphone === 'restricted') {
|
if (result.microphone === 'not-determined' || result.microphone === 'denied' || result.microphone === 'unknown' || result.microphone === 'restricted') {
|
||||||
const res = await ipcRenderer.invoke('request-microphone-permission');
|
const res = await window.api.permissions.requestMicrophonePermission();
|
||||||
if (res.status === 'granted' || res.success === true) {
|
if (res.status === 'granted' || res.success === true) {
|
||||||
this.microphoneGranted = 'granted';
|
this.microphoneGranted = 'granted';
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
@ -357,13 +355,12 @@ export class PermissionHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleScreenClick() {
|
async handleScreenClick() {
|
||||||
if (!window.require || this.screenGranted === 'granted') return;
|
if (!window.api || !window.api.permissions || this.screenGranted === 'granted') return;
|
||||||
|
|
||||||
console.log('[PermissionHeader] Checking screen recording permission...');
|
console.log('[PermissionHeader] Checking screen recording permission...');
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const permissions = await ipcRenderer.invoke('check-system-permissions');
|
const permissions = await window.api.permissions.checkSystemPermissions();
|
||||||
console.log('[PermissionHeader] Screen permission check result:', permissions);
|
console.log('[PermissionHeader] Screen permission check result:', permissions);
|
||||||
|
|
||||||
if (permissions.screen === 'granted') {
|
if (permissions.screen === 'granted') {
|
||||||
@ -373,7 +370,7 @@ export class PermissionHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
if (permissions.screen === 'not-determined' || permissions.screen === 'denied' || permissions.screen === 'unknown' || permissions.screen === 'restricted') {
|
if (permissions.screen === 'not-determined' || permissions.screen === 'denied' || permissions.screen === 'unknown' || permissions.screen === 'restricted') {
|
||||||
console.log('[PermissionHeader] Opening screen recording preferences...');
|
console.log('[PermissionHeader] Opening screen recording preferences...');
|
||||||
await ipcRenderer.invoke('open-system-preferences', 'screen-recording');
|
await window.api.permissions.openSystemPreferences('screen-recording');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check permissions again after a delay
|
// Check permissions again after a delay
|
||||||
@ -389,10 +386,9 @@ export class PermissionHeader extends LitElement {
|
|||||||
this.microphoneGranted === 'granted' &&
|
this.microphoneGranted === 'granted' &&
|
||||||
this.screenGranted === 'granted') {
|
this.screenGranted === 'granted') {
|
||||||
// Mark permissions as completed
|
// Mark permissions as completed
|
||||||
if (window.require) {
|
if (window.api && window.api.permissions) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
try {
|
try {
|
||||||
await ipcRenderer.invoke('mark-permissions-completed');
|
await window.api.permissions.markPermissionsCompleted();
|
||||||
console.log('[PermissionHeader] Marked permissions as completed');
|
console.log('[PermissionHeader] Marked permissions as completed');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[PermissionHeader] Error marking permissions as completed:', error);
|
console.error('[PermissionHeader] Error marking permissions as completed:', error);
|
||||||
@ -405,8 +401,8 @@ export class PermissionHeader extends LitElement {
|
|||||||
|
|
||||||
handleClose() {
|
handleClose() {
|
||||||
console.log('Close button clicked');
|
console.log('Close button clicked');
|
||||||
if (window.require) {
|
if (window.api && window.api.permissions) {
|
||||||
window.require('electron').ipcRenderer.invoke('quit-application');
|
window.api.permissions.quitApplication();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
|||||||
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 { SettingsView } from '../features/settings/SettingsView.js';
|
import { SettingsView } from '../settings/SettingsView.js';
|
||||||
import { AssistantView } from '../features/listen/AssistantView.js';
|
import { ListenView } from '../listen/ListenView.js';
|
||||||
import { AskView } from '../features/ask/AskView.js';
|
import { AskView } from '../ask/AskView.js';
|
||||||
import { ShortcutSettingsView } from '../features/settings/ShortCutSettingsView.js';
|
import { ShortcutSettingsView } from '../settings/ShortCutSettingsView.js';
|
||||||
|
|
||||||
import '../features/listen/renderer/renderer.js';
|
import '../listen/audioCore/renderer.js';
|
||||||
|
|
||||||
export class PickleGlassApp extends LitElement {
|
export class PickleGlassApp extends LitElement {
|
||||||
static styles = css`
|
static styles = css`
|
||||||
@ -17,7 +17,7 @@ export class PickleGlassApp extends LitElement {
|
|||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
assistant-view {
|
listen-view {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -74,13 +74,11 @@ export class PickleGlassApp extends LitElement {
|
|||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
if (window.require) {
|
if (window.api && window.api.app) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
window.api.app.onClickThroughToggled((_, isEnabled) => {
|
||||||
|
|
||||||
ipcRenderer.on('click-through-toggled', (_, isEnabled) => {
|
|
||||||
this._isClickThrough = isEnabled;
|
this._isClickThrough = isEnabled;
|
||||||
});
|
});
|
||||||
// ipcRenderer.on('start-listening-session', () => {
|
// window.api.app.onStartListeningSession(() => {
|
||||||
// console.log('Received start-listening-session command, calling handleListenClick.');
|
// console.log('Received start-listening-session command, calling handleListenClick.');
|
||||||
// this.handleListenClick();
|
// this.handleListenClick();
|
||||||
// });
|
// });
|
||||||
@ -89,10 +87,9 @@ export class PickleGlassApp extends LitElement {
|
|||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
if (window.require) {
|
if (window.api && window.api.app) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
window.api.app.removeAllListeners('click-through-toggled');
|
||||||
ipcRenderer.removeAllListeners('click-through-toggled');
|
// window.api.app.removeAllListeners('start-listening-session');
|
||||||
// ipcRenderer.removeAllListeners('start-listening-session');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,9 +157,8 @@ export class PickleGlassApp extends LitElement {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
async handleClose() {
|
async handleClose() {
|
||||||
if (window.require) {
|
if (window.api && window.api.app) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
await window.api.app.quitApplication();
|
||||||
await ipcRenderer.invoke('quit-application');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,12 +168,12 @@ export class PickleGlassApp extends LitElement {
|
|||||||
render() {
|
render() {
|
||||||
switch (this.currentView) {
|
switch (this.currentView) {
|
||||||
case 'listen':
|
case 'listen':
|
||||||
return html`<assistant-view
|
return html`<listen-view
|
||||||
.currentResponseIndex=${this.currentResponseIndex}
|
.currentResponseIndex=${this.currentResponseIndex}
|
||||||
.selectedProfile=${this.selectedProfile}
|
.selectedProfile=${this.selectedProfile}
|
||||||
.structuredData=${this.structuredData}
|
.structuredData=${this.structuredData}
|
||||||
@response-index-changed=${e => (this.currentResponseIndex = e.detail.index)}
|
@response-index-changed=${e => (this.currentResponseIndex = e.detail.index)}
|
||||||
></assistant-view>`;
|
></listen-view>`;
|
||||||
case 'ask':
|
case 'ask':
|
||||||
return html`<ask-view></ask-view>`;
|
return html`<ask-view></ask-view>`;
|
||||||
case 'settings':
|
case 'settings':
|
@ -230,7 +230,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<script src="../assets/marked-4.3.0.min.js"></script>
|
<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>
|
<pickle-glass-app id="pickle-glass"></pickle-glass-app>
|
||||||
|
|
||||||
@ -238,15 +238,13 @@
|
|||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
const app = document.getElementById('pickle-glass');
|
const app = document.getElementById('pickle-glass');
|
||||||
|
|
||||||
if (window.require) {
|
if (window.api && window.api.animation) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
|
|
||||||
// --- REFACTORED: Event-driven animation handling ---
|
// --- REFACTORED: Event-driven animation handling ---
|
||||||
app.addEventListener('animationend', (event) => {
|
app.addEventListener('animationend', (event) => {
|
||||||
// 숨김 애니메이션이 끝나면 main 프로세스에 알려 창을 실제로 숨깁니다.
|
// 숨김 애니메이션이 끝나면 main 프로세스에 알려 창을 실제로 숨깁니다.
|
||||||
if (event.animationName === 'slideUpToHeader' || event.animationName === 'settingsCollapseToButton') {
|
if (event.animationName === 'slideUpToHeader' || event.animationName === 'settingsCollapseToButton') {
|
||||||
console.log(`Animation finished: ${event.animationName}. Notifying main process.`);
|
console.log(`Animation finished: ${event.animationName}. Notifying main process.`);
|
||||||
ipcRenderer.send('animation-finished');
|
window.api.animation.sendAnimationFinished();
|
||||||
|
|
||||||
// 완료 후 애니메이션 클래스 정리
|
// 완료 후 애니메이션 클래스 정리
|
||||||
app.classList.remove('window-sliding-up', 'settings-window-hide');
|
app.classList.remove('window-sliding-up', 'settings-window-hide');
|
||||||
@ -257,26 +255,26 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('window-show-animation', () => {
|
window.api.animation.onWindowShowAnimation(() => {
|
||||||
console.log('Starting window show animation');
|
console.log('Starting window show animation');
|
||||||
app.classList.remove('window-hidden', 'window-sliding-up', 'settings-window-hide');
|
app.classList.remove('window-hidden', 'window-sliding-up', 'settings-window-hide');
|
||||||
app.classList.add('window-sliding-down');
|
app.classList.add('window-sliding-down');
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('window-hide-animation', () => {
|
window.api.animation.onWindowHideAnimation(() => {
|
||||||
console.log('Starting window hide animation');
|
console.log('Starting window hide animation');
|
||||||
app.classList.remove('window-sliding-down', 'settings-window-show');
|
app.classList.remove('window-sliding-down', 'settings-window-show');
|
||||||
app.classList.add('window-sliding-up');
|
app.classList.add('window-sliding-up');
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('settings-window-hide-animation', () => {
|
window.api.animation.onSettingsWindowHideAnimation(() => {
|
||||||
console.log('Starting settings window hide animation');
|
console.log('Starting settings window hide animation');
|
||||||
app.classList.remove('window-sliding-down', 'settings-window-show');
|
app.classList.remove('window-sliding-down', 'settings-window-show');
|
||||||
app.classList.add('settings-window-hide');
|
app.classList.add('settings-window-hide');
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- UNCHANGED: Existing logic for listen window movement ---
|
// --- UNCHANGED: Existing logic for listen window movement ---
|
||||||
ipcRenderer.on('listen-window-move-to-center', () => {
|
window.api.animation.onListenWindowMoveToCenter(() => {
|
||||||
console.log('Moving listen window to center');
|
console.log('Moving listen window to center');
|
||||||
app.classList.add('listen-window-moving');
|
app.classList.add('listen-window-moving');
|
||||||
app.classList.remove('listen-window-left');
|
app.classList.remove('listen-window-left');
|
||||||
@ -287,7 +285,7 @@
|
|||||||
}, 350);
|
}, 350);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('listen-window-move-to-left', () => {
|
window.api.animation.onListenWindowMoveToLeft(() => {
|
||||||
console.log('Moving listen window to left');
|
console.log('Moving listen window to left');
|
||||||
app.classList.add('listen-window-moving');
|
app.classList.add('listen-window-moving');
|
||||||
app.classList.remove('listen-window-center');
|
app.classList.remove('listen-window-center');
|
@ -17,7 +17,7 @@
|
|||||||
<div id="header-container" tabindex="0" style="outline: none;">
|
<div id="header-container" tabindex="0" style="outline: none;">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="module" src="../../public/build/header.js"></script>
|
<script type="module" src="../../../public/build/header.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
if (params.get('glass') === 'true') {
|
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 {
|
export class AskView extends LitElement {
|
||||||
static properties = {
|
static properties = {
|
||||||
@ -729,18 +729,13 @@ export class AskView extends LitElement {
|
|||||||
this.handleStreamChunk = this.handleStreamChunk.bind(this);
|
this.handleStreamChunk = this.handleStreamChunk.bind(this);
|
||||||
this.handleStreamEnd = this.handleStreamEnd.bind(this);
|
this.handleStreamEnd = this.handleStreamEnd.bind(this);
|
||||||
this.handleSendText = this.handleSendText.bind(this);
|
this.handleSendText = this.handleSendText.bind(this);
|
||||||
this.handleGlobalSendRequest = this.handleGlobalSendRequest.bind(this);
|
|
||||||
this.handleTextKeydown = this.handleTextKeydown.bind(this);
|
this.handleTextKeydown = this.handleTextKeydown.bind(this);
|
||||||
this.closeResponsePanel = this.closeResponsePanel.bind(this);
|
|
||||||
this.handleCopy = this.handleCopy.bind(this);
|
this.handleCopy = this.handleCopy.bind(this);
|
||||||
this.clearResponseContent = this.clearResponseContent.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.handleEscKey = this.handleEscKey.bind(this);
|
||||||
this.handleDocumentClick = this.handleDocumentClick.bind(this);
|
|
||||||
this.handleWindowBlur = this.handleWindowBlur.bind(this);
|
|
||||||
|
|
||||||
this.handleScroll = this.handleScroll.bind(this);
|
this.handleScroll = this.handleScroll.bind(this);
|
||||||
|
this.handleCloseAskWindow = this.handleCloseAskWindow.bind(this);
|
||||||
|
this.handleCloseIfNoContent = this.handleCloseIfNoContent.bind(this);
|
||||||
|
|
||||||
this.loadLibraries();
|
this.loadLibraries();
|
||||||
|
|
||||||
@ -748,6 +743,94 @@ export class AskView extends LitElement {
|
|||||||
this.isThrottled = false;
|
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() {
|
async loadLibraries() {
|
||||||
try {
|
try {
|
||||||
if (!window.marked) {
|
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) {
|
if (!this.currentResponse && !this.isLoading && !this.isStreaming) {
|
||||||
const askContainer = this.shadowRoot?.querySelector('.ask-container');
|
this.handleCloseAskWindow();
|
||||||
if (askContainer && !e.composedPath().includes(askContainer)) {
|
|
||||||
this.closeIfNoContent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEscKey(e) {
|
handleEscKey(e) {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.closeResponsePanel();
|
this.handleCloseIfNoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleWindowBlur() {
|
clearResponseContent() {
|
||||||
if (!this.currentResponse && !this.isLoading && !this.isStreaming) {
|
this.currentResponse = '';
|
||||||
// If there's no active content, ask the main process to close this window.
|
this.currentQuestion = '';
|
||||||
if (window.require) {
|
this.isLoading = false;
|
||||||
const { ipcRenderer } = window.require('electron');
|
this.isStreaming = false;
|
||||||
ipcRenderer.invoke('close-ask-window-if-empty');
|
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) {
|
loadScript(src) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -875,125 +969,6 @@ export class AskView extends LitElement {
|
|||||||
return text;
|
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) {
|
handleScroll(direction) {
|
||||||
const scrollableElement = this.shadowRoot.querySelector('#responseContainer');
|
const scrollableElement = this.shadowRoot.querySelector('#responseContainer');
|
||||||
if (scrollableElement) {
|
if (scrollableElement) {
|
||||||
@ -1136,27 +1111,10 @@ export class AskView extends LitElement {
|
|||||||
this.adjustWindowHeightThrottled();
|
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) {
|
requestWindowResize(targetHeight) {
|
||||||
if (window.require) {
|
if (window.api && window.api.ask) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
window.api.ask.adjustWindowHeight(targetHeight);
|
||||||
ipcRenderer.invoke('adjust-window-height', targetHeight);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1196,13 +1154,6 @@ export class AskView extends LitElement {
|
|||||||
.replace(/`(.*?)`/g, '<code>$1</code>');
|
.replace(/`(.*?)`/g, '<code>$1</code>');
|
||||||
}
|
}
|
||||||
|
|
||||||
closeResponsePanel() {
|
|
||||||
if (window.require) {
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
ipcRenderer.invoke('force-close-window', 'ask');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fixIncompleteMarkdown(text) {
|
fixIncompleteMarkdown(text) {
|
||||||
if (!text) return text;
|
if (!text) return text;
|
||||||
|
|
||||||
@ -1240,29 +1191,6 @@ export class AskView extends LitElement {
|
|||||||
return text;
|
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() {
|
async handleCopy() {
|
||||||
if (this.copyState === 'copied') return;
|
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');
|
const textInput = this.shadowRoot?.getElementById('textInput');
|
||||||
if (!textInput) return;
|
const text = (overridingText || textInput?.value || '').trim();
|
||||||
const text = textInput.value.trim();
|
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
|
|
||||||
textInput.value = '';
|
textInput.value = '';
|
||||||
@ -1351,9 +1278,8 @@ export class AskView extends LitElement {
|
|||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
this.renderContent();
|
this.renderContent();
|
||||||
|
|
||||||
if (window.require) {
|
if (window.api && window.api.ask) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
window.api.ask.sendMessage(text).catch(error => {
|
||||||
ipcRenderer.invoke('ask:sendMessage', text).catch(error => {
|
|
||||||
console.error('Error sending text:', error);
|
console.error('Error sending text:', error);
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.isStreaming = false;
|
this.isStreaming = false;
|
||||||
@ -1393,37 +1319,10 @@ export class AskView extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
focusTextInput() {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
const textInput = this.shadowRoot?.getElementById('textInput');
|
|
||||||
if (textInput) {
|
|
||||||
textInput.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
firstUpdated() {
|
firstUpdated() {
|
||||||
setTimeout(() => this.adjustWindowHeight(), 200);
|
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) {
|
getTruncatedQuestion(question, maxLength = 30) {
|
||||||
if (!question) return '';
|
if (!question) return '';
|
||||||
@ -1431,24 +1330,7 @@ export class AskView extends LitElement {
|
|||||||
return question.substring(0, maxLength) + '...';
|
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() {
|
render() {
|
||||||
const hasResponse = this.isLoading || this.currentResponse || this.isStreaming;
|
const hasResponse = this.isLoading || this.currentResponse || this.isStreaming;
|
||||||
@ -1486,7 +1368,7 @@ export class AskView extends LitElement {
|
|||||||
<path d="M20 6L9 17l-5-5" />
|
<path d="M20 6L9 17l-5-5" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</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">
|
<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="18" y1="6" x2="6" y2="18" />
|
||||||
<line x1="6" y1="6" x2="18" 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"
|
placeholder="Ask about your screen or audio"
|
||||||
@keydown=${this.handleTextKeydown}
|
@keydown=${this.handleTextKeydown}
|
||||||
@focus=${this.handleInputFocus}
|
@focus=${this.handleInputFocus}
|
||||||
@blur=${this.handleInputBlur}
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="submit-btn"
|
class="submit-btn"
|
||||||
@ -1527,7 +1408,7 @@ export class AskView extends LitElement {
|
|||||||
|
|
||||||
// Dynamically resize the BrowserWindow to fit current content
|
// Dynamically resize the BrowserWindow to fit current content
|
||||||
adjustWindowHeight() {
|
adjustWindowHeight() {
|
||||||
if (!window.require) return;
|
if (!window.api || !window.api.ask) return;
|
||||||
|
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
const headerEl = this.shadowRoot.querySelector('.response-header');
|
const headerEl = this.shadowRoot.querySelector('.response-header');
|
||||||
@ -1544,8 +1425,7 @@ export class AskView extends LitElement {
|
|||||||
|
|
||||||
const targetHeight = Math.min(700, idealHeight);
|
const targetHeight = Math.min(700, idealHeight);
|
||||||
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
window.api.ask.adjustWindowHeight(targetHeight);
|
||||||
ipcRenderer.invoke('adjust-window-height', targetHeight);
|
|
||||||
|
|
||||||
}).catch(err => console.error('AskView adjustWindowHeight error:', err));
|
}).catch(err => console.error('AskView adjustWindowHeight error:', err));
|
||||||
}
|
}
|
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 './stt/SttView.js';
|
||||||
import './summary/SummaryView.js';
|
import './summary/SummaryView.js';
|
||||||
|
|
||||||
export class AssistantView extends LitElement {
|
export class ListenView extends LitElement {
|
||||||
static styles = css`
|
static styles = css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
@ -453,9 +453,8 @@ export class AssistantView extends LitElement {
|
|||||||
if (this.isSessionActive) {
|
if (this.isSessionActive) {
|
||||||
this.startTimer();
|
this.startTimer();
|
||||||
}
|
}
|
||||||
if (window.require) {
|
if (window.api && window.api.listen) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
window.api.listen.onSessionStateChanged((event, { isActive }) => {
|
||||||
ipcRenderer.on('session-state-changed', (event, { isActive }) => {
|
|
||||||
const wasActive = this.isSessionActive;
|
const wasActive = this.isSessionActive;
|
||||||
this.isSessionActive = isActive;
|
this.isSessionActive = isActive;
|
||||||
|
|
||||||
@ -514,7 +513,7 @@ export class AssistantView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
adjustWindowHeight() {
|
adjustWindowHeight() {
|
||||||
if (!window.require) return;
|
if (!window.api || !window.api.listen) return;
|
||||||
|
|
||||||
this.updateComplete
|
this.updateComplete
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -537,8 +536,7 @@ export class AssistantView extends LitElement {
|
|||||||
`[Height Adjusted] Mode: ${this.viewMode}, TopBar: ${topBarHeight}px, Content: ${contentHeight}px, Ideal: ${idealHeight}px, Target: ${targetHeight}px`
|
`[Height Adjusted] Mode: ${this.viewMode}, TopBar: ${topBarHeight}px, Content: ${contentHeight}px, Ideal: ${idealHeight}px, Target: ${targetHeight}px`
|
||||||
);
|
);
|
||||||
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
window.api.listen.adjustWindowHeight(targetHeight);
|
||||||
ipcRenderer.invoke('adjust-window-height', targetHeight);
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error in adjustWindowHeight:', error);
|
console.error('Error in adjustWindowHeight:', error);
|
||||||
@ -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 { ipcRenderer } = require('electron');
|
||||||
const createAecModule = require('../../../assets/aec.js');
|
const createAecModule = require('./aec.js');
|
||||||
|
|
||||||
let aecModPromise = null; // 한 번만 로드
|
let aecModPromise = null; // 한 번만 로드
|
||||||
let aecMod = null;
|
let aecMod = null;
|
||||||
@ -198,7 +198,7 @@ function runAecSync(micF32, sysF32) {
|
|||||||
|
|
||||||
|
|
||||||
// System audio data handler
|
// System audio data handler
|
||||||
ipcRenderer.on('system-audio-data', (event, { data }) => {
|
window.api.audio.onSystemAudioData((event, { data }) => {
|
||||||
systemAudioBuffer.push({
|
systemAudioBuffer.push({
|
||||||
data: data,
|
data: data,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
@ -336,7 +336,7 @@ async function setupMicProcessing(micStream) {
|
|||||||
const pcm16 = convertFloat32ToInt16(processedChunk);
|
const pcm16 = convertFloat32ToInt16(processedChunk);
|
||||||
const b64 = arrayBufferToBase64(pcm16.buffer);
|
const b64 = arrayBufferToBase64(pcm16.buffer);
|
||||||
|
|
||||||
ipcRenderer.invoke('send-audio-content', {
|
window.api.audio.sendAudioContent({
|
||||||
data: b64,
|
data: b64,
|
||||||
mimeType: 'audio/pcm;rate=24000',
|
mimeType: 'audio/pcm;rate=24000',
|
||||||
});
|
});
|
||||||
@ -369,7 +369,7 @@ function setupLinuxMicProcessing(micStream) {
|
|||||||
const pcmData16 = convertFloat32ToInt16(chunk);
|
const pcmData16 = convertFloat32ToInt16(chunk);
|
||||||
const base64Data = arrayBufferToBase64(pcmData16.buffer);
|
const base64Data = arrayBufferToBase64(pcmData16.buffer);
|
||||||
|
|
||||||
await ipcRenderer.invoke('send-audio-content', {
|
await window.api.audio.sendAudioContent({
|
||||||
data: base64Data,
|
data: base64Data,
|
||||||
mimeType: 'audio/pcm;rate=24000',
|
mimeType: 'audio/pcm;rate=24000',
|
||||||
});
|
});
|
||||||
@ -403,7 +403,7 @@ function setupSystemAudioProcessing(systemStream) {
|
|||||||
const base64Data = arrayBufferToBase64(pcmData16.buffer);
|
const base64Data = arrayBufferToBase64(pcmData16.buffer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ipcRenderer.invoke('send-system-audio-content', {
|
await window.api.audio.sendSystemAudioContent({
|
||||||
data: base64Data,
|
data: base64Data,
|
||||||
mimeType: 'audio/pcm;rate=24000',
|
mimeType: 'audio/pcm;rate=24000',
|
||||||
});
|
});
|
||||||
@ -433,7 +433,7 @@ async function captureScreenshot(imageQuality = 'medium', isManual = false) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Request screenshot from main process
|
// Request screenshot from main process
|
||||||
const result = await ipcRenderer.invoke('capture-screenshot', {
|
const result = await window.api.audio.captureScreenshot({
|
||||||
quality: imageQuality,
|
quality: imageQuality,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -470,7 +470,7 @@ async function captureManualScreenshot(imageQuality = null) {
|
|||||||
async function getCurrentScreenshot() {
|
async function getCurrentScreenshot() {
|
||||||
try {
|
try {
|
||||||
// First try to get a fresh screenshot from main process
|
// First try to get a fresh screenshot from main process
|
||||||
const result = await ipcRenderer.invoke('get-current-screenshot');
|
const result = await window.api.audio.getCurrentScreenshot();
|
||||||
|
|
||||||
if (result.success && result.base64) {
|
if (result.success && result.base64) {
|
||||||
console.log('📸 Got fresh screenshot from main process');
|
console.log('📸 Got fresh screenshot from main process');
|
||||||
@ -479,7 +479,7 @@ async function getCurrentScreenshot() {
|
|||||||
|
|
||||||
// If no screenshot available, capture one now
|
// If no screenshot available, capture one now
|
||||||
console.log('📸 No screenshot available, capturing new one');
|
console.log('📸 No screenshot available, capturing new one');
|
||||||
const captureResult = await ipcRenderer.invoke('capture-screenshot', {
|
const captureResult = await window.api.audio.captureScreenshot({
|
||||||
quality: currentImageQuality,
|
quality: currentImageQuality,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -518,15 +518,15 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
|
|||||||
console.log('Starting macOS capture with SystemAudioDump...');
|
console.log('Starting macOS capture with SystemAudioDump...');
|
||||||
|
|
||||||
// Start macOS audio capture
|
// Start macOS audio capture
|
||||||
const audioResult = await ipcRenderer.invoke('start-macos-audio');
|
const audioResult = await window.api.audio.startMacosAudio();
|
||||||
if (!audioResult.success) {
|
if (!audioResult.success) {
|
||||||
console.warn('[listenCapture] macOS audio start failed:', audioResult.error);
|
console.warn('[listenCapture] macOS audio start failed:', audioResult.error);
|
||||||
|
|
||||||
// 이미 실행 중 → stop 후 재시도
|
// 이미 실행 중 → stop 후 재시도
|
||||||
if (audioResult.error === 'already_running') {
|
if (audioResult.error === 'already_running') {
|
||||||
await ipcRenderer.invoke('stop-macos-audio');
|
await window.api.audio.stopMacosAudio();
|
||||||
await new Promise(r => setTimeout(r, 500));
|
await new Promise(r => setTimeout(r, 500));
|
||||||
const retry = await ipcRenderer.invoke('start-macos-audio');
|
const retry = await window.api.audio.startMacosAudio();
|
||||||
if (!retry.success) {
|
if (!retry.success) {
|
||||||
throw new Error('Retry failed: ' + retry.error);
|
throw new Error('Retry failed: ' + retry.error);
|
||||||
}
|
}
|
||||||
@ -536,7 +536,7 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize screen capture in main process
|
// Initialize screen capture in main process
|
||||||
const screenResult = await ipcRenderer.invoke('start-screen-capture');
|
const screenResult = await window.api.audio.startScreenCapture();
|
||||||
if (!screenResult.success) {
|
if (!screenResult.success) {
|
||||||
throw new Error('Failed to start screen capture: ' + screenResult.error);
|
throw new Error('Failed to start screen capture: ' + screenResult.error);
|
||||||
}
|
}
|
||||||
@ -604,13 +604,13 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
|
|||||||
console.log('Starting Windows capture with native loopback audio...');
|
console.log('Starting Windows capture with native loopback audio...');
|
||||||
|
|
||||||
// Start screen capture in main process for screenshots
|
// Start screen capture in main process for screenshots
|
||||||
const screenResult = await ipcRenderer.invoke('start-screen-capture');
|
const screenResult = await window.api.audio.startScreenCapture();
|
||||||
if (!screenResult.success) {
|
if (!screenResult.success) {
|
||||||
throw new Error('Failed to start screen capture: ' + screenResult.error);
|
throw new Error('Failed to start screen capture: ' + screenResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure STT sessions are initialized before starting audio capture
|
// Ensure STT sessions are initialized before starting audio capture
|
||||||
const sessionActive = await ipcRenderer.invoke('is-session-active');
|
const sessionActive = await window.api.audio.isSessionActive();
|
||||||
if (!sessionActive) {
|
if (!sessionActive) {
|
||||||
throw new Error('STT sessions not initialized - please wait for initialization to complete');
|
throw new Error('STT sessions not initialized - please wait for initialization to complete');
|
||||||
}
|
}
|
||||||
@ -715,13 +715,13 @@ function stopCapture() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stop screen capture in main process
|
// Stop screen capture in main process
|
||||||
ipcRenderer.invoke('stop-screen-capture').catch(err => {
|
window.api.audio.stopScreenCapture().catch(err => {
|
||||||
console.error('Error stopping screen capture:', err);
|
console.error('Error stopping screen capture:', err);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Stop macOS audio capture if running
|
// Stop macOS audio capture if running
|
||||||
if (isMacOS) {
|
if (isMacOS) {
|
||||||
ipcRenderer.invoke('stop-macos-audio').catch(err => {
|
window.api.audio.stopMacosAudio().catch(err => {
|
||||||
console.error('Error stopping macOS audio:', err);
|
console.error('Error stopping macOS audio:', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -15,7 +15,7 @@ window.pickleGlass = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
ipcRenderer.on('change-listen-capture-state', (_event, { status }) => {
|
window.api.audio.onChangeListenCaptureState((_event, { status }) => {
|
||||||
if (!isListenView) {
|
if (!isListenView) {
|
||||||
console.log('[Renderer] Non-listen view: ignoring capture-state change');
|
console.log('[Renderer] Non-listen view: ignoring capture-state change');
|
||||||
return;
|
return;
|
@ -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 {
|
export class SttView extends LitElement {
|
||||||
static styles = css`
|
static styles = css`
|
||||||
@ -95,17 +95,15 @@ export class SttView extends LitElement {
|
|||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
if (window.require) {
|
if (window.api && window.api.listen) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
window.api.listen.onSttUpdate(this.handleSttUpdate);
|
||||||
ipcRenderer.on('stt-update', this.handleSttUpdate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
if (window.require) {
|
if (window.api && window.api.listen) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
window.api.listen.removeOnSttUpdate(this.handleSttUpdate);
|
||||||
ipcRenderer.removeListener('stt-update', this.handleSttUpdate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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 {
|
export class SummaryView extends LitElement {
|
||||||
static styles = css`
|
static styles = css`
|
||||||
@ -262,9 +262,8 @@ export class SummaryView extends LitElement {
|
|||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
if (window.require) {
|
if (window.api && window.api.listen) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
window.api.listen.onSummaryUpdate((event, data) => {
|
||||||
ipcRenderer.on('summary-update', (event, data) => {
|
|
||||||
this.structuredData = data;
|
this.structuredData = data;
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
});
|
});
|
||||||
@ -273,9 +272,8 @@ export class SummaryView extends LitElement {
|
|||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
if (window.require) {
|
if (window.api && window.api.listen) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
window.api.listen.removeOnSummaryUpdate(() => {});
|
||||||
ipcRenderer.removeAllListeners('summary-update');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,18 +406,9 @@ export class SummaryView extends LitElement {
|
|||||||
async handleRequestClick(requestText) {
|
async handleRequestClick(requestText) {
|
||||||
console.log('🔥 Analysis request clicked:', requestText);
|
console.log('🔥 Analysis request clicked:', requestText);
|
||||||
|
|
||||||
if (window.require) {
|
if (window.api && window.api.listen) {
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const isAskViewVisible = await ipcRenderer.invoke('is-ask-window-visible', 'ask');
|
const result = await ipcRenderer.invoke('ask:sendQuestionToMain', requestText);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log('✅ Question sent to AskView successfully');
|
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 { 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 SettingsView extends LitElement {
|
export class SettingsView extends LitElement {
|
||||||
static styles = css`
|
static styles = css`
|
||||||
@ -543,11 +543,9 @@ export class SettingsView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadAutoUpdateSetting() {
|
async loadAutoUpdateSetting() {
|
||||||
if (!window.require) return;
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
this.autoUpdateLoading = true;
|
this.autoUpdateLoading = true;
|
||||||
try {
|
try {
|
||||||
const enabled = await ipcRenderer.invoke('settings:get-auto-update');
|
const enabled = await window.api.feature.settings.getAutoUpdate();
|
||||||
this.autoUpdateEnabled = enabled;
|
this.autoUpdateEnabled = enabled;
|
||||||
console.log('Auto-update setting loaded:', enabled);
|
console.log('Auto-update setting loaded:', enabled);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -559,13 +557,12 @@ export class SettingsView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleToggleAutoUpdate() {
|
async handleToggleAutoUpdate() {
|
||||||
if (!window.require || this.autoUpdateLoading) return;
|
if (this.autoUpdateLoading) return;
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
this.autoUpdateLoading = true;
|
this.autoUpdateLoading = true;
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
try {
|
try {
|
||||||
const newValue = !this.autoUpdateEnabled;
|
const newValue = !this.autoUpdateEnabled;
|
||||||
const result = await ipcRenderer.invoke('settings:set-auto-update', newValue);
|
const result = await window.api.feature.settings.setAutoUpdate(newValue);
|
||||||
if (result && result.success) {
|
if (result && result.success) {
|
||||||
this.autoUpdateEnabled = newValue;
|
this.autoUpdateEnabled = newValue;
|
||||||
} else {
|
} else {
|
||||||
@ -580,22 +577,20 @@ export class SettingsView extends LitElement {
|
|||||||
|
|
||||||
//////// after_modelStateService ////////
|
//////// after_modelStateService ////////
|
||||||
async loadInitialData() {
|
async loadInitialData() {
|
||||||
if (!window.require) return;
|
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
try {
|
try {
|
||||||
const [userState, config, storedKeys, availableLlm, availableStt, selectedModels, presets, contentProtection, shortcuts, ollamaStatus, whisperModelsResult] = await Promise.all([
|
const [userState, config, storedKeys, availableLlm, availableStt, selectedModels, presets, contentProtection, shortcuts, ollamaStatus, whisperModelsResult] = await Promise.all([
|
||||||
ipcRenderer.invoke('get-current-user'),
|
window.api.feature.settings.getCurrentUser(),
|
||||||
ipcRenderer.invoke('model:get-provider-config'), // Provider 설정 로드
|
window.api.feature.settings.getProviderConfig(), // Provider 설정 로드
|
||||||
ipcRenderer.invoke('model:get-all-keys'),
|
window.api.feature.settings.getAllKeys(),
|
||||||
ipcRenderer.invoke('model:get-available-models', { type: 'llm' }),
|
window.api.feature.settings.getAvailableModels({ type: 'llm' }),
|
||||||
ipcRenderer.invoke('model:get-available-models', { type: 'stt' }),
|
window.api.feature.settings.getAvailableModels({ type: 'stt' }),
|
||||||
ipcRenderer.invoke('model:get-selected-models'),
|
window.api.feature.settings.getSelectedModels(),
|
||||||
ipcRenderer.invoke('settings:getPresets'),
|
window.api.feature.settings.getPresets(),
|
||||||
ipcRenderer.invoke('get-content-protection-status'),
|
window.api.feature.settings.getContentProtectionStatus(),
|
||||||
ipcRenderer.invoke('get-current-shortcuts'),
|
window.api.feature.settings.getCurrentShortcuts(),
|
||||||
ipcRenderer.invoke('ollama:get-status'),
|
window.api.feature.settings.getOllamaStatus(),
|
||||||
ipcRenderer.invoke('whisper:get-installed-models')
|
window.api.feature.settings.getWhisperInstalledModels()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (userState && userState.isLoggedIn) this.firebaseUser = userState;
|
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
|
// For Ollama, we need to ensure it's ready first
|
||||||
if (provider === 'ollama') {
|
if (provider === 'ollama') {
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
|
|
||||||
// First ensure Ollama is installed and running
|
// First ensure Ollama is installed and running
|
||||||
const ensureResult = await ipcRenderer.invoke('ollama:ensure-ready');
|
const ensureResult = await window.api.feature.settings.ensureOllamaReady();
|
||||||
if (!ensureResult.success) {
|
if (!ensureResult.success) {
|
||||||
alert(`Failed to setup Ollama: ${ensureResult.error}`);
|
alert(`Failed to setup Ollama: ${ensureResult.error}`);
|
||||||
this.saving = false;
|
this.saving = false;
|
||||||
@ -655,7 +649,7 @@ export class SettingsView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Now validate (which will check if service is running)
|
// Now validate (which will check if service is running)
|
||||||
const result = await ipcRenderer.invoke('model:validate-key', { provider, key: 'local' });
|
const result = await window.api.feature.settings.validateKey({ provider, key: 'local' });
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.apiKeys = { ...this.apiKeys, [provider]: 'local' };
|
this.apiKeys = { ...this.apiKeys, [provider]: 'local' };
|
||||||
@ -671,8 +665,7 @@ export class SettingsView extends LitElement {
|
|||||||
// For Whisper, just enable it
|
// For Whisper, just enable it
|
||||||
if (provider === 'whisper') {
|
if (provider === 'whisper') {
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
const { ipcRenderer } = window.require('electron');
|
const result = await window.api.feature.settings.validateKey({ provider, key: 'local' });
|
||||||
const result = await ipcRenderer.invoke('model:validate-key', { provider, key: 'local' });
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.apiKeys = { ...this.apiKeys, [provider]: 'local' };
|
this.apiKeys = { ...this.apiKeys, [provider]: 'local' };
|
||||||
@ -686,8 +679,7 @@ export class SettingsView extends LitElement {
|
|||||||
|
|
||||||
// For other providers, use the normal flow
|
// For other providers, use the normal flow
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
const { ipcRenderer } = window.require('electron');
|
const result = await window.api.feature.settings.validateKey({ provider, key });
|
||||||
const result = await ipcRenderer.invoke('model:validate-key', { provider, key });
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.apiKeys = { ...this.apiKeys, [provider]: key };
|
this.apiKeys = { ...this.apiKeys, [provider]: key };
|
||||||
@ -701,20 +693,18 @@ export class SettingsView extends LitElement {
|
|||||||
|
|
||||||
async handleClearKey(provider) {
|
async handleClearKey(provider) {
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
const { ipcRenderer } = window.require('electron');
|
await window.api.feature.settings.removeApiKey({ provider });
|
||||||
await ipcRenderer.invoke('model:remove-api-key', { provider });
|
|
||||||
this.apiKeys = { ...this.apiKeys, [provider]: '' };
|
this.apiKeys = { ...this.apiKeys, [provider]: '' };
|
||||||
await this.refreshModelData();
|
await this.refreshModelData();
|
||||||
this.saving = false;
|
this.saving = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshModelData() {
|
async refreshModelData() {
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
const [availableLlm, availableStt, selected, storedKeys] = await Promise.all([
|
const [availableLlm, availableStt, selected, storedKeys] = await Promise.all([
|
||||||
ipcRenderer.invoke('model:get-available-models', { type: 'llm' }),
|
window.api.feature.settings.getAvailableModels({ type: 'llm' }),
|
||||||
ipcRenderer.invoke('model:get-available-models', { type: 'stt' }),
|
window.api.feature.settings.getAvailableModels({ type: 'stt' }),
|
||||||
ipcRenderer.invoke('model:get-selected-models'),
|
window.api.feature.settings.getSelectedModels(),
|
||||||
ipcRenderer.invoke('model:get-all-keys')
|
window.api.feature.settings.getAllKeys()
|
||||||
]);
|
]);
|
||||||
this.availableLlmModels = availableLlm;
|
this.availableLlmModels = availableLlm;
|
||||||
this.availableSttModels = availableStt;
|
this.availableSttModels = availableStt;
|
||||||
@ -765,8 +755,7 @@ export class SettingsView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
const { ipcRenderer } = window.require('electron');
|
await window.api.feature.settings.setSelectedModel({ type, modelId });
|
||||||
await ipcRenderer.invoke('model:set-selected-model', { type, modelId });
|
|
||||||
if (type === 'llm') this.selectedLlm = modelId;
|
if (type === 'llm') this.selectedLlm = modelId;
|
||||||
if (type === 'stt') this.selectedStt = modelId;
|
if (type === 'stt') this.selectedStt = modelId;
|
||||||
this.isLlmListVisible = false;
|
this.isLlmListVisible = false;
|
||||||
@ -776,8 +765,7 @@ export class SettingsView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async refreshOllamaStatus() {
|
async refreshOllamaStatus() {
|
||||||
const { ipcRenderer } = window.require('electron');
|
const ollamaStatus = await window.api.feature.settings.getOllamaStatus();
|
||||||
const ollamaStatus = await ipcRenderer.invoke('ollama:get-status');
|
|
||||||
if (ollamaStatus?.success) {
|
if (ollamaStatus?.success) {
|
||||||
this.ollamaStatus = { installed: ollamaStatus.installed, running: ollamaStatus.running };
|
this.ollamaStatus = { installed: ollamaStatus.installed, running: ollamaStatus.running };
|
||||||
this.ollamaModels = ollamaStatus.models || [];
|
this.ollamaModels = ollamaStatus.models || [];
|
||||||
@ -821,8 +809,6 @@ export class SettingsView extends LitElement {
|
|||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
|
|
||||||
// Set up progress listener
|
// Set up progress listener
|
||||||
const progressHandler = (event, { modelId: id, progress }) => {
|
const progressHandler = (event, { modelId: id, progress }) => {
|
||||||
if (id === modelId) {
|
if (id === modelId) {
|
||||||
@ -831,10 +817,10 @@ export class SettingsView extends LitElement {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ipcRenderer.on('whisper:download-progress', progressHandler);
|
window.api.feature.settings.onWhisperDownloadProgress(progressHandler);
|
||||||
|
|
||||||
// Start download
|
// Start download
|
||||||
const result = await ipcRenderer.invoke('whisper:download-model', modelId);
|
const result = await window.api.feature.settings.downloadWhisperModel(modelId);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// Auto-select the model after download
|
// Auto-select the model after download
|
||||||
@ -844,7 +830,7 @@ export class SettingsView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
ipcRenderer.removeListener('whisper:download-progress', progressHandler);
|
window.api.feature.settings.removeOnWhisperDownloadProgress(progressHandler);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[SettingsView] Error downloading Whisper model ${modelId}:`, error);
|
console.error(`[SettingsView] Error downloading Whisper model ${modelId}:`, error);
|
||||||
alert(`Error downloading ${modelId}: ${error.message}`);
|
alert(`Error downloading ${modelId}: ${error.message}`);
|
||||||
@ -876,17 +862,12 @@ export class SettingsView extends LitElement {
|
|||||||
if (this.wasJustDragged) return
|
if (this.wasJustDragged) return
|
||||||
|
|
||||||
console.log("Requesting Firebase authentication from main process...")
|
console.log("Requesting Firebase authentication from main process...")
|
||||||
if (window.require) {
|
window.api.feature.settings.startFirebaseAuth();
|
||||||
window.require("electron").ipcRenderer.invoke("start-firebase-auth")
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
//////// after_modelStateService ////////
|
//////// after_modelStateService ////////
|
||||||
|
|
||||||
openShortcutEditor() {
|
openShortcutEditor() {
|
||||||
if (window.require) {
|
window.api.feature.settings.openShortcutEditor();
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
ipcRenderer.invoke('open-shortcut-editor');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
@ -924,10 +905,6 @@ export class SettingsView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupIpcListeners() {
|
setupIpcListeners() {
|
||||||
if (!window.require) return;
|
|
||||||
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
|
|
||||||
this._userStateListener = (event, userState) => {
|
this._userStateListener = (event, userState) => {
|
||||||
console.log('[SettingsView] Received user-state-changed:', userState);
|
console.log('[SettingsView] Received user-state-changed:', userState);
|
||||||
if (userState && userState.isLoggedIn) {
|
if (userState && userState.isLoggedIn) {
|
||||||
@ -949,7 +926,7 @@ export class SettingsView extends LitElement {
|
|||||||
this._presetsUpdatedListener = async (event) => {
|
this._presetsUpdatedListener = async (event) => {
|
||||||
console.log('[SettingsView] Received presets-updated, refreshing presets');
|
console.log('[SettingsView] Received presets-updated, refreshing presets');
|
||||||
try {
|
try {
|
||||||
const presets = await ipcRenderer.invoke('settings:getPresets');
|
const presets = await window.api.feature.settings.getPresets();
|
||||||
this.presets = presets || [];
|
this.presets = presets || [];
|
||||||
|
|
||||||
// 현재 선택된 프리셋이 삭제되었는지 확인 (사용자 프리셋만 고려)
|
// 현재 선택된 프리셋이 삭제되었는지 확인 (사용자 프리셋만 고려)
|
||||||
@ -968,28 +945,24 @@ export class SettingsView extends LitElement {
|
|||||||
this.shortcuts = keybinds;
|
this.shortcuts = keybinds;
|
||||||
};
|
};
|
||||||
|
|
||||||
ipcRenderer.on('user-state-changed', this._userStateListener);
|
window.api.feature.settings.onUserStateChanged(this._userStateListener);
|
||||||
ipcRenderer.on('settings-updated', this._settingsUpdatedListener);
|
window.api.feature.settings.onSettingsUpdated(this._settingsUpdatedListener);
|
||||||
ipcRenderer.on('presets-updated', this._presetsUpdatedListener);
|
window.api.feature.settings.onPresetsUpdated(this._presetsUpdatedListener);
|
||||||
ipcRenderer.on('shortcuts-updated', this._shortcutListener);
|
window.api.feature.settings.onShortcutsUpdated(this._shortcutListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanupIpcListeners() {
|
cleanupIpcListeners() {
|
||||||
if (!window.require) return;
|
|
||||||
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
|
|
||||||
if (this._userStateListener) {
|
if (this._userStateListener) {
|
||||||
ipcRenderer.removeListener('user-state-changed', this._userStateListener);
|
window.api.feature.settings.removeOnUserStateChanged(this._userStateListener);
|
||||||
}
|
}
|
||||||
if (this._settingsUpdatedListener) {
|
if (this._settingsUpdatedListener) {
|
||||||
ipcRenderer.removeListener('settings-updated', this._settingsUpdatedListener);
|
window.api.feature.settings.removeOnSettingsUpdated(this._settingsUpdatedListener);
|
||||||
}
|
}
|
||||||
if (this._presetsUpdatedListener) {
|
if (this._presetsUpdatedListener) {
|
||||||
ipcRenderer.removeListener('presets-updated', this._presetsUpdatedListener);
|
window.api.feature.settings.removeOnPresetsUpdated(this._presetsUpdatedListener);
|
||||||
}
|
}
|
||||||
if (this._shortcutListener) {
|
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 = () => {
|
handleMouseEnter = () => {
|
||||||
if (window.require) {
|
window.api.window.cancelHideSettingsWindow();
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
ipcRenderer.send('cancel-hide-settings-window');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseLeave = () => {
|
handleMouseLeave = () => {
|
||||||
if (window.require) {
|
window.api.window.hideSettingsWindow();
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
ipcRenderer.send('hide-settings-window');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getMainShortcuts() {
|
// getMainShortcuts() {
|
||||||
@ -1083,39 +1050,27 @@ export class SettingsView extends LitElement {
|
|||||||
|
|
||||||
handleMoveLeft() {
|
handleMoveLeft() {
|
||||||
console.log('Move Left clicked');
|
console.log('Move Left clicked');
|
||||||
if (window.require) {
|
window.api.feature.settings.moveWindowStep('left');
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
ipcRenderer.invoke('move-window-step', 'left');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMoveRight() {
|
handleMoveRight() {
|
||||||
console.log('Move Right clicked');
|
console.log('Move Right clicked');
|
||||||
if (window.require) {
|
window.api.feature.settings.moveWindowStep('right');
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
ipcRenderer.invoke('move-window-step', 'right');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePersonalize() {
|
async handlePersonalize() {
|
||||||
console.log('Personalize clicked');
|
console.log('Personalize clicked');
|
||||||
if (window.require) {
|
try {
|
||||||
const { ipcRenderer } = window.require('electron');
|
await window.api.window.openLoginPage();
|
||||||
try {
|
} catch (error) {
|
||||||
await ipcRenderer.invoke('open-login-page');
|
console.error('Failed to open personalize page:', error);
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to open personalize page:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleToggleInvisibility() {
|
async handleToggleInvisibility() {
|
||||||
console.log('Toggle Invisibility clicked');
|
console.log('Toggle Invisibility clicked');
|
||||||
if (window.require) {
|
this.isContentProtectionOn = await window.api.window.toggleContentProtection();
|
||||||
const { ipcRenderer } = window.require('electron');
|
this.requestUpdate();
|
||||||
this.isContentProtectionOn = await ipcRenderer.invoke('toggle-content-protection');
|
|
||||||
this.requestUpdate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSaveApiKey() {
|
async handleSaveApiKey() {
|
||||||
@ -1123,62 +1078,46 @@ export class SettingsView extends LitElement {
|
|||||||
if (!input || !input.value) return;
|
if (!input || !input.value) return;
|
||||||
|
|
||||||
const newApiKey = input.value;
|
const newApiKey = input.value;
|
||||||
if (window.require) {
|
try {
|
||||||
const { ipcRenderer } = window.require('electron');
|
const result = await window.api.feature.settings.saveApiKey(newApiKey);
|
||||||
try {
|
if (result.success) {
|
||||||
const result = await ipcRenderer.invoke('settings:saveApiKey', newApiKey);
|
console.log('API Key saved successfully via IPC.');
|
||||||
if (result.success) {
|
this.apiKey = newApiKey;
|
||||||
console.log('API Key saved successfully via IPC.');
|
this.requestUpdate();
|
||||||
this.apiKey = newApiKey;
|
} else {
|
||||||
this.requestUpdate();
|
console.error('Failed to save API Key via IPC:', result.error);
|
||||||
} else {
|
|
||||||
console.error('Failed to save API Key via IPC:', result.error);
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
console.error('Error invoking save-api-key IPC:', e);
|
|
||||||
}
|
}
|
||||||
|
} catch(e) {
|
||||||
|
console.error('Error invoking save-api-key IPC:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleClearApiKey() {
|
async handleClearApiKey() {
|
||||||
console.log('Clear API Key clicked');
|
console.log('Clear API Key clicked');
|
||||||
if (window.require) {
|
await window.api.feature.settings.removeApiKey();
|
||||||
const { ipcRenderer } = window.require('electron');
|
this.apiKey = null;
|
||||||
await ipcRenderer.invoke('settings:removeApiKey');
|
this.requestUpdate();
|
||||||
this.apiKey = null;
|
|
||||||
this.requestUpdate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleQuit() {
|
handleQuit() {
|
||||||
console.log('Quit clicked');
|
console.log('Quit clicked');
|
||||||
if (window.require) {
|
window.api.window.quitApplication();
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
ipcRenderer.invoke('quit-application');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFirebaseLogout() {
|
handleFirebaseLogout() {
|
||||||
console.log('Firebase Logout clicked');
|
console.log('Firebase Logout clicked');
|
||||||
if (window.require) {
|
window.api.window.firebaseLogout();
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
ipcRenderer.invoke('firebase-logout');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleOllamaShutdown() {
|
async handleOllamaShutdown() {
|
||||||
console.log('[SettingsView] Shutting down Ollama service...');
|
console.log('[SettingsView] Shutting down Ollama service...');
|
||||||
|
|
||||||
if (!window.require) return;
|
|
||||||
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Show loading state
|
// Show loading state
|
||||||
this.ollamaStatus = { ...this.ollamaStatus, running: false };
|
this.ollamaStatus = { ...this.ollamaStatus, running: false };
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
|
|
||||||
const result = await ipcRenderer.invoke('ollama:shutdown', false); // Graceful shutdown
|
const result = await window.api.feature.settings.shutdownOllama(false); // Graceful shutdown
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log('[SettingsView] Ollama shut down successfully');
|
console.log('[SettingsView] Ollama shut down successfully');
|
||||||
@ -1330,329 +1269,6 @@ export class SettingsView extends LitElement {
|
|||||||
//////// before_modelStateService ////////
|
//////// before_modelStateService ////////
|
||||||
|
|
||||||
//////// after_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);
|
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([
|
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',
|
'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.feedback = {};
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.capturingKey = null;
|
this.capturingKey = null;
|
||||||
this.ipcRenderer = window.require ? window.require('electron').ipcRenderer : null;
|
this.hasAPI = window.api && window.api.settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
if (!this.ipcRenderer) return;
|
if (!this.hasAPI) return;
|
||||||
this.loadShortcutsHandler = (event, keybinds) => {
|
this.loadShortcutsHandler = (event, keybinds) => {
|
||||||
this.shortcuts = keybinds;
|
this.shortcuts = keybinds;
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
};
|
};
|
||||||
this.ipcRenderer.on('load-shortcuts', this.loadShortcutsHandler);
|
window.api.settings.onLoadShortcuts(this.loadShortcutsHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
if (this.ipcRenderer && this.loadShortcutsHandler) {
|
if (this.hasAPI && this.loadShortcutsHandler) {
|
||||||
this.ipcRenderer.removeListener('load-shortcuts', this.loadShortcutsHandler);
|
window.api.settings.removeOnLoadShortcuts(this.loadShortcutsHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,25 +171,25 @@ export class ShortcutSettingsView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleSave() {
|
async handleSave() {
|
||||||
if (!this.ipcRenderer) return;
|
if (!this.hasAPI) return;
|
||||||
const result = await this.ipcRenderer.invoke('save-shortcuts', this.shortcuts);
|
const result = await window.api.settings.saveShortcuts(this.shortcuts);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
alert('Failed to save shortcuts: ' + result.error);
|
alert('Failed to save shortcuts: ' + result.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClose() {
|
handleClose() {
|
||||||
if (!this.ipcRenderer) return;
|
if (!this.hasAPI) return;
|
||||||
this.ipcRenderer.send('close-shortcut-editor');
|
window.api.settings.closeShortcutEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleResetToDefault() {
|
async handleResetToDefault() {
|
||||||
if (!this.ipcRenderer) return;
|
if (!this.hasAPI) return;
|
||||||
const confirmation = confirm("Are you sure you want to reset all shortcuts to their default values?");
|
const confirmation = confirm("Are you sure you want to reset all shortcuts to their default values?");
|
||||||
if (!confirmation) return;
|
if (!confirmation) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const defaultShortcuts = await this.ipcRenderer.invoke('get-default-shortcuts');
|
const defaultShortcuts = await window.api.settings.getDefaultShortcuts();
|
||||||
this.shortcuts = defaultShortcuts;
|
this.shortcuts = defaultShortcuts;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('Failed to load default settings.');
|
alert('Failed to load default settings.');
|