// Global variables let authToken = ''; let userId = ''; let currentBook = null; let currentChapter = null; let userTimeSpent = 0; // Track user's cumulative time spent // ==================== UTILITY FUNCTIONS ==================== function showSection(sectionId) { document.querySelectorAll('.section').forEach(section => { section.classList.add('hidden'); }); document.getElementById(sectionId).classList.remove('hidden'); } function showLoading(show = true) { const loading = document.getElementById('loading'); if (show) { loading.classList.remove('hidden'); } else { loading.classList.add('hidden'); } } function addLogEntry(message, type = 'info') { const log = document.getElementById('results-log'); const entry = document.createElement('div'); entry.className = `log-entry log-${type}`; entry.textContent = `${new Date().toLocaleTimeString()} - ${message}`; log.appendChild(entry); log.scrollTop = log.scrollHeight; } function updateProgress(current, total) { const progressFill = document.getElementById('progress-fill'); const progressText = document.getElementById('progress-text'); const percentage = Math.round((current / total) * 100); progressFill.style.width = `${percentage}%`; progressText.textContent = `Progress: ${current}/${total} (${percentage}%)`; } // ==================== API FUNCTIONS ==================== // Get section parts count async function getSectionPartsCount(section, chapter = currentChapter) { try { if (!section || !section.number) { return 99; } if (!chapter || !chapter.number) { return 99; } const response = await fetch(`/api/section-parts/${currentBook.zybook_code}/${chapter.number}/${section.number}?auth_token=${authToken}`); if (response.ok) { const data = await response.json(); if (data.success) { return data.totalParts; } } return 99; } catch (error) { console.error('Error getting section parts count:', error); return 99; } } // Solve single section for batch processing async function solveSectionForBatch(section, currentSection, totalSections, sectionIndex, globalPartTracker) { try { if (!currentBook || !currentBook.zybook_code) { throw new Error('Current book not set'); } if (!currentChapter) { throw new Error('Current chapter not set'); } if (!authToken) { throw new Error('Auth token not set'); } if (!section) { throw new Error('Section data missing'); } const response = await fetch('/api/solve/section', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' }, body: JSON.stringify({ bookCode: currentBook.zybook_code, chapter: currentChapter, section: section, auth: authToken, timeSpent: userTimeSpent }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; let sectionResults = []; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.startsWith('data: ')) { try { const data = JSON.parse(line.slice(6)); switch (data.type) { case 'total': addLogEntry(` 📊 [${sectionIndex}/${totalSections}] Section has ${data.total} parts`, 'info'); break; case 'progress': addLogEntry(` ⚡ [${sectionIndex}/${totalSections}] Processing ${data.current}/${data.total}...`, 'info'); break; case 'result': sectionResults.push(data); globalPartTracker.completedParts++; updateProgress(globalPartTracker.completedParts, globalPartTracker.totalParts); const status = data.success ? '✅' : '❌'; addLogEntry(` ${status} [${sectionIndex}/${totalSections}] Part ${data.part || sectionResults.length}: ${data.success ? 'Success' : 'Failed'} (Global: ${globalPartTracker.completedParts}/${globalPartTracker.totalParts})`, data.success ? 'success' : 'error'); break; case 'complete': const successCount = sectionResults.filter(r => r.success).length; addLogEntry(` 🎯 [${sectionIndex}/${totalSections}] Section completed! Success: ${successCount}/${sectionResults.length}`, 'success'); break; case 'error': addLogEntry(` ❌ [${sectionIndex}/${totalSections}] Section failed: ${data.message}`, 'error'); throw new Error(data.message); } } catch (e) { if (e.message && e.message !== 'Unexpected end of JSON input') { console.error('Error parsing SSE data:', e); } } } } } } catch (error) { throw error; } } // Solve single section async function solveSection(section) { showSection('progress-section'); updateProgress(0, 1); document.getElementById('results-log').innerHTML = ''; addLogEntry(`Starting section: ${currentChapter.number}.${section.number} - ${section.title}`); try { const response = await fetch('/api/solve/section', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' }, body: JSON.stringify({ bookCode: currentBook.zybook_code, chapter: currentChapter, section: section, auth: authToken, timeSpent: userTimeSpent }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.startsWith('data: ')) { try { const data = JSON.parse(line.slice(6)); handleSSEMessage(data); } catch (e) { console.error('Error parsing SSE data:', e); } } } } } catch (error) { console.error('Section solving error:', error); addLogEntry('Error solving section: ' + error.message, 'error'); } } // Handle SSE messages function handleSSEMessage(data) { switch (data.type) { case 'start': addLogEntry(data.message, 'info'); break; case 'total': updateProgress(0, data.total); break; case 'progress': updateProgress(data.current, data.total); addLogEntry(data.message, 'info'); break; case 'result': updateProgress(data.current, data.total); const resultType = data.success ? 'success' : 'error'; addLogEntry(data.message, resultType); // Update user time spent if provided if (data.timeSpent !== undefined) { userTimeSpent = data.timeSpent; } break; case 'complete': addLogEntry(data.message, 'success'); // Update final time spent if provided if (data.finalTimeSpent !== undefined) { userTimeSpent = data.finalTimeSpent; } break; case 'error': addLogEntry(data.message, 'error'); break; } } function goHome() { // Reset user time spent userTimeSpent = 0; document.getElementById('results-log').innerHTML = ''; document.getElementById('progress-fill').style.width = '0%'; document.getElementById('progress-text').textContent = 'Ready to start...'; showSection('books-section'); } // ==================== LOGIN & DATA LOADING ==================== document.addEventListener('DOMContentLoaded', function() { const loading = document.getElementById('loading'); if (loading) { loading.classList.add('hidden'); } const loginSection = document.getElementById('login-section'); if (loginSection) { loginSection.classList.remove('hidden'); } }); document.getElementById('login-form').addEventListener('submit', async (e) => { e.preventDefault(); const email = document.getElementById('email').value; const password = document.getElementById('password').value; showLoading(true); try { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await response.json(); if (data.success) { authToken = data.auth_token; userId = data.user_id; loadBooks(); } else { alert('Login failed: ' + data.message); } } catch (error) { console.error('Login error:', error); alert('Login error: ' + error.message); } finally { showLoading(false); } }); async function loadBooks() { showLoading(true); try { const response = await fetch(`/api/books/${userId}?auth_token=${authToken}`); const data = await response.json(); if (data.success) { const booksList = document.getElementById('books-list'); booksList.innerHTML = data.books.map(book => `
${book.title} ${book.zybook_code}
` ).join(''); showSection('books-section'); } else { alert('Failed to load books: ' + data.message); } } catch (error) { console.error('Error loading books:', error); alert('Error loading books: ' + error.message); } finally { showLoading(false); } } async function loadChapters(bookCode, bookTitle) { showLoading(true); currentBook = { zybook_code: bookCode, title: bookTitle }; try { const response = await fetch(`/api/chapters/${bookCode}?auth_token=${authToken}`); const data = await response.json(); if (data.success) { const chaptersList = document.getElementById('chapters-list'); chaptersList.innerHTML = data.chapters.map((chapter, index) => `
Chapter ${chapter.number}: ${chapter.title} ${chapter.sections.length} sections
` ).join(''); window.chaptersData = data.chapters; updateChapterSelectionCount(); showSection('chapters-section'); } else { alert('Failed to load chapters: ' + data.message); } } catch (error) { console.error('Error loading chapters:', error); alert('Error loading chapters: ' + error.message); } finally { showLoading(false); } } function loadSections(chapter) { currentChapter = chapter; const sectionsList = document.getElementById('sections-list'); sectionsList.innerHTML = chapter.sections.map((section, index) => `
Section ${chapter.number}.${section.number}: ${section.title}
` ).join(''); window.sectionsData = chapter.sections; updateSectionSelectionCount(); showSection('sections-section'); } // ==================== CHAPTER MULTI-SELECT ==================== function updateChapterSelection() { const checkboxes = document.querySelectorAll('#chapters-list input[type="checkbox"]'); checkboxes.forEach((checkbox, index) => { const listItem = checkbox.closest('.list-item-checkbox'); if (checkbox.checked) { listItem.classList.add('selected'); } else { listItem.classList.remove('selected'); } }); updateChapterSelectionCount(); } function updateChapterSelectionCount() { const checkboxes = document.querySelectorAll('#chapters-list input[type="checkbox"]:checked'); const count = checkboxes.length; const batchControls = document.querySelector('#chapters-section .batch-controls'); let countDisplay = batchControls.querySelector('.selected-count'); if (!countDisplay) { countDisplay = document.createElement('div'); countDisplay.className = 'selected-count'; batchControls.appendChild(countDisplay); } countDisplay.textContent = `${count} chapters selected`; } function selectAllChapters(selectAll) { const checkboxes = document.querySelectorAll('#chapters-list input[type="checkbox"]'); checkboxes.forEach(checkbox => { checkbox.checked = selectAll; }); updateChapterSelection(); } function selectSingleChapter(index) { const chapter = window.chaptersData[index]; currentChapter = chapter; loadSections(chapter); } async function executeSelectedChapters() { const checkboxes = document.querySelectorAll('#chapters-list input[type="checkbox"]:checked'); if (checkboxes.length === 0) { alert('Please select at least one chapter'); return; } const selectedChapters = Array.from(checkboxes).map(checkbox => { const parentDiv = checkbox.closest('.list-item-checkbox'); const index = parseInt(parentDiv.dataset.chapterIndex); return { index: index, chapter: window.chaptersData[index] }; }); const totalSections = selectedChapters.reduce((total, { chapter }) => total + chapter.sections.length, 0); showSection('progress-section'); updateProgress(0, 1); document.getElementById('results-log').innerHTML = ''; addLogEntry(`Starting batch processing of ${selectedChapters.length} chapters (${totalSections} sections total)`); addLogEntry(`📊 Calculating total parts for all sections...`, 'info'); // Pre-calculate total parts let totalParts = 0; let sectionIndex = 0; for (let i = 0; i < selectedChapters.length; i++) { const { chapter } = selectedChapters[i]; addLogEntry(`📚 Calculating chapter ${chapter.number}: ${chapter.title} (${chapter.sections.length} sections)`, 'info'); for (let j = 0; j < chapter.sections.length; j++) { sectionIndex++; const section = chapter.sections[j]; addLogEntry(` 🔍 Getting parts count for section ${chapter.number}.${section.number}...`, 'info'); const sectionParts = await getSectionPartsCount(section, chapter); totalParts += sectionParts; addLogEntry(` 📊 Section ${chapter.number}.${section.number} has ${sectionParts} parts`, 'info'); } } const globalPartTracker = { totalParts: totalParts, completedParts: 0 }; updateProgress(0, totalParts); addLogEntry(`🎯 Total parts to process: ${totalParts}`, 'success'); sectionIndex = 0; for (let i = 0; i < selectedChapters.length; i++) { const { chapter } = selectedChapters[i]; currentChapter = chapter; addLogEntry(`📚 Starting chapter ${chapter.number}: ${chapter.title} (${chapter.sections.length} sections)`, 'info'); for (let j = 0; j < chapter.sections.length; j++) { sectionIndex++; const section = chapter.sections[j]; addLogEntry(`📄 Starting section ${chapter.number}.${section.number}: ${section.title}`, 'info'); try { await solveSectionForBatch(section, `${chapter.number}.${section.number}`, totalSections, sectionIndex, globalPartTracker); addLogEntry(`✅ Section ${chapter.number}.${section.number} completed`, 'success'); } catch (error) { addLogEntry(`❌ Section ${chapter.number}.${section.number} failed: ${error.message}`, 'error'); } await new Promise(resolve => setTimeout(resolve, 1000)); } addLogEntry(`✅ Chapter ${chapter.number} completed (${chapter.sections.length} sections)`, 'success'); if (i < selectedChapters.length - 1) { await new Promise(resolve => setTimeout(resolve, 2000)); } } addLogEntry(`🎉 Batch chapter processing completed! Processed ${totalParts} parts total`, 'success'); } // ==================== SECTION MULTI-SELECT ==================== function updateSectionSelection() { const checkboxes = document.querySelectorAll('#sections-list input[type="checkbox"]'); checkboxes.forEach((checkbox, index) => { const listItem = checkbox.closest('.list-item-checkbox'); if (checkbox.checked) { listItem.classList.add('selected'); } else { listItem.classList.remove('selected'); } }); updateSectionSelectionCount(); } function updateSectionSelectionCount() { const checkboxes = document.querySelectorAll('#sections-list input[type="checkbox"]:checked'); const count = checkboxes.length; const batchControls = document.querySelector('#sections-section .batch-controls'); let countDisplay = batchControls.querySelector('.selected-count'); if (!countDisplay) { countDisplay = document.createElement('div'); countDisplay.className = 'selected-count'; batchControls.appendChild(countDisplay); } countDisplay.textContent = `${count} sections selected`; } function selectAllSections(selectAll) { const checkboxes = document.querySelectorAll('#sections-list input[type="checkbox"]'); checkboxes.forEach(checkbox => { checkbox.checked = selectAll; }); updateSectionSelection(); } async function executeSelectedSections() { const checkboxes = document.querySelectorAll('#sections-list input[type="checkbox"]:checked'); if (checkboxes.length === 0) { alert('Please select at least one section'); return; } const selectedSections = Array.from(checkboxes).map(checkbox => { const parentDiv = checkbox.closest('.list-item-checkbox'); const index = parseInt(parentDiv.dataset.sectionIndex); return { index: index, section: window.sectionsData[index] }; }); showSection('progress-section'); updateProgress(0, 1); document.getElementById('results-log').innerHTML = ''; addLogEntry(`Starting batch processing of ${selectedSections.length} sections`); addLogEntry(`📊 Calculating total parts for all sections...`, 'info'); // Pre-calculate total parts let totalParts = 0; for (let i = 0; i < selectedSections.length; i++) { const { section } = selectedSections[i]; addLogEntry(` 🔍 Getting parts count for section ${currentChapter.number}.${section.number}...`, 'info'); const sectionParts = await getSectionPartsCount(section, currentChapter); totalParts += sectionParts; addLogEntry(` 📊 Section ${currentChapter.number}.${section.number} has ${sectionParts} parts`, 'info'); } const globalPartTracker = { totalParts: totalParts, completedParts: 0 }; updateProgress(0, totalParts); addLogEntry(`🎯 Total parts to process: ${totalParts}`, 'success'); for (let i = 0; i < selectedSections.length; i++) { const { section } = selectedSections[i]; addLogEntry(`📄 Starting section ${currentChapter.number}.${section.number}: ${section.title}`, 'info'); try { await solveSectionForBatch(section, i + 1, selectedSections.length, i + 1, globalPartTracker); addLogEntry(`✅ Section ${currentChapter.number}.${section.number} completed`, 'success'); } catch (error) { addLogEntry(`❌ Section ${currentChapter.number}.${section.number} failed: ${error.message}`, 'error'); } if (i < selectedSections.length - 1) { await new Promise(resolve => setTimeout(resolve, 1000)); } } addLogEntry(`🎉 Batch section processing completed! Processed ${totalParts} parts total`, 'success'); }