// ────────────────────────────── static/quiz.js ────────────────────────────── (function() { // Quiz state let currentQuiz = null; let currentQuestionIndex = 0; let quizAnswers = {}; let quizTimer = null; let timeRemaining = 0; let quizSetupStep = 1; // DOM elements const quizLink = document.getElementById('quiz-link'); const quizSetupModal = document.getElementById('quiz-setup-modal'); const quizModal = document.getElementById('quiz-modal'); const quizResultsModal = document.getElementById('quiz-results-modal'); const quizSetupForm = document.getElementById('quiz-setup-form'); const quizQuestionsInput = document.getElementById('quiz-questions-input'); const quizTimeLimit = document.getElementById('quiz-time-limit'); const quizDocumentList = document.getElementById('quiz-document-list'); const quizPrevStep = document.getElementById('quiz-prev-step'); const quizNextStep = document.getElementById('quiz-next-step'); const quizCancel = document.getElementById('quiz-cancel'); const quizSubmit = document.getElementById('quiz-submit'); const quizTimerElement = document.getElementById('quiz-timer'); const quizProgressFill = document.getElementById('quiz-progress-fill'); const quizProgressText = document.getElementById('quiz-progress-text'); const quizQuestion = document.getElementById('quiz-question'); const quizAnswers = document.getElementById('quiz-answers'); const quizPrev = document.getElementById('quiz-prev'); const quizNext = document.getElementById('quiz-next'); const quizSubmitBtn = document.getElementById('quiz-submit'); const quizResultsContent = document.getElementById('quiz-results-content'); const quizResultsClose = document.getElementById('quiz-results-close'); // Initialize init(); function init() { setupEventListeners(); } function setupEventListeners() { // Quiz link if (quizLink) { quizLink.addEventListener('click', (e) => { e.preventDefault(); openQuizSetup(); }); } // Quiz setup form if (quizSetupForm) { quizSetupForm.addEventListener('submit', handleQuizSetupSubmit); } // Quiz setup navigation if (quizNextStep) { quizNextStep.addEventListener('click', nextQuizStep); } if (quizPrevStep) { quizPrevStep.addEventListener('click', prevQuizStep); } if (quizCancel) { quizCancel.addEventListener('click', closeQuizSetup); } // Quiz navigation if (quizPrev) { quizPrev.addEventListener('click', prevQuestion); } if (quizNext) { quizNext.addEventListener('click', nextQuestion); } if (quizSubmitBtn) { quizSubmitBtn.addEventListener('click', submitQuiz); } // Quiz results if (quizResultsClose) { quizResultsClose.addEventListener('click', closeQuizResults); } // Close modals on outside click document.addEventListener('click', (e) => { if (e.target.classList.contains('modal')) { closeAllQuizModals(); } }); } async function openQuizSetup() { const user = window.__sb_get_user(); if (!user) { alert('Please sign in to create a quiz'); window.__sb_show_auth_modal(); return; } const currentProject = window.__sb_get_current_project && window.__sb_get_current_project(); if (!currentProject) { alert('Please select a project first'); return; } // Load available documents await loadQuizDocuments(); // Reset form quizSetupStep = 1; updateQuizSetupStep(); // Show modal quizSetupModal.classList.remove('hidden'); } async function loadQuizDocuments() { const user = window.__sb_get_user(); const currentProject = window.__sb_get_current_project && window.__sb_get_current_project(); if (!user || !currentProject) return; try { const res = await fetch(`/files?user_id=${encodeURIComponent(user.user_id)}&project_id=${encodeURIComponent(currentProject.project_id)}`); if (!res.ok) return; const data = await res.json(); const files = data.files || []; // Clear existing documents quizDocumentList.innerHTML = ''; if (files.length === 0) { quizDocumentList.innerHTML = '
No documents available. Please upload documents first.
'; return; } // Create document checkboxes files.forEach((file, index) => { const item = document.createElement('div'); item.className = 'document-checkbox-item'; item.innerHTML = ` `; quizDocumentList.appendChild(item); }); } catch (error) { console.error('Failed to load documents:', error); quizDocumentList.innerHTML = '
Failed to load documents.
'; } } function updateQuizSetupStep() { // Hide all steps document.querySelectorAll('.quiz-step').forEach(step => { step.style.display = 'none'; }); // Show current step const currentStep = document.getElementById(`quiz-step-${quizSetupStep}`); if (currentStep) { currentStep.style.display = 'block'; } // Update navigation buttons quizPrevStep.style.display = quizSetupStep > 1 ? 'inline-flex' : 'none'; quizNextStep.style.display = quizSetupStep < 3 ? 'inline-flex' : 'none'; quizSubmit.style.display = quizSetupStep === 3 ? 'inline-flex' : 'none'; } function nextQuizStep() { if (quizSetupStep < 3) { quizSetupStep++; updateQuizSetupStep(); } } function prevQuizStep() { if (quizSetupStep > 1) { quizSetupStep--; updateQuizSetupStep(); } } function closeQuizSetup() { quizSetupModal.classList.add('hidden'); quizSetupStep = 1; updateQuizSetupStep(); } async function handleQuizSetupSubmit(e) { e.preventDefault(); const user = window.__sb_get_user(); const currentProject = window.__sb_get_current_project && window.__sb_get_current_project(); if (!user || !currentProject) { showNotification('Please sign in and select a project', 'error'); return; } // Get form data const questionsInput = quizQuestionsInput.value.trim(); const timeLimit = parseInt(quizTimeLimit.value) || 0; // Get selected documents const selectedDocs = Array.from(quizDocumentList.querySelectorAll('input[type="checkbox"]:checked')) .map(input => input.value); if (selectedDocs.length === 0) { showNotification('Please select at least one document', 'warning'); return; } if (!questionsInput) { showNotification('Please specify how many questions you want', 'warning'); return; } // Validate questions input if (questionsInput.length < 10) { showNotification('Please provide more details about your question requirements', 'warning'); return; } // Show loading with progress updates showLoading('Creating quiz...'); updateLoadingProgress('Parsing your requirements...', 20); try { // Create quiz const formData = new FormData(); formData.append('user_id', user.user_id); formData.append('project_id', currentProject.project_id); formData.append('questions_input', questionsInput); formData.append('time_limit', timeLimit.toString()); formData.append('documents', JSON.stringify(selectedDocs)); updateLoadingProgress('Analyzing documents...', 40); const response = await fetch('/quiz/create', { method: 'POST', body: formData }); const data = await response.json(); if (response.ok) { updateLoadingProgress('Generating questions...', 80); // Simulate processing time for better UX await new Promise(resolve => setTimeout(resolve, 1000)); hideLoading(); closeQuizSetup(); // Start quiz currentQuiz = data.quiz; currentQuestionIndex = 0; quizAnswers = {}; timeRemaining = timeLimit * 60; // Convert to seconds showNotification(`Quiz created successfully! ${currentQuiz.questions.length} questions generated.`, 'success'); startQuiz(); } else { hideLoading(); showNotification(data.detail || 'Failed to create quiz', 'error'); } } catch (error) { hideLoading(); console.error('Quiz creation failed:', error); showNotification('Failed to create quiz. Please try again.', 'error'); } } function startQuiz() { // Show quiz modal quizModal.classList.remove('hidden'); // Start timer if time limit is set if (timeRemaining > 0) { startQuizTimer(); } else { quizTimerElement.textContent = 'No time limit'; } // Show first question showQuestion(0); } function startQuizTimer() { updateTimerDisplay(); quizTimer = setInterval(() => { timeRemaining--; updateTimerDisplay(); if (timeRemaining <= 0) { clearInterval(quizTimer); timeUp(); } }, 1000); } function updateTimerDisplay() { const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; const timeString = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; quizTimerElement.textContent = `Time: ${timeString}`; // Add warning classes quizTimerElement.classList.remove('warning', 'danger'); if (timeRemaining <= 60) { quizTimerElement.classList.add('danger'); } else if (timeRemaining <= 300) { // 5 minutes quizTimerElement.classList.add('warning'); } } function timeUp() { alert('Time Up!'); submitQuiz(); } function showQuestion(index) { if (!currentQuiz || !currentQuiz.questions || index >= currentQuiz.questions.length) { return; } const question = currentQuiz.questions[index]; currentQuestionIndex = index; // Update progress const progress = ((index + 1) / currentQuiz.questions.length) * 100; quizProgressFill.style.width = `${progress}%`; quizProgressText.textContent = `Question ${index + 1} of ${currentQuiz.questions.length}`; // Show question quizQuestion.innerHTML = `

