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: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:get-all-keys', () => modelStateService.getAllApiKeys());
 | 
			
		||||
    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 { getCurrentModelInfo, windowPool, captureScreenshot } = require('../../window/windowManager');
 | 
			
		||||
const sessionRepository = require('../common/repositories/session');
 | 
			
		||||
@ -17,17 +17,6 @@ class AskService {
 | 
			
		||||
        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 - 대화 내용 문자열의 배열
 | 
			
		||||
 | 
			
		||||
@ -36,15 +36,17 @@ class ModelStateService {
 | 
			
		||||
        console.log(`[ModelStateService] Current Selection -> LLM: ${llmModel || 'None'} (Provider: ${llmProvider}), STT: ${sttModel || 'None'} (Provider: ${sttProvider})`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _autoSelectAvailableModels() {
 | 
			
		||||
        console.log('[ModelStateService] Running auto-selection for models...');
 | 
			
		||||
    _autoSelectAvailableModels(forceReselectionForTypes = []) {
 | 
			
		||||
        console.log(`[ModelStateService] Running auto-selection for models. Force re-selection for: [${forceReselectionForTypes.join(', ')}]`);
 | 
			
		||||
        const types = ['llm', 'stt'];
 | 
			
		||||
 | 
			
		||||
        types.forEach(type => {
 | 
			
		||||
            const currentModelId = this.state.selectedModels[type];
 | 
			
		||||
            let isCurrentModelValid = false;
 | 
			
		||||
 | 
			
		||||
            if (currentModelId) {
 | 
			
		||||
            const forceReselection = forceReselectionForTypes.includes(type);
 | 
			
		||||
 | 
			
		||||
            if (currentModelId && !forceReselection) {
 | 
			
		||||
                const provider = this.getProviderForModel(type, currentModelId);
 | 
			
		||||
                const apiKey = this.getApiKey(provider);
 | 
			
		||||
                // For Ollama, 'local' is a valid API key
 | 
			
		||||
@ -54,7 +56,7 @@ class ModelStateService {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
                if (availableModels.length > 0) {
 | 
			
		||||
                    // Prefer API providers over local providers for auto-selection
 | 
			
		||||
@ -331,7 +333,16 @@ class ModelStateService {
 | 
			
		||||
    async setApiKey(provider, key) {
 | 
			
		||||
        if (provider in this.state.apiKeys) {
 | 
			
		||||
            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();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
@ -395,6 +406,8 @@ class ModelStateService {
 | 
			
		||||
    areProvidersConfigured() {
 | 
			
		||||
        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 키가 설정되었는지 확인
 | 
			
		||||
        const hasLlmKey = Object.entries(this.state.apiKeys).some(([provider, key]) => {
 | 
			
		||||
            if (provider === 'ollama') {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
const { ipcMain, BrowserWindow } = require('electron');
 | 
			
		||||
const { BrowserWindow } = require('electron');
 | 
			
		||||
const SttService = require('./stt/sttService');
 | 
			
		||||
const SummaryService = require('./summary/summaryService');
 | 
			
		||||
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) {
 | 
			
		||||
        console.log(`[ListenService] Transcription complete: ${speaker} - ${text}`);
 | 
			
		||||
        
 | 
			
		||||
@ -222,70 +217,57 @@ class ListenService {
 | 
			
		||||
        return this.summaryService.getConversationHistory();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setupIpcHandlers() {
 | 
			
		||||
        ipcMain.handle('send-audio-content', async (event, { data, mimeType }) => {
 | 
			
		||||
    _createHandler(asyncFn, successMessage, errorMessage) {
 | 
			
		||||
        return async (...args) => {
 | 
			
		||||
            try {
 | 
			
		||||
                await this.sendAudioContent(data, mimeType);
 | 
			
		||||
                return { success: true };
 | 
			
		||||
                const result = await asyncFn.apply(this, args);
 | 
			
		||||
                if (successMessage) console.log(successMessage);
 | 
			
		||||
                // `startMacOSAudioCapture`는 성공 시 { success, error } 객체를 반환하지 않으므로,
 | 
			
		||||
                // 핸들러가 일관된 응답을 보내도록 여기서 success 객체를 반환합니다.
 | 
			
		||||
                // 다른 함수들은 이미 success 객체를 반환합니다.
 | 
			
		||||
                return result && typeof result.success !== 'undefined' ? result : { success: true };
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                console.error('Error sending user audio:', e);
 | 
			
		||||
                console.error(errorMessage, e);
 | 
			
		||||
                return { success: false, error: e.message };
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        ipcMain.handle('send-system-audio-content', async (event, { data, mimeType }) => {
 | 
			
		||||
            try {
 | 
			
		||||
                await this.sttService.sendSystemAudioContent(data, mimeType);
 | 
			
		||||
                
 | 
			
		||||
                // Send system audio data back to renderer for AEC reference (like macOS does)
 | 
			
		||||
                this.sendToRenderer('system-audio-data', { data });
 | 
			
		||||
                
 | 
			
		||||
                return { success: true };
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                console.error('Error sending system audio:', error);
 | 
			
		||||
                return { success: false, error: error.message };
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    // `_createHandler`를 사용하여 핸들러들을 동적으로 생성합니다.
 | 
			
		||||
    handleSendAudioContent = this._createHandler(
 | 
			
		||||
        this.sendAudioContent,
 | 
			
		||||
        null,
 | 
			
		||||
        'Error sending user audio:'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
        ipcMain.handle('start-macos-audio', async () => {
 | 
			
		||||
    handleStartMacosAudio = this._createHandler(
 | 
			
		||||
        async () => {
 | 
			
		||||
            if (process.platform !== 'darwin') {
 | 
			
		||||
                return { success: false, error: 'macOS audio capture only available on macOS' };
 | 
			
		||||
            }
 | 
			
		||||
            if (this.sttService.isMacOSAudioRunning?.()) {
 | 
			
		||||
                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 {
 | 
			
		||||
                const success = await this.startMacOSAudioCapture();
 | 
			
		||||
                return { success, error: null };
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                console.error('Error starting macOS audio capture:', error);
 | 
			
		||||
                return { success: false, error: error.message };
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        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');
 | 
			
		||||
    }
 | 
			
		||||
    handleUpdateGoogleSearchSetting = this._createHandler(
 | 
			
		||||
        async (enabled) => {
 | 
			
		||||
            console.log('Google Search setting updated to:', enabled);
 | 
			
		||||
        },
 | 
			
		||||
        null,
 | 
			
		||||
        'Error updating Google Search setting:'
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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() {
 | 
			
		||||
        const finalText = (this.myCompletionBuffer + this.myCurrentUtterance).trim();
 | 
			
		||||
        if (!this.modelInfo || !finalText) return;
 | 
			
		||||
 | 
			
		||||
@ -197,8 +197,6 @@ app.whenReady().then(async () => {
 | 
			
		||||
        await modelStateService.initialize();
 | 
			
		||||
        //////// after_modelStateService ////////
 | 
			
		||||
 | 
			
		||||
        listenService.initialize();
 | 
			
		||||
        askService.initialize();
 | 
			
		||||
        featureBridge.initialize();  // 추가: featureBridge 초기화
 | 
			
		||||
        setupWebDataHandlers();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1541,7 +1541,7 @@ export class ApiKeyHeader extends LitElement {
 | 
			
		||||
    this.classList.remove("sliding-out");
 | 
			
		||||
    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) {
 | 
			
		||||
      console.error('[ApiKeyHeader] handleAnimationEnd: window.require not available');
 | 
			
		||||
@ -1585,7 +1585,8 @@ export class ApiKeyHeader extends LitElement {
 | 
			
		||||
 | 
			
		||||
  connectedCallback() {
 | 
			
		||||
    super.connectedCallback()
 | 
			
		||||
    this.addEventListener("animationend", this.handleAnimationEnd)
 | 
			
		||||
    // this.addEventListener("animationend", this.handleAnimationEnd)
 | 
			
		||||
    this.addEventListener("transitionend", this.handleAnimationEnd)
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  handleMessageFadeEnd(e) {
 | 
			
		||||
@ -1603,8 +1604,8 @@ export class ApiKeyHeader extends LitElement {
 | 
			
		||||
 | 
			
		||||
  disconnectedCallback() {
 | 
			
		||||
    super.disconnectedCallback()
 | 
			
		||||
    this.removeEventListener("animationend", this.handleAnimationEnd)
 | 
			
		||||
    
 | 
			
		||||
    // this.removeEventListener("animationend", this.handleAnimationEnd)
 | 
			
		||||
    this.removeEventListener("transitionend", this.handleAnimationEnd)
 | 
			
		||||
    // Professional cleanup of all resources
 | 
			
		||||
    this._performCompleteCleanup();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -96,8 +96,12 @@ class HeaderTransitionManager {
 | 
			
		||||
 | 
			
		||||
    //////// after_modelStateService ////////
 | 
			
		||||
    async handleStateUpdate(userState) {
 | 
			
		||||
        console.log('[HeaderController DEBUG] handleStateUpdate called with userState:', userState);
 | 
			
		||||
        const { ipcRenderer } = window.require('electron');
 | 
			
		||||
        
 | 
			
		||||
        console.log('[HeaderController DEBUG] Invoking "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) {
 | 
			
		||||
            const { isLoggedIn } = userState;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user