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,082 lines (919 loc) 37.6 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Company Question Sets - Random Learner</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%); min-height: 100vh; padding: 20px; color: #333; } .container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 20px; box-shadow: 0 20px 40px rgba(0,0,0,0.1); overflow: hidden; } .header { background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 30px; text-align: center; } .header h1 { font-size: 2.5em; margin-bottom: 10px; } .header p { font-size: 1.1em; opacity: 0.9; } .content { padding: 30px; } /* Loading State */ .loading { text-align: center; padding: 60px; color: #666; } .loading::after { content: ''; width: 30px; height: 30px; border: 3px solid #667eea; border-top: 3px solid transparent; border-radius: 50%; animation: spin 1s linear infinite; margin: 20px auto; display: block; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Stats Overview */ .stats-overview { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 40px; } .stat-card { background: #f8f9fa; border-radius: 15px; padding: 25px; text-align: center; border-left: 5px solid #667eea; } .stat-value { font-size: 2.5em; font-weight: bold; color: #667eea; margin-bottom: 10px; } .stat-label { color: #666; font-size: 0.9em; } /* Company Grid */ .companies-section { margin-bottom: 40px; } .section-title { font-size: 1.8em; color: #333; margin-bottom: 25px; display: flex; align-items: center; } .section-title::before { content: ''; width: 4px; height: 30px; background: linear-gradient(135deg, #667eea, #764ba2); margin-right: 15px; border-radius: 2px; } .companies-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 25px; } .company-card { background: #f8f9fa; border-radius: 15px; padding: 25px; border-left: 5px solid #667eea; transition: all 0.3s ease; position: relative; } .company-card:hover { transform: translateY(-5px); box-shadow: 0 10px 30px rgba(0,0,0,0.1); } .company-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 15px; } .company-name { font-size: 1.4em; font-weight: bold; color: #333; } .company-status { padding: 4px 12px; border-radius: 12px; font-size: 0.8em; font-weight: bold; text-transform: uppercase; } .status-available { background: #d4edda; color: #155724; } .status-empty { background: #f8d7da; color: #721c24; } .status-downloading { background: #fff3cd; color: #856404; } .company-description { color: #666; margin-bottom: 20px; line-height: 1.5; } .company-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-bottom: 20px; } .company-stat { text-align: center; padding: 10px; background: white; border-radius: 8px; } .company-stat-value { font-size: 1.5em; font-weight: bold; color: #667eea; } .company-stat-label { font-size: 0.8em; color: #666; } .company-actions { display: flex; gap: 10px; flex-wrap: wrap; } .action-btn { padding: 10px 20px; border: none; border-radius: 8px; font-size: 0.9em; font-weight: 500; cursor: pointer; transition: all 0.3s ease; flex: 1; min-width: 120px; } .btn-primary { background: linear-gradient(135deg, #667eea, #764ba2); color: white; } .btn-secondary { background: #6c757d; color: white; } .btn-success { background: #28a745; color: white; } .btn-danger { background: #dc3545; color: white; } .action-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); } .action-btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; } /* Download Progress */ .download-progress { display: none; margin-top: 15px; padding: 15px; background: white; border-radius: 10px; border: 2px solid #667eea; } .progress-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } .progress-title { font-weight: bold; color: #333; } .progress-percentage { font-weight: bold; color: #667eea; } .progress-bar { height: 8px; background: #e9ecef; border-radius: 4px; overflow: hidden; margin-bottom: 10px; } .progress-fill { height: 100%; background: linear-gradient(90deg, #667eea, #764ba2); width: 0%; transition: width 0.3s ease; } .progress-message { font-size: 0.9em; color: #666; } /* Download History */ .download-history { margin-bottom: 40px; } .history-table { width: 100%; border-collapse: collapse; background: #f8f9fa; border-radius: 10px; overflow: hidden; } .history-table th, .history-table td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #e9ecef; } .history-table th { background: #667eea; color: white; font-weight: bold; } .history-table tr:hover { background: #e9ecef; } .status-success { color: #28a745; font-weight: bold; } .status-failed { color: #dc3545; font-weight: bold; } /* Global Actions */ .global-actions { text-align: center; padding: 30px; background: #f8f9fa; border-radius: 15px; margin-top: 30px; } .global-actions h3 { margin-bottom: 20px; color: #333; } .global-btn { background: linear-gradient(135deg, #667eea, #764ba2); color: white; border: none; padding: 15px 30px; border-radius: 25px; font-size: 1.1em; font-weight: bold; cursor: pointer; margin: 0 10px; transition: all 0.3s ease; } .global-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3); } .global-btn.danger { background: #dc3545; } .global-btn.secondary { background: #6c757d; } /* Responsive Design */ @media (max-width: 768px) { .companies-grid { grid-template-columns: 1fr; } .stats-overview { grid-template-columns: repeat(2, 1fr); } .company-stats { grid-template-columns: 1fr; } .company-actions { flex-direction: column; } .action-btn { min-width: auto; } } /* Modal Styles */ .modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); } .modal-content { background-color: white; margin: 5% auto; padding: 30px; border-radius: 15px; width: 90%; max-width: 600px; max-height: 80vh; overflow-y: auto; } .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 2px solid #f0f0f0; } .modal-title { font-size: 1.5em; color: #333; } .close-btn { background: none; border: none; font-size: 1.5em; cursor: pointer; color: #666; } .close-btn:hover { color: #333; } /* AI Configuration */ .ai-config { background: #f8f9fa; border-radius: 10px; padding: 20px; margin-bottom: 20px; } .ai-status { display: flex; align-items: center; margin-bottom: 15px; } .status-indicator { width: 12px; height: 12px; border-radius: 50%; margin-right: 10px; } .status-connected { background: #28a745; } .status-disconnected { background: #dc3545; } .ai-provider { font-weight: bold; color: #333; } </style> </head> <body> <div class="container"> <div class="header"> <h1>🏢 Company Question Sets</h1> <p>Download and manage AI-generated interview questions for specific companies</p> </div> <div class="content"> <!-- Loading State --> <div class="loading" id="loading"> Loading company question sets... </div> <!-- Main Content --> <div id="main-content" style="display: none;"> <!-- Stats Overview --> <div class="stats-overview" id="stats-overview"> <!-- Stats will be populated by JavaScript --> </div> <!-- AI Configuration Status --> <div class="ai-config" id="ai-config"> <div class="ai-status"> <div class="status-indicator" id="ai-status-indicator"></div> <span class="ai-provider" id="ai-provider">Checking AI connection...</span> </div> <p id="ai-description">AI is required to download company-specific question sets.</p> </div> <!-- Companies Section --> <div class="companies-section"> <h2 class="section-title">🏢 Available Companies</h2> <div class="companies-grid" id="companies-grid"> <!-- Company cards will be populated by JavaScript --> </div> </div> <!-- Download History --> <div class="download-history"> <h2 class="section-title">📈 Recent Downloads</h2> <table class="history-table" id="history-table"> <thead> <tr> <th>Company</th> <th>Questions</th> <th>Duration</th> <th>Status</th> <th>Date</th> </tr> </thead> <tbody id="history-tbody"> <!-- History will be populated by JavaScript --> </tbody> </table> </div> <!-- Global Actions --> <div class="global-actions"> <h3>🛠️ Management Actions</h3> <button class="global-btn" id="download-all-btn">📥 Download All Companies</button> <button class="global-btn secondary" id="export-all-btn">📤 Export All Questions</button> <button class="global-btn danger" id="clear-all-btn">🗑️ Clear All Questions</button> <button class="global-btn secondary" onclick="closeWindow()">✖️ Close</button> </div> </div> </div> </div> <!-- Company Details Modal --> <div id="company-modal" class="modal"> <div class="modal-content"> <div class="modal-header"> <h3 class="modal-title" id="modal-company-name">Company Details</h3> <button class="close-btn" onclick="closeModal()">&times;</button> </div> <div id="modal-content"> <!-- Modal content will be populated by JavaScript --> </div> </div> </div> <script> const { ipcRenderer } = require('electron'); let companyData = {}; let downloadStats = {}; let aiStatus = { connected: false, provider: 'none' }; let activeDownloads = new Set(); // Initialize the interface async function initializeInterface() { try { console.log('Initializing company questions interface...'); // Load data await loadCompanyData(); await checkAIStatus(); // Display content displayStats(); displayCompanies(); displayDownloadHistory(); // Setup event listeners setupEventListeners(); // Show main content document.getElementById('loading').style.display = 'none'; document.getElementById('main-content').style.display = 'block'; } catch (error) { console.error('Failed to initialize interface:', error); showError('Failed to load company question sets'); } } // Load company data async function loadCompanyData() { try { companyData = await ipcRenderer.invoke('get-company-question-summary'); downloadStats = await ipcRenderer.invoke('get-company-download-stats'); console.log('Company data loaded:', companyData); } catch (error) { console.error('Failed to load company data:', error); throw error; } } // Check AI status async function checkAIStatus() { try { const settings = await ipcRenderer.invoke('get-settings'); const aiEnabled = settings.questionSettings?.useAI || settings.questionSettings?.useOpenAI; if (aiEnabled) { const provider = settings.aiProvider || 'openai'; const apiKey = provider === 'gemini' ? settings.geminiApiKey : settings.openaiApiKey; if (apiKey) { aiStatus = { connected: true, provider: provider }; } else { aiStatus = { connected: false, provider: provider, reason: 'No API key configured' }; } } else { aiStatus = { connected: false, provider: 'none', reason: 'AI not enabled' }; } updateAIStatus(); } catch (error) { console.error('Failed to check AI status:', error); aiStatus = { connected: false, provider: 'unknown', reason: 'Connection check failed' }; updateAIStatus(); } } // Update AI status display function updateAIStatus() { const indicator = document.getElementById('ai-status-indicator'); const provider = document.getElementById('ai-provider'); const description = document.getElementById('ai-description'); if (aiStatus.connected) { indicator.className = 'status-indicator status-connected'; provider.textContent = `${aiStatus.provider.toUpperCase()} Connected`; description.textContent = 'AI is ready to generate company-specific questions.'; } else { indicator.className = 'status-indicator status-disconnected'; provider.textContent = `AI Not Available`; description.textContent = `${aiStatus.reason || 'AI connection required'}. Please configure AI in settings.`; } } // Display stats function displayStats() { const container = document.getElementById('stats-overview'); container.innerHTML = ''; const totalCompanies = Object.keys(companyData).length; const availableCompanies = Object.values(companyData).filter(c => c.isAvailable).length; const totalQuestions = Object.values(companyData).reduce((sum, c) => sum + c.totalQuestions, 0); const totalDownloads = downloadStats.totalDownloads || 0; const stats = [ { label: 'Total Companies', value: totalCompanies }, { label: 'Available Sets', value: availableCompanies }, { label: 'Total Questions', value: totalQuestions }, { label: 'Downloads', value: totalDownloads } ]; stats.forEach(stat => { const card = document.createElement('div'); card.className = 'stat-card'; card.innerHTML = ` <div class="stat-value">${stat.value}</div> <div class="stat-label">${stat.label}</div> `; container.appendChild(card); }); } // Display companies function displayCompanies() { const container = document.getElementById('companies-grid'); container.innerHTML = ''; Object.entries(companyData).forEach(([companyKey, company]) => { const card = document.createElement('div'); card.className = 'company-card'; card.dataset.company = companyKey; const status = company.isAvailable ? 'available' : 'empty'; const statusText = company.isAvailable ? 'Available' : 'No Questions'; const questionTypes = Object.entries(company.questionTypes || {}) .map(([type, count]) => `${type}: ${count}`) .join(', ') || 'None'; const lastUpdated = company.lastUpdated ? new Date(company.lastUpdated).toLocaleDateString() : 'Never'; card.innerHTML = ` <div class="company-header"> <div class="company-name">${company.name}</div> <div class="company-status status-${status}">${statusText}</div> </div> <div class="company-description">${company.description}</div> <div class="company-stats"> <div class="company-stat"> <div class="company-stat-value">${company.totalQuestions}</div> <div class="company-stat-label">Questions</div> </div> <div class="company-stat"> <div class="company-stat-value">${Object.keys(company.questionTypes || {}).length}</div> <div class="company-stat-label">Types</div> </div> </div> <div class="company-actions"> <button class="action-btn btn-primary" onclick="downloadCompanyQuestions('${companyKey}')" ${!aiStatus.connected ? 'disabled' : ''}> 📥 Download </button> <button class="action-btn btn-secondary" onclick="viewCompanyDetails('${companyKey}')"> 👁️ View </button> ${company.isAvailable ? ` <button class="action-btn btn-success" onclick="exportCompanyQuestions('${companyKey}')"> 📤 Export </button> <button class="action-btn btn-danger" onclick="deleteCompanyQuestions('${companyKey}')"> 🗑️ Delete </button> ` : ''} </div> <div class="download-progress" id="progress-${companyKey}"> <div class="progress-header"> <div class="progress-title">Downloading Questions...</div> <div class="progress-percentage" id="percentage-${companyKey}">0%</div> </div> <div class="progress-bar"> <div class="progress-fill" id="fill-${companyKey}"></div> </div> <div class="progress-message" id="message-${companyKey}">Preparing download...</div> </div> `; container.appendChild(card); }); } // Display download history function displayDownloadHistory() { const tbody = document.getElementById('history-tbody'); tbody.innerHTML = ''; const history = downloadStats.downloadHistory || []; if (history.length === 0) { tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; color: #666;">No downloads yet</td></tr>'; return; } history.forEach(download => { const row = document.createElement('tr'); const duration = Math.round(download.duration / 1000); const date = new Date(download.endTime).toLocaleString(); const status = download.success ? 'Success' : 'Failed'; const statusClass = download.success ? 'status-success' : 'status-failed'; row.innerHTML = ` <td>${companyData[download.company]?.name || download.company}</td> <td>${download.questionsGenerated}</td> <td>${duration}s</td> <td class="${statusClass}">${status}</td> <td>${date}</td> `; tbody.appendChild(row); }); } // Download company questions async function downloadCompanyQuestions(companyKey) { if (!aiStatus.connected) { alert('AI connection required. Please configure AI in settings first.'); return; } if (activeDownloads.has(companyKey)) { alert('Download already in progress for this company.'); return; } try { activeDownloads.add(companyKey); // Show progress const progressDiv = document.getElementById(`progress-${companyKey}`); progressDiv.style.display = 'block'; // Disable download button const card = document.querySelector(`[data-company="${companyKey}"]`); const downloadBtn = card.querySelector('.btn-primary'); downloadBtn.disabled = true; downloadBtn.textContent = '⏳ Downloading...'; // Start download const result = await ipcRenderer.invoke('download-company-questions', companyKey); if (result.success) { alert(`Successfully downloaded ${result.questionsGenerated} questions for ${result.company}!`); // Reload data and refresh display await loadCompanyData(); displayStats(); displayCompanies(); displayDownloadHistory(); } else { throw new Error(result.error || 'Download failed'); } } catch (error) { console.error('Download failed:', error); alert(`Failed to download questions: ${error.message}`); // Hide progress const progressDiv = document.getElementById(`progress-${companyKey}`); progressDiv.style.display = 'none'; // Re-enable button const card = document.querySelector(`[data-company="${companyKey}"]`); const downloadBtn = card.querySelector('.btn-primary'); downloadBtn.disabled = false; downloadBtn.textContent = '📥 Download'; } finally { activeDownloads.delete(companyKey); } } // Update download progress function updateDownloadProgress(companyKey, progress) { const fillElement = document.getElementById(`fill-${companyKey}`); const percentageElement = document.getElementById(`percentage-${companyKey}`); const messageElement = document.getElementById(`message-${companyKey}`); if (fillElement) fillElement.style.width = `${progress.progress}%`; if (percentageElement) percentageElement.textContent = `${Math.round(progress.progress)}%`; if (messageElement) messageElement.textContent = progress.message; } // View company details function viewCompanyDetails(companyKey) { const company = companyData[companyKey]; if (!company) return; document.getElementById('modal-company-name').textContent = company.name; const questionTypesList = Object.entries(company.questionTypes || {}) .map(([type, count]) => `<li><strong>${type}:</strong> ${count} questions</li>`) .join(''); const lastUpdated = company.lastUpdated ? new Date(company.lastUpdated).toLocaleString() : 'Never'; document.getElementById('modal-content').innerHTML = ` <div style="margin-bottom: 20px;"> <h4>Description</h4> <p>${company.description}</p> </div> <div style="margin-bottom: 20px;"> <h4>Statistics</h4> <ul> <li><strong>Total Questions:</strong> ${company.totalQuestions}</li> <li><strong>Last Updated:</strong> ${lastUpdated}</li> <li><strong>Status:</strong> ${company.isAvailable ? 'Available' : 'No questions'}</li> </ul> </div> ${questionTypesList ? ` <div style="margin-bottom: 20px;"> <h4>Question Types</h4> <ul>${questionTypesList}</ul> </div> ` : ''} <div style="text-align: center; margin-top: 30px;"> <button class="action-btn btn-primary" onclick="downloadCompanyQuestions('${companyKey}')" ${!aiStatus.connected ? 'disabled' : ''}> 📥 Download Questions </button> ${company.isAvailable ? ` <button class="action-btn btn-success" onclick="exportCompanyQuestions('${companyKey}'); closeModal();"> 📤 Export Questions </button> ` : ''} </div> `; document.getElementById('company-modal').style.display = 'block'; } // Export company questions async function exportCompanyQuestions(companyKey) { try { const data = await ipcRenderer.invoke('export-company-questions', companyKey); const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${companyKey}-questions-${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(url); } catch (error) { console.error('Export failed:', error); alert('Failed to export questions: ' + error.message); } } // Delete company questions async function deleteCompanyQuestions(companyKey) { const company = companyData[companyKey]; if (!company) return; if (confirm(`Are you sure you want to delete all ${company.totalQuestions} questions for ${company.name}? This action cannot be undone.`)) { try { await ipcRenderer.invoke('delete-company-questions', companyKey); // Reload data and refresh display await loadCompanyData(); displayStats(); displayCompanies(); alert(`Successfully deleted questions for ${company.name}.`); } catch (error) { console.error('Delete failed:', error); alert('Failed to delete questions: ' + error.message); } } } // Setup event listeners function setupEventListeners() { // Global actions document.getElementById('download-all-btn').addEventListener('click', downloadAllCompanies); document.getElementById('export-all-btn').addEventListener('click', exportAllQuestions); document.getElementById('clear-all-btn').addEventListener('click', clearAllQuestions); // Listen for download progress updates ipcRenderer.on('download-progress', (event, companyKey, progress) => { updateDownloadProgress(companyKey, progress); }); // Modal close on outside click document.getElementById('company-modal').addEventListener('click', (e) => { if (e.target.id === 'company-modal') { closeModal(); } }); } // Download all companies async function downloadAllCompanies() { if (!aiStatus.connected) { alert('AI connection required. Please configure AI in settings first.'); return; } const companies = Object.keys(companyData); if (confirm(`This will download questions for all ${companies.length} companies. This may take several minutes. Continue?`)) { for (const companyKey of companies) { if (!activeDownloads.has(companyKey)) { try { await downloadCompanyQuestions(companyKey); // Small delay between downloads await new Promise(resolve => setTimeout(resolve, 2000)); } catch (error) { console.error(`Failed to download ${companyKey}:`, error); } } } } } // Export all questions async function exportAllQuestions() { try { const allData = {}; for (const [companyKey, company] of Object.entries(companyData)) { if (company.isAvailable) { allData[companyKey] = await ipcRenderer.invoke('export-company-questions', companyKey); } } const exportData = { exportedAt: new Date(), companies: allData, totalCompanies: Object.keys(allData).length, totalQuestions: Object.values(allData).reduce((sum, data) => sum + data.totalQuestions, 0) }; const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `all-company-questions-${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(url); } catch (error) { console.error('Export all failed:', error); alert('Failed to export all questions: ' + error.message); } } // Clear all questions async function clearAllQuestions() { const totalQuestions = Object.values(companyData).reduce((sum, c) => sum + c.totalQuestions, 0); if (confirm(`Are you sure you want to delete ALL ${totalQuestions} company questions? This action cannot be undone.`)) { try { await ipcRenderer.invoke('clear-all-company-questions'); // Reload data and refresh display await loadCompanyData(); displayStats(); displayCompanies(); displayDownloadHistory(); alert('Successfully cleared all company questions.'); } catch (error) { console.error('Clear all failed:', error); alert('Failed to clear questions: ' + error.message); } } } // Close modal function closeModal() { document.getElementById('company-modal').style.display = 'none'; } // Close window function closeWindow() { window.close(); } // Show error function showError(message) { document.getElementById('loading').innerHTML = ` <h3 style="color: #dc3545;">⚠️ Error</h3> <p>${message}</p> <button class="action-btn btn-primary" onclick="closeWindow()">Close</button> `; } // Initialize when page loads document.addEventListener('DOMContentLoaded', initializeInterface); </script> </body> </html>