UNPKG

@codejoy/random-learner

Version:

A comprehensive interview preparation and learning companion with AI-powered questions, mock interviews, skill assessments, and company-specific question sets for technical job interviews

1,178 lines (1,026 loc) 45.2 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Random Learner Settings</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #333; min-height: 100vh; padding: 20px; overflow-y: auto; } .container { background: white; border-radius: 15px; padding: 30px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); max-width: min(1200px, 95vw); width: 95%; margin: 0 auto; animation: slideIn 0.3s ease-out; } /* Responsive design for larger screens */ @media (min-width: 768px) { .container { width: 90%; max-width: 1400px; } } @media (min-width: 1200px) { .container { width: 85%; max-width: 1600px; } } @media (min-width: 1600px) { .container { width: 80%; max-width: 1800px; } /* Two-column layout for very wide screens */ .settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; } /* Make certain sections span full width */ .settings-section.full-width { grid-column: 1 / -1; } } @keyframes slideIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } .header { text-align: center; margin-bottom: 30px; border-bottom: 2px solid #f0f0f0; padding-bottom: 20px; } .header-logo { width: 64px; height: 64px; margin: 0 auto 15px auto; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); background: white; padding: 8px; display: flex; align-items: center; justify-content: center; } .header-logo img { width: 48px; height: 48px; border-radius: 8px; } .header h1 { color: #667eea; font-size: 2em; margin-bottom: 5px; display: flex; align-items: center; justify-content: center; gap: 10px; } .header p { color: #666; font-size: 1em; } .settings-section { margin-bottom: 30px; border: 1px solid #e0e0e0; border-radius: 10px; padding: 20px; background: #fafafa; } /* Responsive typography and spacing */ @media (min-width: 768px) { .settings-section { padding: 30px; margin-bottom: 35px; } .section-title { font-size: 1.3em; } .form-group label { font-size: 1.05em; } .btn { padding: 12px 20px; font-size: 1em; } input, select, textarea { font-size: 1em; padding: 10px 12px; } } @media (min-width: 1200px) { .container { padding: 40px; } .settings-section { padding: 35px; } .header h1 { font-size: 2.2em; } .section-title { font-size: 1.4em; } } .section-title { font-size: 1.3em; font-weight: bold; color: #667eea; margin-bottom: 15px; display: flex; align-items: center; gap: 10px; } .form-group { margin-bottom: 20px; } .form-group label { display: block; margin-bottom: 8px; font-weight: 500; color: #333; } .form-group input, .form-group select, .form-group textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 1em; transition: border-color 0.2s ease; } .form-group input:focus, .form-group select:focus, .form-group textarea:focus { outline: none; border-color: #667eea; } .form-group textarea { resize: vertical; min-height: 120px; font-family: 'Courier New', monospace; font-size: 0.9em; } .checkbox-group { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; margin-top: 10px; } .checkbox-item { display: flex; align-items: center; gap: 8px; padding: 8px; border-radius: 5px; background: white; border: 1px solid #e0e0e0; } .checkbox-item input[type="checkbox"] { width: auto; margin: 0; } .checkbox-item label { margin: 0; cursor: pointer; font-weight: normal; } .range-group { display: flex; gap: 15px; align-items: center; } .range-group input { width: 100px; } .btn { padding: 12px 25px; border: none; border-radius: 8px; cursor: pointer; font-size: 1em; font-weight: 500; transition: all 0.2s ease; text-transform: uppercase; letter-spacing: 0.5px; margin-right: 10px; margin-bottom: 10px; } .btn-primary { background: #667eea; color: white; } .btn-primary:hover { background: #5a67d8; transform: translateY(-2px); } .btn-secondary { background: #e2e8f0; color: #4a5568; } .btn-secondary:hover { background: #cbd5e0; } .btn-danger { background: #e53e3e; color: white; } .btn-danger:hover { background: #c53030; } .btn-success { background: #38a169; color: white; } .btn-success:hover { background: #2f855a; } .buttons { display: flex; gap: 10px; justify-content: center; margin-top: 30px; flex-wrap: wrap; } .status-message { padding: 12px; border-radius: 8px; margin-bottom: 20px; display: none; animation: fadeIn 0.3s ease-out; } .status-message.success { background: #c6f6d5; border: 1px solid #38a169; color: #22543d; } .status-message.error { background: #fed7d7; border: 1px solid #e53e3e; color: #742a2a; } /* Pause/Resume Styles */ .pause-resume-container { display: flex; align-items: center; gap: 20px; padding: 20px; background: #f8f9ff; border-radius: 8px; border: 2px solid #e0e7ff; } #pause-resume-btn { display: flex; align-items: center; gap: 8px; padding: 12px 20px; font-size: 1.1em; font-weight: 600; border: none; border-radius: 8px; cursor: pointer; transition: all 0.2s ease; min-width: 160px; justify-content: center; } #pause-resume-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); } #pause-resume-btn.paused { background: #10b981; color: white; } #pause-resume-btn.paused:hover { background: #059669; } .pause-status { flex: 1; } #pause-status-text { font-size: 1em; color: #4a5568; font-weight: 500; } #pause-status-text.paused { color: #d97706; font-weight: 600; } .status-message.info { background: #bee3f8; border: 1px solid #3182ce; color: #2a4365; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .help-text { font-size: 0.9em; color: #666; margin-top: 5px; font-style: italic; } .prompt-actions { display: flex; gap: 10px; margin-top: 10px; } .connection-status { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 0.8em; font-weight: bold; } .connection-status.connected { background: #c6f6d5; color: #22543d; } .connection-status.disconnected { background: #fed7d7; color: #742a2a; } .loading { opacity: 0.6; pointer-events: none; } .loading::after { content: ''; animation: dots 1.5s infinite; } @keyframes dots { 0%, 20% { content: '...'; } 40% { content: '....'; } 60%, 100% { content: '.....'; } } </style> </head> <body> <div class="container"> <div class="header"> <div class="header-logo"> <img src="icons/app-icon.png" alt="Random Learner Logo" id="header-logo-img"> </div> <h1>Random Learner Settings</h1> <p>Customize your learning experience</p> </div> <div id="status-message" class="status-message"></div> <div class="settings-grid"> <!-- Question Scheduling Control --> <div class="settings-section"> <div class="section-title">⏯️ Question Scheduling</div> <div class="form-group"> <div class="pause-resume-container"> <button id="pause-resume-btn" class="btn btn-primary"> <span id="pause-resume-icon">⏸️</span> <span id="pause-resume-text">Pause Questions</span> </button> <div id="pause-status" class="pause-status"> <span id="pause-status-text">Questions are active</span> </div> </div> </div> </div> <!-- AI Provider Settings --> <div class="settings-section full-width"> <div class="section-title"> 🤖 AI Integration <span id="connection-status" class="connection-status disconnected">Not Connected</span> </div> <div class="form-group"> <label for="ai-provider">AI Provider</label> <select id="ai-provider"> <option value="openai">OpenAI (GPT-3.5/GPT-4)</option> <option value="gemini">Google Gemini</option> </select> <div class="help-text"> Choose your preferred AI provider for question generation and answer validation. </div> </div> <div class="form-group" id="openai-settings"> <label for="openai-api-key">OpenAI API Key</label> <div style="position: relative;"> <input type="password" id="openai-api-key" placeholder="sk-..." autocomplete="off" spellcheck="false" style="padding-right: 50px;"> <button type="button" id="toggle-openai-key" style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; cursor: pointer; font-size: 1.2em;">👁️</button> </div> <div class="help-text"> Enter your OpenAI API key to enable AI-generated questions. <a href="https://platform.openai.com/api-keys" target="_blank">Get your API key here</a> <br><small>💡 Tip: You can paste your API key using Ctrl+V (Cmd+V on Mac) or click the eye icon to show/hide</small> </div> </div> <div class="form-group" id="gemini-settings" style="display: none;"> <label for="gemini-api-key">Gemini API Key</label> <div style="position: relative;"> <input type="password" id="gemini-api-key" placeholder="..." autocomplete="off" spellcheck="false" style="padding-right: 50px;"> <button type="button" id="toggle-gemini-key" style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; cursor: pointer; font-size: 1.2em;">👁️</button> </div> <div class="help-text"> Enter your Gemini API key to enable AI-generated questions. <a href="https://aistudio.google.com/app/apikey" target="_blank">Get your API key here</a> <br><small>💡 Tip: You can paste your API key using Ctrl+V (Cmd+V on Mac) or click the eye icon to show/hide</small> </div> </div> <div class="form-group"> <label for="custom-prompt">Custom Prompt for Question Generation</label> <textarea id="custom-prompt" placeholder="Enter your custom prompt here..."></textarea> <div class="help-text"> Customize how AI generates questions. Use {topic} and {level} as placeholders. </div> <div class="prompt-actions"> <button type="button" class="btn btn-secondary" id="show-default-prompt">Show Default Prompt</button> <button type="button" class="btn btn-secondary" id="reset-prompt">Reset to Default</button> </div> </div> </div> <!-- Question Settings --> <div class="settings-section"> <div class="section-title">📚 Question Preferences</div> <div class="form-group"> <label>Preferred Topics</label> <div class="checkbox-group" id="topics-group"> <div class="checkbox-item"> <input type="checkbox" id="topic-oops" value="oops"> <label for="topic-oops">Object-Oriented Programming</label> </div> <div class="checkbox-item"> <input type="checkbox" id="topic-java" value="java"> <label for="topic-java">Java</label> </div> <div class="checkbox-item"> <input type="checkbox" id="topic-python" value="python"> <label for="topic-python">Python</label> </div> <div class="checkbox-item"> <input type="checkbox" id="topic-ai" value="ai"> <label for="topic-ai">Artificial Intelligence</label> </div> <div class="checkbox-item"> <input type="checkbox" id="topic-databases" value="databases"> <label for="topic-databases">Databases</label> </div> </div> </div> <div class="form-group"> <label>Preferred Difficulty Levels</label> <div class="checkbox-group" id="levels-group"> <div class="checkbox-item"> <input type="checkbox" id="level-beginner" value="beginner"> <label for="level-beginner">Beginner</label> </div> <div class="checkbox-item"> <input type="checkbox" id="level-intermediate" value="intermediate"> <label for="level-intermediate">Intermediate</label> </div> <div class="checkbox-item"> <input type="checkbox" id="level-advanced" value="advanced"> <label for="level-advanced">Advanced</label> </div> </div> </div> <div class="form-group"> <label> <input type="checkbox" id="use-ai"> Use AI for question generation </label> <div class="help-text"> When enabled, the app will generate questions using the selected AI provider in addition to the built-in question bank. </div> </div> </div> <!-- Timing Settings --> <div class="settings-section"> <div class="section-title">⏰ Timing Settings</div> <div class="form-group"> <label>Question Popup Interval (minutes)</label> <div class="range-group"> <span>Min:</span> <input type="number" id="min-interval" min="1" max="60" value="2"> <span>Max:</span> <input type="number" id="max-interval" min="1" max="120" value="10"> </div> <div class="help-text"> Questions will appear randomly between the minimum and maximum intervals. </div> </div> </div> <!-- UI Settings --> <div class="settings-section"> <div class="section-title">🎨 Interface Settings</div> <div class="form-group"> <label> <input type="checkbox" id="always-on-top"> Question windows always on top </label> </div> <div class="form-group"> <label> <input type="checkbox" id="auto-close"> Auto-close windows after showing results </label> </div> <div class="form-group"> <label for="auto-close-delay">Auto-close delay (seconds)</label> <input type="number" id="auto-close-delay" min="1" max="30" value="5"> </div> </div> <!-- Question Cache Management --> <div class="settings-section full-width"> <div class="section-title">💾 Question Cache Management</div> <div class="form-group"> <div id="cache-stats" class="help-text"> Loading cache statistics... </div> <button type="button" class="btn btn-info" id="refresh-cache-stats" style="margin-top: 10px; padding: 5px 10px; font-size: 0.9em;">🔄 Refresh Statistics</button> </div> <div class="form-group"> <div class="help-text"> AI-generated questions are automatically saved to a local JSON file (<code>ai-questions-cache.json</code>) in your project directory. This file can be manually edited to add your own questions and serves as a backup when AI providers are unavailable. <br><br> <strong>File Location:</strong> <code>ai-questions-cache.json</code> (in the app directory) <br> <strong>Manual Editing:</strong> Follow the format shown in the file's instructions section. </div> </div> </div> </div> <!-- End settings-grid --> <div class="buttons"> <button type="button" class="btn btn-primary" id="save-settings">Save Settings</button> <button type="button" class="btn btn-secondary" id="test-ai">Test AI Connection</button> <button type="button" class="btn btn-success" id="generate-test-question">Generate Test Question</button> <button type="button" class="btn btn-secondary" id="export-settings">Export Settings</button> <button type="button" class="btn btn-secondary" id="import-settings">Import Settings</button> <button type="button" class="btn btn-info" id="export-cache">Export Question Cache</button> <button type="button" class="btn btn-warning" id="clear-cache">Clear Question Cache</button> <button type="button" class="btn btn-danger" id="reset-settings">Reset to Defaults</button> </div> <input type="file" id="import-file" style="display: none" accept=".json"> </div> <script> const { ipcRenderer } = require('electron'); // DOM elements const elements = { pauseResumeBtn: document.getElementById('pause-resume-btn'), pauseResumeIcon: document.getElementById('pause-resume-icon'), pauseResumeText: document.getElementById('pause-resume-text'), pauseStatusText: document.getElementById('pause-status-text'), aiProvider: document.getElementById('ai-provider'), openaiApiKey: document.getElementById('openai-api-key'), geminiApiKey: document.getElementById('gemini-api-key'), customPrompt: document.getElementById('custom-prompt'), connectionStatus: document.getElementById('connection-status'), statusMessage: document.getElementById('status-message'), useAI: document.getElementById('use-ai'), minInterval: document.getElementById('min-interval'), maxInterval: document.getElementById('max-interval'), alwaysOnTop: document.getElementById('always-on-top'), autoClose: document.getElementById('auto-close'), autoCloseDelay: document.getElementById('auto-close-delay'), openaiSettings: document.getElementById('openai-settings'), geminiSettings: document.getElementById('gemini-settings') }; // Update pause/resume button state async function updatePauseStatus() { try { const status = await ipcRenderer.invoke('get-pause-status'); updatePauseStatusUI(status.paused); } catch (error) { console.error('Failed to update pause status:', error); } } // Initialize settings page async function initializeSettings() { try { const settings = await ipcRenderer.invoke('get-settings'); loadSettingsToUI(settings); updateConnectionStatus(settings); await updatePauseStatus(); } catch (error) { showStatus('Failed to load settings', 'error'); } } function loadSettingsToUI(settings) { // AI Provider settings elements.aiProvider.value = settings.aiProvider || 'openai'; elements.openaiApiKey.value = settings.openaiApiKey || ''; elements.geminiApiKey.value = settings.geminiApiKey || ''; elements.customPrompt.value = settings.customPrompt || ''; // Show/hide provider-specific settings toggleProviderSettings(settings.aiProvider || 'openai'); // Question settings - auto-enable AI if keys are present const hasAIKey = (settings.aiProvider === 'gemini' && settings.geminiApiKey) || (settings.aiProvider === 'openai' && settings.openaiApiKey) || settings.openaiApiKey; // fallback for legacy elements.useAI.checked = settings.questionSettings.useAI || settings.questionSettings.useOpenAI || hasAIKey; // Load preferred topics settings.questionSettings.preferredTopics.forEach(topic => { const checkbox = document.getElementById(`topic-${topic}`); if (checkbox) checkbox.checked = true; }); // Load preferred levels settings.questionSettings.preferredLevels.forEach(level => { const checkbox = document.getElementById(`level-${level}`); if (checkbox) checkbox.checked = true; }); // Timing settings elements.minInterval.value = settings.timingSettings.minInterval / (60 * 1000); elements.maxInterval.value = settings.timingSettings.maxInterval / (60 * 1000); // UI settings elements.alwaysOnTop.checked = settings.uiSettings.alwaysOnTop; elements.autoClose.checked = settings.uiSettings.autoClose; elements.autoCloseDelay.value = settings.uiSettings.autoCloseDelay / 1000; } function toggleProviderSettings(provider) { if (provider === 'gemini') { elements.openaiSettings.style.display = 'none'; elements.geminiSettings.style.display = 'block'; } else { elements.openaiSettings.style.display = 'block'; elements.geminiSettings.style.display = 'none'; } } function updateConnectionStatus(settings) { const provider = settings.aiProvider || 'openai'; let apiKey; if (provider === 'gemini') { apiKey = settings.geminiApiKey; } else { apiKey = settings.openaiApiKey; } const isConnected = apiKey && apiKey.trim().length > 0; elements.connectionStatus.textContent = isConnected ? `Connected (${provider.toUpperCase()})` : 'Not Connected'; elements.connectionStatus.className = `connection-status ${isConnected ? 'connected' : 'disconnected'}`; } function showStatus(message, type = 'info') { elements.statusMessage.textContent = message; elements.statusMessage.className = `status-message ${type}`; elements.statusMessage.style.display = 'block'; setTimeout(() => { elements.statusMessage.style.display = 'none'; }, 5000); } function getSettingsFromUI() { // Get selected topics const preferredTopics = []; document.querySelectorAll('#topics-group input[type="checkbox"]:checked').forEach(cb => { preferredTopics.push(cb.value); }); // Get selected levels const preferredLevels = []; document.querySelectorAll('#levels-group input[type="checkbox"]:checked').forEach(cb => { preferredLevels.push(cb.value); }); return { aiProvider: elements.aiProvider.value, openaiApiKey: elements.openaiApiKey.value.trim(), geminiApiKey: elements.geminiApiKey.value.trim(), customPrompt: elements.customPrompt.value.trim(), questionSettings: { preferredTopics, preferredLevels, useAI: elements.useAI.checked, mixStaticAndAI: true }, timingSettings: { minInterval: parseInt(elements.minInterval.value) * 60 * 1000, maxInterval: parseInt(elements.maxInterval.value) * 60 * 1000 }, uiSettings: { alwaysOnTop: elements.alwaysOnTop.checked, autoClose: elements.autoClose.checked, autoCloseDelay: parseInt(elements.autoCloseDelay.value) * 1000 } }; } // Event listeners // Pause/Resume functionality elements.pauseResumeBtn.addEventListener('click', async () => { try { const currentStatus = await ipcRenderer.invoke('get-pause-status'); const isPaused = currentStatus.paused; if (isPaused) { const result = await ipcRenderer.invoke('resume-questions'); showStatus('Questions resumed', 'success'); updatePauseStatusUI(result.paused); } else { const result = await ipcRenderer.invoke('pause-questions'); showStatus('Questions paused', 'info'); updatePauseStatusUI(result.paused); } } catch (error) { console.error('Failed to toggle pause state:', error); showStatus('Failed to update question scheduling', 'error'); } }); document.getElementById('save-settings').addEventListener('click', async () => { try { const settings = getSettingsFromUI(); await ipcRenderer.invoke('save-settings', settings); updateConnectionStatus(settings); showStatus('Settings saved successfully!', 'success'); } catch (error) { showStatus('Failed to save settings', 'error'); } }); document.getElementById('test-ai').addEventListener('click', async () => { const btn = document.getElementById('test-ai'); const originalText = btn.textContent; btn.textContent = 'Testing'; btn.classList.add('loading'); try { const provider = elements.aiProvider.value; let apiKey; if (provider === 'gemini') { apiKey = elements.geminiApiKey.value.trim(); } else { apiKey = elements.openaiApiKey.value.trim(); } if (!apiKey) { showStatus('Please enter an API key first', 'error'); return; } const result = await ipcRenderer.invoke('test-ai-connection', { provider, apiKey }); if (result.success) { showStatus(`${provider.toUpperCase()} connection successful!`, 'success'); updateConnectionStatus({ aiProvider: provider, [provider + 'ApiKey']: apiKey }); } else { showStatus(`Connection failed: ${result.error}`, 'error'); } } catch (error) { showStatus('Connection test failed', 'error'); } finally { btn.textContent = originalText; btn.classList.remove('loading'); } }); document.getElementById('generate-test-question').addEventListener('click', async () => { const btn = document.getElementById('generate-test-question'); const originalText = btn.textContent; btn.textContent = 'Generating'; btn.classList.add('loading'); try { const result = await ipcRenderer.invoke('generate-test-question'); if (result.success) { showStatus(`Test question generated: "${result.question.question}"`, 'success'); } else { showStatus(`Failed to generate question: ${result.error}`, 'error'); } } catch (error) { showStatus('Failed to generate test question', 'error'); } finally { btn.textContent = originalText; btn.classList.remove('loading'); } }); document.getElementById('show-default-prompt').addEventListener('click', async () => { try { const defaultPrompt = await ipcRenderer.invoke('get-default-prompt'); elements.customPrompt.value = defaultPrompt; showStatus('Default prompt loaded into editor', 'info'); } catch (error) { showStatus('Failed to load default prompt', 'error'); } }); document.getElementById('reset-prompt').addEventListener('click', () => { elements.customPrompt.value = ''; showStatus('Prompt reset to default', 'info'); }); document.getElementById('export-settings').addEventListener('click', async () => { try { const settings = await ipcRenderer.invoke('export-settings'); const blob = new Blob([settings], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'random-learner-settings.json'; a.click(); URL.revokeObjectURL(url); showStatus('Settings exported successfully!', 'success'); } catch (error) { showStatus('Failed to export settings', 'error'); } }); document.getElementById('import-settings').addEventListener('click', () => { document.getElementById('import-file').click(); }); document.getElementById('import-file').addEventListener('change', async (event) => { const file = event.target.files[0]; if (!file) return; try { const text = await file.text(); const result = await ipcRenderer.invoke('import-settings', text); if (result.success) { showStatus('Settings imported successfully! Reloading...', 'success'); setTimeout(() => { initializeSettings(); }, 1000); } else { showStatus(`Failed to import settings: ${result.error}`, 'error'); } } catch (error) { showStatus('Failed to read settings file', 'error'); } // Reset file input event.target.value = ''; }); document.getElementById('reset-settings').addEventListener('click', async () => { if (confirm('Are you sure you want to reset all settings to defaults? This cannot be undone.')) { try { await ipcRenderer.invoke('reset-settings'); showStatus('Settings reset to defaults! Reloading...', 'success'); setTimeout(() => { initializeSettings(); }, 1000); } catch (error) { showStatus('Failed to reset settings', 'error'); } } }); // Provider change handler elements.aiProvider.addEventListener('change', (e) => { toggleProviderSettings(e.target.value); }); // Toggle API key visibility for OpenAI document.getElementById('toggle-openai-key').addEventListener('click', () => { const apiKeyInput = elements.openaiApiKey; const toggleBtn = document.getElementById('toggle-openai-key'); if (apiKeyInput.type === 'password') { apiKeyInput.type = 'text'; toggleBtn.textContent = '🙈'; toggleBtn.title = 'Hide API key'; } else { apiKeyInput.type = 'password'; toggleBtn.textContent = '👁️'; toggleBtn.title = 'Show API key'; } }); // Toggle API key visibility for Gemini document.getElementById('toggle-gemini-key').addEventListener('click', () => { const apiKeyInput = elements.geminiApiKey; const toggleBtn = document.getElementById('toggle-gemini-key'); if (apiKeyInput.type === 'password') { apiKeyInput.type = 'text'; toggleBtn.textContent = '🙈'; toggleBtn.title = 'Hide API key'; } else { apiKeyInput.type = 'password'; toggleBtn.textContent = '👁️'; toggleBtn.title = 'Show API key'; } }); // Ensure paste functionality works for OpenAI elements.openaiApiKey.addEventListener('paste', (event) => { setTimeout(() => { elements.openaiApiKey.value = elements.openaiApiKey.value.trim(); }, 10); }); // Ensure paste functionality works for Gemini elements.geminiApiKey.addEventListener('paste', (event) => { setTimeout(() => { elements.geminiApiKey.value = elements.geminiApiKey.value.trim(); }, 10); }); // Handle Ctrl+V / Cmd+V for paste - OpenAI elements.openaiApiKey.addEventListener('keydown', (event) => { if ((event.ctrlKey || event.metaKey) && event.key === 'v') { setTimeout(() => { elements.openaiApiKey.value = elements.openaiApiKey.value.trim(); }, 10); } }); // Handle Ctrl+V / Cmd+V for paste - Gemini elements.geminiApiKey.addEventListener('keydown', (event) => { if ((event.ctrlKey || event.metaKey) && event.key === 'v') { setTimeout(() => { elements.geminiApiKey.value = elements.geminiApiKey.value.trim(); }, 10); } }); // Cache management event listeners document.getElementById('export-cache').addEventListener('click', async () => { try { const cacheData = await ipcRenderer.invoke('export-cache'); const blob = new Blob([cacheData], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `ai-questions-cache-${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showStatus('Question cache exported successfully!', 'success'); } catch (error) { showStatus('Failed to export cache', 'error'); } }); document.getElementById('clear-cache').addEventListener('click', async () => { if (confirm('Are you sure you want to clear all cached questions? This action cannot be undone.')) { try { await ipcRenderer.invoke('clear-question-cache'); showStatus('Question cache cleared successfully!', 'success'); updateCacheStats(); } catch (error) { showStatus('Failed to clear cache', 'error'); } } }); document.getElementById('refresh-cache-stats').addEventListener('click', async () => { try { // Reload cache from file to get latest data await ipcRenderer.invoke('reload-cache'); updateCacheStats(); showStatus('Cache statistics refreshed!', 'success'); } catch (error) { showStatus('Failed to refresh cache statistics', 'error'); } }); // Function to update cache statistics async function updateCacheStats() { try { const stats = await ipcRenderer.invoke('get-cache-stats'); const cacheStatsEl = document.getElementById('cache-stats'); let statsHtml = `<strong>Cache Statistics:</strong><br>`; statsHtml += `📊 Total Questions: ${stats.total}<br>`; if (stats.total > 0) { statsHtml += `<br><strong>By Source:</strong><br>`; Object.entries(stats.bySource).forEach(([source, count]) => { const icon = source === 'openai' ? '🤖' : source === 'gemini' ? '💎' : '✏️'; statsHtml += `${icon} ${source.toUpperCase()}: ${count}<br>`; }); statsHtml += `<br><strong>By Topic:</strong><br>`; Object.entries(stats.byTopic).forEach(([topic, count]) => { statsHtml += `📚 ${topic}: ${count}<br>`; }); statsHtml += `<br><strong>By Level:</strong><br>`; Object.entries(stats.byLevel).forEach(([level, count]) => { const icon = level === 'beginner' ? '🌱' : level === 'intermediate' ? '🌿' : '🌳'; statsHtml += `${icon} ${level}: ${count}<br>`; }); } cacheStatsEl.innerHTML = statsHtml; } catch (error) { document.getElementById('cache-stats').innerHTML = 'Failed to load cache statistics'; } } // Listen for pause state changes from main process ipcRenderer.on('pause-state-changed', (event, data) => { updatePauseStatusUI(data.paused); }); // Helper function to update pause UI without IPC call function updatePauseStatusUI(isPaused) { if (isPaused) { elements.pauseResumeBtn.classList.add('paused'); elements.pauseResumeIcon.textContent = '▶️'; elements.pauseResumeText.textContent = 'Resume Questions'; elements.pauseStatusText.textContent = 'Questions are paused'; elements.pauseStatusText.classList.add('paused'); } else { elements.pauseResumeBtn.classList.remove('paused'); elements.pauseResumeIcon.textContent = '⏸️'; elements.pauseResumeText.textContent = 'Pause Questions'; elements.pauseStatusText.textContent = 'Questions are active'; elements.pauseStatusText.classList.remove('paused'); } } // Initialize on load initializeSettings(); updateCacheStats(); // Update cache stats when window gains focus (in case questions were added while settings was closed) window.addEventListener('focus', async () => { try { await ipcRenderer.invoke('reload-cache'); updateCacheStats(); } catch (error) { console.error('Failed to reload cache on focus:', error); updateCacheStats(); // Still try to update with current cache } }); // Also update cache stats periodically setInterval(updateCacheStats, 5000); // Update every 5 seconds </script> </body> </html>