document.addEventListener('DOMContentLoaded', function() { const toggleButton = document.getElementById('toggleListening'); const transcriptDiv = document.getElementById('transcript'); const aiResponseDiv = document.getElementById('aiResponse'); const apiKeyInput = document.getElementById('apiKeyInput'); const saveApiKeyButton = document.getElementById('saveApiKey'); const aiProviderSelect = document.getElementById('aiProvider'); const modelSelect = document.getElementById('modelSelect'); const apiKeyStatus = document.getElementById('apiKeyStatus'); // Context management elements const contextFileInput = document.getElementById('contextFileInput'); const uploadContextBtn = document.getElementById('uploadContextBtn'); const contextTextInput = document.getElementById('contextTextInput'); const contextTypeSelect = document.getElementById('contextTypeSelect'); const contextTitleInput = document.getElementById('contextTitleInput'); const addContextBtn = document.getElementById('addContextBtn'); const contextList = document.getElementById('contextList'); const clearAllContextBtn = document.getElementById('clearAllContextBtn'); // Multi-device elements const enableRemoteListening = document.getElementById('enableRemoteListening'); const remoteStatus = document.getElementById('remoteStatus'); const deviceInfo = document.getElementById('deviceInfo'); const accessUrl = document.getElementById('accessUrl'); const copyUrlBtn = document.getElementById('copyUrlBtn'); const qrCode = document.getElementById('qrCode'); let isListening = false; let remoteServerActive = false; // AI Provider configurations const aiProviders = { openai: { name: 'OpenAI', models: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-3.5-turbo'], defaultModel: 'gpt-4o-mini', apiKeyPlaceholder: 'Enter your OpenAI API Key', requiresKey: true }, anthropic: { name: 'Anthropic', models: ['claude-3-5-sonnet-20241022', 'claude-3-5-haiku-20241022', 'claude-3-opus-20240229'], defaultModel: 'claude-3-5-sonnet-20241022', apiKeyPlaceholder: 'Enter your Anthropic API Key', requiresKey: true }, google: { name: 'Google', models: ['gemini-1.5-pro', 'gemini-1.5-flash', 'gemini-pro'], defaultModel: 'gemini-1.5-flash', apiKeyPlaceholder: 'Enter your Google AI API Key', requiresKey: true }, ollama: { name: 'Ollama', models: ['llama3.2', 'llama3.1', 'mistral', 'codellama', 'phi3'], defaultModel: 'llama3.2', apiKeyPlaceholder: 'No API key required (Local)', requiresKey: false } }; // Load saved settings chrome.storage.sync.get(['aiProvider', 'selectedModel', 'apiKeys'], (result) => { const savedProvider = result.aiProvider || 'openai'; const savedModel = result.selectedModel || aiProviders[savedProvider].defaultModel; const savedApiKeys = result.apiKeys || {}; aiProviderSelect.value = savedProvider; updateModelOptions(savedProvider, savedModel); updateApiKeyInput(savedProvider); if (savedApiKeys[savedProvider] && aiProviders[savedProvider].requiresKey) { apiKeyInput.value = savedApiKeys[savedProvider]; updateApiKeyStatus('API Key Saved', 'success'); saveApiKeyButton.textContent = 'API Key Saved'; saveApiKeyButton.disabled = true; } }); // Load and display saved contexts loadContexts(); // Helper functions function updateModelOptions(provider, selectedModel = null) { const models = aiProviders[provider].models; modelSelect.innerHTML = ''; models.forEach(model => { const option = document.createElement('option'); option.value = model; option.textContent = model; if (selectedModel === model || (!selectedModel && model === aiProviders[provider].defaultModel)) { option.selected = true; } modelSelect.appendChild(option); }); } function updateApiKeyInput(provider) { const providerConfig = aiProviders[provider]; apiKeyInput.placeholder = providerConfig.apiKeyPlaceholder; apiKeyInput.disabled = !providerConfig.requiresKey; saveApiKeyButton.disabled = !providerConfig.requiresKey; if (!providerConfig.requiresKey) { apiKeyInput.value = ''; updateApiKeyStatus('No API key required', 'success'); } else { updateApiKeyStatus('', ''); } } function updateApiKeyStatus(message, type) { apiKeyStatus.textContent = message; apiKeyStatus.className = `status-message ${type}`; } // Context Management Functions async function loadContexts() { const result = await chrome.storage.local.get('contexts'); const contexts = result.contexts || []; displayContexts(contexts); updateManageTabCount(contexts.length); } function displayContexts(contexts) { contextList.innerHTML = ''; if (contexts.length === 0) { contextList.innerHTML = '
No context added yet. Add your CV or job description to get better responses!
'; return; } contexts.forEach((context, index) => { const contextItem = document.createElement('div'); contextItem.className = 'context-item'; contextItem.innerHTML = `
${context.title} ${context.type ? `• ${context.type}` : ''}
${context.content.substring(0, 100)}${context.content.length > 100 ? '...' : ''}
`; contextList.appendChild(contextItem); }); } function updateManageTabCount(count) { const manageTab = document.querySelector('[data-tab="manage"]'); manageTab.textContent = `Manage (${count})`; } async function saveContext(title, content) { if (!title.trim() || !content.trim()) { alert('Please provide both title and content'); return; } // Optional basic guard for extremely large items (>4MB) const approxBytes = new Blob([content]).size; if (approxBytes > 4 * 1024 * 1024) { alert('This context is too large to store locally. Please split it into smaller parts.'); return; } const result = await chrome.storage.local.get('contexts'); const contexts = result.contexts || []; contexts.push({ id: Date.now(), title: title.trim(), content: content.trim(), type: (contextTypeSelect && contextTypeSelect.value) || 'general', createdAt: new Date().toISOString() }); await chrome.storage.local.set({ contexts: contexts }); loadContexts(); // Clear inputs contextTitleInput.value = ''; contextTextInput.value = ''; if (contextTypeSelect) contextTypeSelect.value = 'general'; // Switch to manage tab switchTab('manage'); } async function deleteContext(index) { if (!confirm('Are you sure you want to delete this context?')) return; const result = await chrome.storage.local.get('contexts'); const contexts = result.contexts || []; contexts.splice(index, 1); await chrome.storage.local.set({ contexts: contexts }); loadContexts(); } async function clearAllContexts() { if (!confirm('Are you sure you want to delete all contexts? This cannot be undone.')) return; await chrome.storage.local.set({ contexts: [] }); loadContexts(); } function switchTab(tabName) { // Update tab buttons document.querySelectorAll('.tab-button').forEach(btn => { btn.classList.remove('active'); }); document.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); // Update tab content document.querySelectorAll('.tab-content').forEach(content => { content.classList.remove('active'); }); document.getElementById(`${tabName}Tab`).classList.add('active'); } async function processFile(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = function(e) { const content = e.target.result; resolve({ title: file.name, content: content }); }; reader.onerror = function() { reject(new Error('Failed to read file')); }; if (file.type === 'text/plain') { reader.readAsText(file); } else { // For other file types, we'll need to extract text // This is a simplified version - in production, you'd want proper PDF/DOC parsing reader.readAsText(file); } }); } // Multi-device functions async function enableRemoteAccess() { try { remoteStatus.textContent = 'Starting server...'; remoteStatus.className = 'status-message'; // Generate a unique session ID const sessionId = Math.random().toString(36).substring(2, 15); const port = 8765; const accessURL = `http://localhost:${port}?session=${sessionId}`; // Start WebSocket server (we'll implement this) chrome.runtime.sendMessage({ action: 'startRemoteServer', sessionId: sessionId, port: port }, (response) => { if (response.success) { remoteServerActive = true; accessUrl.textContent = accessURL; deviceInfo.style.display = 'block'; remoteStatus.textContent = 'Remote access enabled!'; remoteStatus.className = 'status-message success'; enableRemoteListening.textContent = '🛑 Disable Remote Access'; // Generate QR code (simplified) generateQRCode(accessURL); } else { remoteStatus.textContent = 'Failed to start server: ' + response.error; remoteStatus.className = 'status-message error'; } }); } catch (error) { remoteStatus.textContent = 'Error: ' + error.message; remoteStatus.className = 'status-message error'; } } function disableRemoteAccess() { chrome.runtime.sendMessage({ action: 'stopRemoteServer' }, (response) => { remoteServerActive = false; deviceInfo.style.display = 'none'; remoteStatus.textContent = ''; enableRemoteListening.textContent = '🌐 Enable Remote Access'; }); } function generateQRCode(url) { // Simple QR code placeholder - in production, use a QR code library qrCode.innerHTML = `
QR Code
Scan to access
`; } // Make functions available globally for onclick handlers window.editContext = function(index) { chrome.storage.local.get('contexts', (result) => { const contexts = result.contexts || []; const context = contexts[index]; if (context) { contextTitleInput.value = context.title; contextTextInput.value = context.content; if (contextTypeSelect) contextTypeSelect.value = context.type || 'general'; switchTab('text'); // Remove the old context when editing deleteContext(index); } }); }; window.deleteContext = deleteContext; // Event listeners aiProviderSelect.addEventListener('change', function() { const selectedProvider = this.value; updateModelOptions(selectedProvider); updateApiKeyInput(selectedProvider); // Save provider selection chrome.storage.sync.set({ aiProvider: selectedProvider, selectedModel: aiProviders[selectedProvider].defaultModel }); // Load saved API key for this provider chrome.storage.sync.get('apiKeys', (result) => { const apiKeys = result.apiKeys || {}; if (apiKeys[selectedProvider] && aiProviders[selectedProvider].requiresKey) { apiKeyInput.value = apiKeys[selectedProvider]; updateApiKeyStatus('API Key Saved', 'success'); saveApiKeyButton.textContent = 'API Key Saved'; saveApiKeyButton.disabled = true; } else { apiKeyInput.value = ''; saveApiKeyButton.textContent = 'Save API Key'; saveApiKeyButton.disabled = !aiProviders[selectedProvider].requiresKey; } }); }); modelSelect.addEventListener('change', function() { chrome.storage.sync.set({ selectedModel: this.value }); }); apiKeyInput.addEventListener('input', function() { if (aiProviders[aiProviderSelect.value].requiresKey) { saveApiKeyButton.textContent = 'Save API Key'; saveApiKeyButton.disabled = false; updateApiKeyStatus('', ''); } }); saveApiKeyButton.addEventListener('click', function() { const apiKey = apiKeyInput.value.trim(); const provider = aiProviderSelect.value; if (!aiProviders[provider].requiresKey) { return; } if (apiKey) { // Save API key for the current provider chrome.storage.sync.get('apiKeys', (result) => { const apiKeys = result.apiKeys || {}; apiKeys[provider] = apiKey; chrome.storage.sync.set({ apiKeys: apiKeys }, () => { saveApiKeyButton.textContent = 'API Key Saved'; saveApiKeyButton.disabled = true; updateApiKeyStatus('API Key Saved', 'success'); }); }); } else { updateApiKeyStatus('Please enter a valid API key', 'error'); } }); // Context management event listeners document.querySelectorAll('.tab-button').forEach(button => { button.addEventListener('click', function() { const tabName = this.getAttribute('data-tab'); switchTab(tabName); }); }); uploadContextBtn.addEventListener('click', function() { contextFileInput.click(); }); contextFileInput.addEventListener('change', async function() { const files = Array.from(this.files); for (const file of files) { try { const result = await processFile(file); await saveContext(result.title, result.content); } catch (error) { alert('Error processing file: ' + error.message); } } this.value = ''; // Clear file input }); addContextBtn.addEventListener('click', function() { const title = contextTitleInput.value.trim(); const content = contextTextInput.value.trim(); saveContext(title, content); }); clearAllContextBtn.addEventListener('click', clearAllContexts); // Multi-device event listeners enableRemoteListening.addEventListener('click', function() { if (remoteServerActive) { disableRemoteAccess(); } else { enableRemoteAccess(); } }); copyUrlBtn.addEventListener('click', function() { navigator.clipboard.writeText(accessUrl.textContent).then(() => { const originalText = copyUrlBtn.textContent; copyUrlBtn.textContent = '✅ Copied!'; setTimeout(() => { copyUrlBtn.textContent = originalText; }, 2000); }); }); toggleButton.addEventListener('click', function() { isListening = !isListening; toggleButton.textContent = isListening ? 'Stop Listening' : 'Start Listening'; if (isListening) { // Send current AI configuration with start listening const currentProvider = aiProviderSelect.value; const currentModel = modelSelect.value; chrome.runtime.sendMessage({ action: 'startListening', aiProvider: currentProvider, model: currentModel }); transcriptDiv.textContent = 'Listening for questions...'; aiResponseDiv.textContent = `Using ${aiProviders[currentProvider].name} (${currentModel}). The answer will appear here.`; } else { chrome.runtime.sendMessage({action: 'stopListening'}); transcriptDiv.textContent = ''; aiResponseDiv.textContent = ''; } }); chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { if (request.action === 'updateTranscript') { transcriptDiv.textContent = request.transcript; } else if (request.action === 'updateAIResponse') { aiResponseDiv.textContent = request.response; } }); });