const { BrowserWindow, ipcMain } = require('electron'); const { getSystemPrompt } = require('./prompts'); const { getAvailableModel, incrementLimitCount, getApiKey, getOpenAICredentials, getOpenAISDKCredentials, getPreferences } = require('../storage'); // Import provider implementations const geminiProvider = require('./gemini'); const openaiRealtimeProvider = require('./openai-realtime'); const openaiSdkProvider = require('./openai-sdk'); // Conversation tracking (shared across providers) let currentSessionId = null; let conversationHistory = []; let screenAnalysisHistory = []; let currentProfile = null; let currentCustomPrompt = null; let currentProvider = 'gemini'; // 'gemini', 'openai-realtime', or 'openai-sdk' let providerConfig = {}; function sendToRenderer(channel, data) { const windows = BrowserWindow.getAllWindows(); if (windows.length > 0) { windows[0].webContents.send(channel, data); } } function initializeNewSession(profile = null, customPrompt = null) { currentSessionId = Date.now().toString(); conversationHistory = []; screenAnalysisHistory = []; currentProfile = profile; currentCustomPrompt = customPrompt; console.log('New conversation session started:', currentSessionId, 'profile:', profile, 'provider:', currentProvider); if (profile) { sendToRenderer('save-session-context', { sessionId: currentSessionId, profile: profile, customPrompt: customPrompt || '', provider: currentProvider, }); } } function saveConversationTurn(transcription, aiResponse) { if (!currentSessionId) { initializeNewSession(); } const conversationTurn = { timestamp: Date.now(), transcription: transcription.trim(), ai_response: aiResponse.trim(), }; conversationHistory.push(conversationTurn); console.log('Saved conversation turn:', conversationTurn); sendToRenderer('save-conversation-turn', { sessionId: currentSessionId, turn: conversationTurn, fullHistory: conversationHistory, }); } function saveScreenAnalysis(prompt, response, model) { if (!currentSessionId) { initializeNewSession(); } const analysisEntry = { timestamp: Date.now(), prompt: prompt, response: response.trim(), model: model, provider: currentProvider, }; screenAnalysisHistory.push(analysisEntry); console.log('Saved screen analysis:', analysisEntry); sendToRenderer('save-screen-analysis', { sessionId: currentSessionId, analysis: analysisEntry, fullHistory: screenAnalysisHistory, profile: currentProfile, customPrompt: currentCustomPrompt, }); } function getCurrentSessionData() { return { sessionId: currentSessionId, history: conversationHistory, provider: currentProvider, }; } // Get provider configuration from storage async function getStoredSetting(key, defaultValue) { try { const windows = BrowserWindow.getAllWindows(); if (windows.length > 0) { await new Promise(resolve => setTimeout(resolve, 100)); const value = await windows[0].webContents.executeJavaScript(` (function() { try { if (typeof localStorage === 'undefined') { return '${defaultValue}'; } const stored = localStorage.getItem('${key}'); return stored || '${defaultValue}'; } catch (e) { return '${defaultValue}'; } })() `); return value; } } catch (error) { console.error('Error getting stored setting for', key, ':', error.message); } return defaultValue; } // Initialize AI session based on selected provider async function initializeAISession(customPrompt = '', profile = 'interview', language = 'en-US') { // Read provider from file-based storage (preferences.json) const prefs = getPreferences(); const provider = prefs.aiProvider || 'gemini'; currentProvider = provider; console.log('Initializing AI session with provider:', provider); // Check if Google Search is enabled for system prompt const googleSearchEnabled = prefs.googleSearchEnabled ?? true; const systemPrompt = getSystemPrompt(profile, customPrompt, googleSearchEnabled); if (provider === 'openai-realtime') { // Get OpenAI Realtime configuration const creds = getOpenAICredentials(); if (!creds.apiKey) { sendToRenderer('update-status', 'OpenAI API key not configured'); return false; } providerConfig = { apiKey: creds.apiKey, baseUrl: creds.baseUrl || null, model: creds.model, systemPrompt, language, isReconnect: false, }; initializeNewSession(profile, customPrompt); try { await openaiRealtimeProvider.initializeOpenAISession(providerConfig, conversationHistory); return true; } catch (error) { console.error('Failed to initialize OpenAI Realtime session:', error); sendToRenderer('update-status', 'Failed to connect to OpenAI Realtime'); return false; } } else if (provider === 'openai-sdk') { // Get OpenAI SDK configuration (for BotHub, etc.) const creds = getOpenAISDKCredentials(); if (!creds.apiKey) { sendToRenderer('update-status', 'OpenAI SDK API key not configured'); return false; } providerConfig = { apiKey: creds.apiKey, baseUrl: creds.baseUrl || null, model: creds.model, visionModel: creds.visionModel, whisperModel: creds.whisperModel, }; initializeNewSession(profile, customPrompt); try { await openaiSdkProvider.initializeOpenAISDK(providerConfig); openaiSdkProvider.setSystemPrompt(systemPrompt); openaiSdkProvider.updatePushToTalkSettings(prefs.audioInputMode || 'auto'); sendToRenderer('update-status', 'Ready (OpenAI SDK)'); return true; } catch (error) { console.error('Failed to initialize OpenAI SDK:', error); sendToRenderer('update-status', 'Failed to initialize OpenAI SDK: ' + error.message); return false; } } else { // Use Gemini (default) const apiKey = getApiKey(); if (!apiKey) { sendToRenderer('update-status', 'Gemini API key not configured'); return false; } const session = await geminiProvider.initializeGeminiSession(apiKey, customPrompt, profile, language); if (session && global.geminiSessionRef) { global.geminiSessionRef.current = session; return true; } return false; } } // Send audio to appropriate provider async function sendAudioContent(data, mimeType, isSystemAudio = true) { if (currentProvider === 'openai-realtime') { return await openaiRealtimeProvider.sendAudioToOpenAI(data); } else if (currentProvider === 'openai-sdk') { // OpenAI SDK buffers audio and transcribes on flush return await openaiSdkProvider.processAudioChunk(data, mimeType); } else { // Gemini if (!global.geminiSessionRef?.current) { return { success: false, error: 'No active Gemini session' }; } try { const marker = isSystemAudio ? '.' : ','; process.stdout.write(marker); await global.geminiSessionRef.current.sendRealtimeInput({ audio: { data, mimeType }, }); return { success: true }; } catch (error) { console.error('Error sending audio to Gemini:', error); return { success: false, error: error.message }; } } } // Send image to appropriate provider async function sendImageContent(data, prompt) { if (currentProvider === 'openai-realtime') { const creds = getOpenAICredentials(); const result = await openaiRealtimeProvider.sendImageToOpenAI(data, prompt, { apiKey: creds.apiKey, baseUrl: creds.baseUrl, model: creds.model, }); if (result.success) { saveScreenAnalysis(prompt, result.text, result.model); } return result; } else if (currentProvider === 'openai-sdk') { const result = await openaiSdkProvider.sendImageMessage(data, prompt); if (result.success) { saveScreenAnalysis(prompt, result.text, result.model); } return result; } else { // Use Gemini HTTP API const result = await geminiProvider.sendImageToGeminiHttp(data, prompt); // Screen analysis is saved inside sendImageToGeminiHttp for Gemini return result; } } // Send text message to appropriate provider async function sendTextMessage(text) { if (currentProvider === 'openai-realtime') { return await openaiRealtimeProvider.sendTextToOpenAI(text); } else if (currentProvider === 'openai-sdk') { const result = await openaiSdkProvider.sendTextMessage(text); if (result.success && result.text) { saveConversationTurn(text, result.text); } return result; } else { // Gemini if (!global.geminiSessionRef?.current) { return { success: false, error: 'No active Gemini session' }; } try { console.log('Sending text message to Gemini:', text); await global.geminiSessionRef.current.sendRealtimeInput({ text: text.trim() }); return { success: true }; } catch (error) { console.error('Error sending text to Gemini:', error); return { success: false, error: error.message }; } } } // Close session for appropriate provider async function closeSession() { try { if (currentProvider === 'openai-realtime') { openaiRealtimeProvider.closeOpenAISession(); } else if (currentProvider === 'openai-sdk') { openaiSdkProvider.closeOpenAISDK(); } else { geminiProvider.stopMacOSAudioCapture(); if (global.geminiSessionRef?.current) { await global.geminiSessionRef.current.close(); global.geminiSessionRef.current = null; } } return { success: true }; } catch (error) { console.error('Error closing session:', error); return { success: false, error: error.message }; } } // Setup IPC handlers function setupAIProviderIpcHandlers(geminiSessionRef) { // Store reference for Gemini global.geminiSessionRef = geminiSessionRef; // Listen for conversation turn save requests from providers ipcMain.on('save-conversation-turn-data', (event, { transcription, response }) => { saveConversationTurn(transcription, response); }); ipcMain.on('push-to-talk-toggle', () => { if (currentProvider === 'openai-sdk') { openaiSdkProvider.togglePushToTalk(); } }); ipcMain.on('update-push-to-talk-settings', (event, { inputMode } = {}) => { openaiSdkProvider.updatePushToTalkSettings(inputMode || 'auto'); }); ipcMain.handle('initialize-ai-session', async (event, customPrompt, profile, language) => { return await initializeAISession(customPrompt, profile, language); }); ipcMain.handle('send-audio-content', async (event, { data, mimeType }) => { return await sendAudioContent(data, mimeType, true); }); ipcMain.handle('send-mic-audio-content', async (event, { data, mimeType }) => { return await sendAudioContent(data, mimeType, false); }); ipcMain.handle('send-image-content', async (event, { data, prompt }) => { return await sendImageContent(data, prompt); }); ipcMain.handle('send-text-message', async (event, text) => { return await sendTextMessage(text); }); ipcMain.handle('close-session', async event => { return await closeSession(); }); // macOS system audio ipcMain.handle('start-macos-audio', async event => { if (process.platform !== 'darwin') { return { success: false, error: 'macOS audio capture only available on macOS', }; } try { if (currentProvider === 'gemini') { const success = await geminiProvider.startMacOSAudioCapture(global.geminiSessionRef); return { success }; } else if (currentProvider === 'openai-sdk') { const success = await openaiSdkProvider.startMacOSAudioCapture(); return { success }; } else if (currentProvider === 'openai-realtime') { // OpenAI Realtime uses WebSocket, handle differently if needed return { success: false, error: 'OpenAI Realtime uses WebSocket for audio', }; } return { success: false, error: 'Unknown provider: ' + currentProvider, }; } catch (error) { console.error('Error starting macOS audio capture:', error); return { success: false, error: error.message }; } }); ipcMain.handle('stop-macos-audio', async event => { try { if (currentProvider === 'gemini') { geminiProvider.stopMacOSAudioCapture(); } else if (currentProvider === 'openai-sdk') { openaiSdkProvider.stopMacOSAudioCapture(); } return { success: true }; } catch (error) { console.error('Error stopping macOS audio capture:', error); return { success: false, error: error.message }; } }); // Session management ipcMain.handle('get-current-session', async event => { try { return { success: true, data: getCurrentSessionData() }; } catch (error) { console.error('Error getting current session:', error); return { success: false, error: error.message }; } }); ipcMain.handle('start-new-session', async event => { try { initializeNewSession(); return { success: true, sessionId: currentSessionId }; } catch (error) { console.error('Error starting new session:', 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 }; } }); // Provider switching ipcMain.handle('switch-ai-provider', async (event, provider) => { try { console.log('Switching AI provider to:', provider); currentProvider = provider; return { success: true }; } catch (error) { console.error('Error switching provider:', error); return { success: false, error: error.message }; } }); } module.exports = { setupAIProviderIpcHandlers, initializeAISession, sendAudioContent, sendImageContent, sendTextMessage, closeSession, getCurrentSessionData, initializeNewSession, saveConversationTurn, };