featureBridge init
This commit is contained in:
parent
2cf71f1034
commit
bf20d002ba
@ -56,7 +56,23 @@ module.exports = {
|
|||||||
ipcMain.handle('ollama:get-warm-up-status', async () => await ollamaService.handleGetWarmUpStatus());
|
ipcMain.handle('ollama:get-warm-up-status', async () => await ollamaService.handleGetWarmUpStatus());
|
||||||
ipcMain.handle('ollama:shutdown', async (event, force = false) => await ollamaService.handleShutdown(event, force));
|
ipcMain.handle('ollama:shutdown', async (event, force = false) => await ollamaService.handleShutdown(event, force));
|
||||||
|
|
||||||
// ModelStateService
|
// Ask
|
||||||
|
ipcMain.handle('ask:sendMessage', async (event, userPrompt, conversationHistoryRaw = []) => await askService.sendMessage(userPrompt, conversationHistoryRaw));
|
||||||
|
|
||||||
|
// Listen
|
||||||
|
ipcMain.handle('send-audio-content', async (event, { data, mimeType }) => await listenService.handleSendAudioContent(data, mimeType));
|
||||||
|
ipcMain.handle('send-system-audio-content', async (event, { data, mimeType }) => {
|
||||||
|
const result = await listenService.sttService.sendSystemAudioContent(data, mimeType);
|
||||||
|
if(result.success) {
|
||||||
|
listenService.sendToRenderer('system-audio-data', { data });
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
ipcMain.handle('start-macos-audio', async () => await listenService.handleStartMacosAudio());
|
||||||
|
ipcMain.handle('stop-macos-audio', async () => await listenService.handleStopMacosAudio());
|
||||||
|
ipcMain.handle('update-google-search-setting', async (event, enabled) => await listenService.handleUpdateGoogleSearchSetting(enabled));
|
||||||
|
|
||||||
|
// ModelStateService
|
||||||
ipcMain.handle('model:validate-key', async (e, { provider, key }) => await modelStateService.handleValidateKey(provider, key));
|
ipcMain.handle('model:validate-key', async (e, { provider, key }) => await modelStateService.handleValidateKey(provider, key));
|
||||||
ipcMain.handle('model:get-all-keys', () => modelStateService.getAllApiKeys());
|
ipcMain.handle('model:get-all-keys', () => modelStateService.getAllApiKeys());
|
||||||
ipcMain.handle('model:set-api-key', async (e, { provider, key }) => await modelStateService.setApiKey(provider, key));
|
ipcMain.handle('model:set-api-key', async (e, { provider, key }) => await modelStateService.setApiKey(provider, key));
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const { ipcMain, BrowserWindow } = require('electron');
|
const { BrowserWindow } = require('electron');
|
||||||
const { createStreamingLLM } = require('../common/ai/factory');
|
const { createStreamingLLM } = require('../common/ai/factory');
|
||||||
const { getCurrentModelInfo, windowPool, captureScreenshot } = require('../../window/windowManager');
|
const { getCurrentModelInfo, windowPool, captureScreenshot } = require('../../window/windowManager');
|
||||||
const sessionRepository = require('../common/repositories/session');
|
const sessionRepository = require('../common/repositories/session');
|
||||||
@ -17,17 +17,6 @@ class AskService {
|
|||||||
console.log('[AskService] Service instance created.');
|
console.log('[AskService] Service instance created.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* IPC 리스너를 등록하여 렌더러 프로세스로부터의 요청을 처리합니다.
|
|
||||||
* Electron 애플리케이션의 메인 프로세스에서 한 번만 호출되어야 합니다.
|
|
||||||
*/
|
|
||||||
initialize() {
|
|
||||||
ipcMain.handle('ask:sendMessage', async (event, userPrompt, conversationHistoryRaw=[]) => {
|
|
||||||
return this.sendMessage(userPrompt, conversationHistoryRaw);
|
|
||||||
});
|
|
||||||
console.log('[AskService] Initialized and ready.');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 대화 기록 배열을 프롬프트에 적합한 단일 문자열로 변환합니다.
|
* 대화 기록 배열을 프롬프트에 적합한 단일 문자열로 변환합니다.
|
||||||
* @param {string[]} conversationTexts - 대화 내용 문자열의 배열
|
* @param {string[]} conversationTexts - 대화 내용 문자열의 배열
|
||||||
|
@ -36,15 +36,17 @@ class ModelStateService {
|
|||||||
console.log(`[ModelStateService] Current Selection -> LLM: ${llmModel || 'None'} (Provider: ${llmProvider}), STT: ${sttModel || 'None'} (Provider: ${sttProvider})`);
|
console.log(`[ModelStateService] Current Selection -> LLM: ${llmModel || 'None'} (Provider: ${llmProvider}), STT: ${sttModel || 'None'} (Provider: ${sttProvider})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
_autoSelectAvailableModels() {
|
_autoSelectAvailableModels(forceReselectionForTypes = []) {
|
||||||
console.log('[ModelStateService] Running auto-selection for models...');
|
console.log(`[ModelStateService] Running auto-selection for models. Force re-selection for: [${forceReselectionForTypes.join(', ')}]`);
|
||||||
const types = ['llm', 'stt'];
|
const types = ['llm', 'stt'];
|
||||||
|
|
||||||
types.forEach(type => {
|
types.forEach(type => {
|
||||||
const currentModelId = this.state.selectedModels[type];
|
const currentModelId = this.state.selectedModels[type];
|
||||||
let isCurrentModelValid = false;
|
let isCurrentModelValid = false;
|
||||||
|
|
||||||
if (currentModelId) {
|
const forceReselection = forceReselectionForTypes.includes(type);
|
||||||
|
|
||||||
|
if (currentModelId && !forceReselection) {
|
||||||
const provider = this.getProviderForModel(type, currentModelId);
|
const provider = this.getProviderForModel(type, currentModelId);
|
||||||
const apiKey = this.getApiKey(provider);
|
const apiKey = this.getApiKey(provider);
|
||||||
// For Ollama, 'local' is a valid API key
|
// For Ollama, 'local' is a valid API key
|
||||||
@ -54,7 +56,7 @@ class ModelStateService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isCurrentModelValid) {
|
if (!isCurrentModelValid) {
|
||||||
console.log(`[ModelStateService] No valid ${type.toUpperCase()} model selected. Finding an alternative...`);
|
console.log(`[ModelStateService] No valid ${type.toUpperCase()} model selected or re-selection forced. Finding an alternative...`);
|
||||||
const availableModels = this.getAvailableModels(type);
|
const availableModels = this.getAvailableModels(type);
|
||||||
if (availableModels.length > 0) {
|
if (availableModels.length > 0) {
|
||||||
// Prefer API providers over local providers for auto-selection
|
// Prefer API providers over local providers for auto-selection
|
||||||
@ -331,7 +333,16 @@ class ModelStateService {
|
|||||||
async setApiKey(provider, key) {
|
async setApiKey(provider, key) {
|
||||||
if (provider in this.state.apiKeys) {
|
if (provider in this.state.apiKeys) {
|
||||||
this.state.apiKeys[provider] = key;
|
this.state.apiKeys[provider] = key;
|
||||||
this._autoSelectAvailableModels();
|
|
||||||
|
const supportedTypes = [];
|
||||||
|
if (PROVIDERS[provider]?.llmModels.length > 0 || provider === 'ollama') {
|
||||||
|
supportedTypes.push('llm');
|
||||||
|
}
|
||||||
|
if (PROVIDERS[provider]?.sttModels.length > 0 || provider === 'whisper') {
|
||||||
|
supportedTypes.push('stt');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._autoSelectAvailableModels(supportedTypes);
|
||||||
await this._saveState();
|
await this._saveState();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -395,6 +406,8 @@ class ModelStateService {
|
|||||||
areProvidersConfigured() {
|
areProvidersConfigured() {
|
||||||
if (this.isLoggedInWithFirebase()) return true;
|
if (this.isLoggedInWithFirebase()) return true;
|
||||||
|
|
||||||
|
console.log('[DEBUG] Checking configured providers with apiKeys state:', JSON.stringify(this.state.apiKeys, (key, value) => (value ? '***' : null), 2));
|
||||||
|
|
||||||
// LLM과 STT 모델을 제공하는 Provider 중 하나라도 API 키가 설정되었는지 확인
|
// LLM과 STT 모델을 제공하는 Provider 중 하나라도 API 키가 설정되었는지 확인
|
||||||
const hasLlmKey = Object.entries(this.state.apiKeys).some(([provider, key]) => {
|
const hasLlmKey = Object.entries(this.state.apiKeys).some(([provider, key]) => {
|
||||||
if (provider === 'ollama') {
|
if (provider === 'ollama') {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const { ipcMain, BrowserWindow } = require('electron');
|
const { BrowserWindow } = 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');
|
||||||
@ -46,11 +46,6 @@ class ListenService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
|
||||||
this.setupIpcHandlers();
|
|
||||||
console.log('[ListenService] Initialized and ready.');
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleTranscriptionComplete(speaker, text) {
|
async handleTranscriptionComplete(speaker, text) {
|
||||||
console.log(`[ListenService] Transcription complete: ${speaker} - ${text}`);
|
console.log(`[ListenService] Transcription complete: ${speaker} - ${text}`);
|
||||||
|
|
||||||
@ -222,70 +217,57 @@ class ListenService {
|
|||||||
return this.summaryService.getConversationHistory();
|
return this.summaryService.getConversationHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupIpcHandlers() {
|
_createHandler(asyncFn, successMessage, errorMessage) {
|
||||||
ipcMain.handle('send-audio-content', async (event, { data, mimeType }) => {
|
return async (...args) => {
|
||||||
try {
|
try {
|
||||||
await this.sendAudioContent(data, mimeType);
|
const result = await asyncFn.apply(this, args);
|
||||||
return { success: true };
|
if (successMessage) console.log(successMessage);
|
||||||
|
// `startMacOSAudioCapture`는 성공 시 { success, error } 객체를 반환하지 않으므로,
|
||||||
|
// 핸들러가 일관된 응답을 보내도록 여기서 success 객체를 반환합니다.
|
||||||
|
// 다른 함수들은 이미 success 객체를 반환합니다.
|
||||||
|
return result && typeof result.success !== 'undefined' ? result : { success: true };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error sending user audio:', e);
|
console.error(errorMessage, e);
|
||||||
return { success: false, error: e.message };
|
return { success: false, error: e.message };
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
}
|
||||||
|
|
||||||
ipcMain.handle('send-system-audio-content', async (event, { data, mimeType }) => {
|
// `_createHandler`를 사용하여 핸들러들을 동적으로 생성합니다.
|
||||||
try {
|
handleSendAudioContent = this._createHandler(
|
||||||
await this.sttService.sendSystemAudioContent(data, mimeType);
|
this.sendAudioContent,
|
||||||
|
null,
|
||||||
// Send system audio data back to renderer for AEC reference (like macOS does)
|
'Error sending user audio:'
|
||||||
this.sendToRenderer('system-audio-data', { data });
|
);
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error sending system audio:', error);
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('start-macos-audio', async () => {
|
handleStartMacosAudio = this._createHandler(
|
||||||
|
async () => {
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
return { success: false, error: 'macOS audio capture only available on macOS' };
|
return { success: false, error: 'macOS audio capture only available on macOS' };
|
||||||
}
|
}
|
||||||
if (this.sttService.isMacOSAudioRunning?.()) {
|
if (this.sttService.isMacOSAudioRunning?.()) {
|
||||||
return { success: false, error: 'already_running' };
|
return { success: false, error: 'already_running' };
|
||||||
}
|
}
|
||||||
|
await this.startMacOSAudioCapture();
|
||||||
|
return { success: true, error: null };
|
||||||
|
},
|
||||||
|
'macOS audio capture started.',
|
||||||
|
'Error starting macOS audio capture:'
|
||||||
|
);
|
||||||
|
|
||||||
|
handleStopMacosAudio = this._createHandler(
|
||||||
|
this.stopMacOSAudioCapture,
|
||||||
|
'macOS audio capture stopped.',
|
||||||
|
'Error stopping macOS audio capture:'
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
handleUpdateGoogleSearchSetting = this._createHandler(
|
||||||
const success = await this.startMacOSAudioCapture();
|
async (enabled) => {
|
||||||
return { success, error: null };
|
console.log('Google Search setting updated to:', enabled);
|
||||||
} catch (error) {
|
},
|
||||||
console.error('Error starting macOS audio capture:', error);
|
null,
|
||||||
return { success: false, error: error.message };
|
'Error updating Google Search setting:'
|
||||||
}
|
);
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('stop-macos-audio', async () => {
|
|
||||||
try {
|
|
||||||
this.stopMacOSAudioCapture();
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error stopping macOS audio capture:', error);
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('update-google-search-setting', async (event, enabled) => {
|
|
||||||
try {
|
|
||||||
console.log('Google Search setting updated to:', enabled);
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error updating Google Search setting:', error);
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('✅ Listen service IPC handlers registered');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const listenService = new ListenService();
|
const listenService = new ListenService();
|
||||||
|
@ -41,6 +41,17 @@ class SttService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleSendSystemAudioContent(data, mimeType) {
|
||||||
|
try {
|
||||||
|
await this.sendSystemAudioContent(data, mimeType);
|
||||||
|
this.sendToRenderer('system-audio-data', { data });
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending system audio:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
flushMyCompletion() {
|
flushMyCompletion() {
|
||||||
const finalText = (this.myCompletionBuffer + this.myCurrentUtterance).trim();
|
const finalText = (this.myCompletionBuffer + this.myCurrentUtterance).trim();
|
||||||
if (!this.modelInfo || !finalText) return;
|
if (!this.modelInfo || !finalText) return;
|
||||||
|
@ -197,8 +197,6 @@ app.whenReady().then(async () => {
|
|||||||
await modelStateService.initialize();
|
await modelStateService.initialize();
|
||||||
//////// after_modelStateService ////////
|
//////// after_modelStateService ////////
|
||||||
|
|
||||||
listenService.initialize();
|
|
||||||
askService.initialize();
|
|
||||||
featureBridge.initialize(); // 추가: featureBridge 초기화
|
featureBridge.initialize(); // 추가: featureBridge 초기화
|
||||||
setupWebDataHandlers();
|
setupWebDataHandlers();
|
||||||
|
|
||||||
|
@ -1541,7 +1541,7 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
this.classList.remove("sliding-out");
|
this.classList.remove("sliding-out");
|
||||||
this.classList.add("hidden");
|
this.classList.add("hidden");
|
||||||
|
|
||||||
console.log('[ApiKeyHeader] handleAnimationEnd: Animation completed, transitioning to next state...');
|
console.log('[ApiKeyHeader] handleAnimationEnd: Transition completed, transitioning to next state...');
|
||||||
|
|
||||||
if (!window.require) {
|
if (!window.require) {
|
||||||
console.error('[ApiKeyHeader] handleAnimationEnd: window.require not available');
|
console.error('[ApiKeyHeader] handleAnimationEnd: window.require not available');
|
||||||
@ -1585,7 +1585,8 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback()
|
super.connectedCallback()
|
||||||
this.addEventListener("animationend", this.handleAnimationEnd)
|
// this.addEventListener("animationend", this.handleAnimationEnd)
|
||||||
|
this.addEventListener("transitionend", this.handleAnimationEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMessageFadeEnd(e) {
|
handleMessageFadeEnd(e) {
|
||||||
@ -1603,8 +1604,8 @@ export class ApiKeyHeader extends LitElement {
|
|||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
super.disconnectedCallback()
|
super.disconnectedCallback()
|
||||||
this.removeEventListener("animationend", this.handleAnimationEnd)
|
// this.removeEventListener("animationend", this.handleAnimationEnd)
|
||||||
|
this.removeEventListener("transitionend", this.handleAnimationEnd)
|
||||||
// Professional cleanup of all resources
|
// Professional cleanup of all resources
|
||||||
this._performCompleteCleanup();
|
this._performCompleteCleanup();
|
||||||
}
|
}
|
||||||
|
@ -96,8 +96,12 @@ class HeaderTransitionManager {
|
|||||||
|
|
||||||
//////// after_modelStateService ////////
|
//////// after_modelStateService ////////
|
||||||
async handleStateUpdate(userState) {
|
async handleStateUpdate(userState) {
|
||||||
|
console.log('[HeaderController DEBUG] handleStateUpdate called with userState:', userState);
|
||||||
const { ipcRenderer } = window.require('electron');
|
const { ipcRenderer } = window.require('electron');
|
||||||
|
|
||||||
|
console.log('[HeaderController DEBUG] Invoking "model:are-providers-configured"...');
|
||||||
const isConfigured = await ipcRenderer.invoke('model:are-providers-configured');
|
const isConfigured = await ipcRenderer.invoke('model:are-providers-configured');
|
||||||
|
console.log('[HeaderController DEBUG] "model:are-providers-configured" returned:', isConfigured);
|
||||||
|
|
||||||
if (isConfigured) {
|
if (isConfigured) {
|
||||||
const { isLoggedIn } = userState;
|
const { isLoggedIn } = userState;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user