Question ${index + 1}

${question.question}

`; // Show answers if (question.type === 'mcq') { showMCQAnswers(question); } else if (question.type === 'self_reflect') { showSelfReflectAnswer(question); } // Update navigation quizPrev.disabled = index === 0; quizNext.style.display = index < currentQuiz.questions.length - 1 ? 'inline-flex' : 'none'; quizSubmitBtn.style.display = index === currentQuiz.questions.length - 1 ? 'inline-flex' : 'none'; } function showMCQAnswers(question) { quizAnswers.innerHTML = ''; question.options.forEach((option, index) => { const optionDiv = document.createElement('div'); optionDiv.className = 'quiz-answer-option'; optionDiv.innerHTML = ` `; // Check if already answered if (quizAnswers[currentQuestionIndex] !== undefined) { const radio = optionDiv.querySelector('input[type="radio"]'); radio.checked = quizAnswers[currentQuestionIndex] === index; if (radio.checked) { optionDiv.classList.add('selected'); } } // Add click handler optionDiv.addEventListener('click', () => { // Remove selection from other options quizAnswers.querySelectorAll('.quiz-answer-option').forEach(opt => { opt.classList.remove('selected'); }); // Select this option optionDiv.classList.add('selected'); const radio = optionDiv.querySelector('input[type="radio"]'); radio.checked = true; // Save answer quizAnswers[currentQuestionIndex] = index; }); quizAnswers.appendChild(optionDiv); }); } function showSelfReflectAnswer(question) { quizAnswers.innerHTML = ` `; const textarea = quizAnswers.querySelector('textarea'); textarea.addEventListener('input', (e) => { quizAnswers[currentQuestionIndex] = e.target.value; }); } function prevQuestion() { if (currentQuestionIndex > 0) { showQuestion(currentQuestionIndex - 1); } } function nextQuestion() { if (currentQuestionIndex < currentQuiz.questions.length - 1) { showQuestion(currentQuestionIndex + 1); } } async function submitQuiz() { if (quizTimer) { clearInterval(quizTimer); } // Validate that all questions are answered const unansweredQuestions = []; for (let i = 0; i < currentQuiz.questions.length; i++) { if (quizAnswers[i] === undefined || quizAnswers[i] === null || quizAnswers[i] === '') { unansweredQuestions.push(i + 1); } } if (unansweredQuestions.length > 0) { const confirmSubmit = confirm(`You have ${unansweredQuestions.length} unanswered questions (${unansweredQuestions.join(', ')}). Do you want to submit anyway?`); if (!confirmSubmit) { return; } } // Show loading with progress showLoading('Submitting quiz...'); updateLoadingProgress('Processing your answers...', 30); try { const user = window.__sb_get_user(); const currentProject = window.__sb_get_current_project && window.__sb_get_current_project(); updateLoadingProgress('Marking your answers...', 60); const formData = new FormData(); formData.append('user_id', user.user_id); formData.append('project_id', currentProject.project_id); formData.append('quiz_id', currentQuiz.quiz_id); formData.append('answers', JSON.stringify(quizAnswers)); const response = await fetch('/quiz/submit', { method: 'POST', body: formData }); const data = await response.json(); if (response.ok) { updateLoadingProgress('Generating feedback...', 90); // Simulate processing time for better UX await new Promise(resolve => setTimeout(resolve, 1500)); hideLoading(); closeQuizModal(); showQuizResults(data.results); } else { hideLoading(); showNotification(data.detail || 'Failed to submit quiz', 'error'); } } catch (error) { hideLoading(); console.error('Quiz submission failed:', error); showNotification('Failed to submit quiz. Please try again.', 'error'); } } function showQuizResults(results) { // Show results summary const totalQuestions = results.questions.length; const correctAnswers = results.questions.filter(q => q.status === 'correct').length; const partialAnswers = results.questions.filter(q => q.status === 'partial').length; const incorrectAnswers = results.questions.filter(q => q.status === 'incorrect').length; const score = Math.round((correctAnswers + partialAnswers * 0.5) / totalQuestions * 100); // Determine performance level let performanceLevel = 'Needs Improvement'; let performanceColor = 'var(--error)'; if (score >= 90) { performanceLevel = 'Excellent'; performanceColor = 'var(--success)'; } else if (score >= 80) { performanceLevel = 'Good'; performanceColor = 'var(--accent)'; } else if (score >= 70) { performanceLevel = 'Satisfactory'; performanceColor = 'var(--warning)'; } quizResultsContent.innerHTML = `
${score}%
Score
${performanceLevel}
${correctAnswers}
Correct
${partialAnswers}
Partial
${incorrectAnswers}
Incorrect
${results.questions.map((question, index) => `
Question ${index + 1}
${question.status}
${question.question}
${question.type === 'mcq' ? `
Your Answer:
${question.options[question.user_answer] || 'No answer'}
${question.status === 'incorrect' ? `
Correct Answer:
${question.options[question.correct_answer]}
` : ''} ` : `
Your Answer:
${question.user_answer || 'No answer'}
`} ${question.explanation ? `
Explanation: ${question.explanation}
` : ''}
`).join('')}
`; quizResultsModal.classList.remove('hidden'); // Show success notification showNotification(`Quiz completed! You scored ${score}% (${performanceLevel})`, 'success'); } function closeQuizModal() { quizModal.classList.add('hidden'); } function closeQuizResults() { quizResultsModal.classList.add('hidden'); } function closeAllQuizModals() { closeQuizSetup(); closeQuizModal(); closeQuizResults(); } function showLoading(message = 'Loading...') { const loadingOverlay = document.getElementById('loading-overlay'); const loadingMessage = document.getElementById('loading-message'); if (loadingOverlay && loadingMessage) { loadingMessage.textContent = message; loadingOverlay.classList.remove('hidden'); } } function updateLoadingProgress(message, progress) { const loadingOverlay = document.getElementById('loading-overlay'); const loadingMessage = document.getElementById('loading-message'); const loadingProgress = document.getElementById('loading-progress'); if (loadingOverlay && loadingMessage) { loadingMessage.textContent = message; if (loadingProgress) { loadingProgress.style.width = `${progress}%`; } } } function hideLoading() { const loadingOverlay = document.getElementById('loading-overlay'); if (loadingOverlay) { loadingOverlay.classList.add('hidden'); } } function showNotification(message, type = 'info') { // Create notification element const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.innerHTML = `
${message}
`; // Add to page document.body.appendChild(notification); // Auto remove after 5 seconds setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 5000); // Close button notification.querySelector('.notification-close').addEventListener('click', () => { notification.remove(); }); } // Expose functions globally window.__sb_open_quiz_setup = openQuizSetup; window.__sb_close_quiz_setup = closeQuizSetup; })();