// Main JavaScript file for EduCat document.addEventListener('DOMContentLoaded', function() { // Initialize tooltips var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); // Initialize file upload functionality initializeFileUpload(); // Initialize drag and drop initializeDragAndDrop(); // Add fade-in animation to cards animateCards(); }); function initializeFileUpload() { const fileInput = document.getElementById('noteFile'); const uploadBtn = document.getElementById('upload-btn'); const fileInfo = document.getElementById('file-info'); const fileName = document.getElementById('file-name'); const fileSize = document.getElementById('file-size'); const uploadProgress = document.getElementById('upload-progress'); const uploadResult = document.getElementById('upload-result'); const uploadArea = document.getElementById('upload-area'); if (!fileInput) return; // Remove any existing event listeners to prevent duplicates fileInput.removeEventListener('change', handleFileChange); fileInput.addEventListener('change', handleFileChange); if (uploadBtn) { uploadBtn.removeEventListener('click', handleUploadClick); uploadBtn.addEventListener('click', handleUploadClick); } function handleFileChange(e) { const file = e.target.files[0]; if (file) { showFileInfo(file); uploadBtn.disabled = false; } } function handleUploadClick(e) { e.preventDefault(); uploadFile(); } function showFileInfo(file) { if (fileName && fileSize && fileInfo) { // Define allowed file types for client-side validation const allowedExtensions = ['.pdf', '.txt', '.doc', '.docx', '.xlsx', '.xls', '.md', '.json', '.csv', '.xml']; const allowedMimeTypes = [ 'application/pdf', 'text/plain', 'text/markdown', 'text/csv', 'text/xml', 'application/xml', 'application/json', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ]; // Get file extension const fileExtension = '.' + file.name.split('.').pop().toLowerCase(); const fileMimeType = file.type.toLowerCase(); // Check if file type is allowed const isExtensionAllowed = allowedExtensions.includes(fileExtension); const isMimeTypeAllowed = allowedMimeTypes.includes(fileMimeType) || fileMimeType === ''; if (!isExtensionAllowed) { // Show error for invalid file type fileInfo.innerHTML = `
Invalid file type! "${fileExtension}" files are not supported.
Only document files (PDF, Word, Excel, text files) are allowed to prevent RAG corruption.
`; fileInfo.classList.remove('d-none'); uploadBtn.disabled = true; return; } // Show valid file info fileInfo.innerHTML = `
${file.name} (${formatFileSize(file.size)})
File type supported for AI processing
`; fileInfo.classList.remove('d-none'); uploadBtn.disabled = false; } } function uploadFile() { const file = fileInput.files[0]; if (!file) return; const formData = new FormData(); formData.append('noteFile', file); uploadBtn.disabled = true; uploadProgress.classList.remove('d-none'); uploadResult.innerHTML = ''; fetch('/upload', { method: 'POST', body: formData }) .then(response => response.json()) .then(result => { uploadProgress.classList.add('d-none'); if (result.success) { if (result.processing) { // Show processing status uploadResult.innerHTML = `
Upload Successful! ${result.message}
Processing document chunks for AI analysis...
This may take a few moments depending on document size.
View Dashboard
`; // Start polling for status updates startStatusPolling(result.fileInfo.id); } else { // Immediate success (shouldn't happen with new flow) uploadResult.innerHTML = `
Success! ${result.message}
Revise with AI View Dashboard
`; } // Reset form fileInput.value = ''; fileInfo.classList.add('d-none'); uploadBtn.disabled = true; // Add success animation uploadArea.classList.add('upload-success'); setTimeout(() => { uploadArea.classList.remove('upload-success'); }, 2000); } else { uploadResult.innerHTML = `
Error! ${result.error} ${result.details ? `
${result.details}` : ''}
`; uploadArea.classList.add('upload-error'); setTimeout(() => { uploadArea.classList.remove('upload-error'); }, 2000); } }) .catch(error => { uploadProgress.classList.add('d-none'); uploadResult.innerHTML = `
Error! ${error.message}
`; uploadArea.classList.add('upload-error'); setTimeout(() => { uploadArea.classList.remove('upload-error'); }, 2000); }) .finally(() => { uploadBtn.disabled = false; }); } } function initializeDragAndDrop() { const uploadArea = document.getElementById('upload-area'); const fileInput = document.getElementById('noteFile'); if (!uploadArea || !fileInput) return; uploadArea.addEventListener('click', function(e) { // Only trigger file input if clicking on the area itself, not the button if (e.target === uploadArea || e.target.tagName === 'I' || e.target.tagName === 'H5' || e.target.tagName === 'P') { fileInput.click(); } }); uploadArea.addEventListener('dragover', function(e) { e.preventDefault(); uploadArea.classList.add('dragover'); }); uploadArea.addEventListener('dragleave', function(e) { e.preventDefault(); uploadArea.classList.remove('dragover'); }); uploadArea.addEventListener('drop', function(e) { e.preventDefault(); uploadArea.classList.remove('dragover'); const files = e.dataTransfer.files; if (files.length > 0) { fileInput.files = files; fileInput.dispatchEvent(new Event('change')); } }); } function animateCards() { const cards = document.querySelectorAll('.card'); cards.forEach((card, index) => { card.style.opacity = '0'; card.style.transform = 'translateY(20px)'; setTimeout(() => { card.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; card.style.opacity = '1'; card.style.transform = 'translateY(0)'; }, index * 100); }); } function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // Utility functions function showToast(message, type = 'info') { const toast = document.createElement('div'); toast.className = `toast align-items-center text-bg-${type} border-0`; toast.setAttribute('role', 'alert'); toast.setAttribute('aria-live', 'assertive'); toast.setAttribute('aria-atomic', 'true'); toast.innerHTML = `
${message}
`; // Add to page let toastContainer = document.querySelector('.toast-container'); if (!toastContainer) { toastContainer = document.createElement('div'); toastContainer.className = 'toast-container position-fixed bottom-0 end-0 p-3'; document.body.appendChild(toastContainer); } toastContainer.appendChild(toast); const bsToast = new bootstrap.Toast(toast); bsToast.show(); // Remove after hiding toast.addEventListener('hidden.bs.toast', function() { toast.remove(); }); } function showLoading(element) { element.innerHTML = `
Loading...
Loading...
`; } function hideLoading(element, originalContent) { element.innerHTML = originalContent; } // Error handling window.addEventListener('error', function(e) { console.error('Global error:', e.error); showToast('An unexpected error occurred. Please try again.', 'danger'); }); // Handle unhandled promise rejections window.addEventListener('unhandledrejection', function(e) { console.error('Unhandled promise rejection:', e.reason); showToast('An unexpected error occurred. Please try again.', 'danger'); }); // Add smooth scrolling for anchor links (but not for dropdown triggers) document.querySelectorAll('a[href^="#"]').forEach(anchor => { const href = anchor.getAttribute('href'); // Skip empty anchors and dropdown triggers if (href === '#' || anchor.hasAttribute('data-bs-toggle')) { return; } anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(href); if (target) { target.scrollIntoView({ behavior: 'smooth' }); } }); }); // Add loading states to buttons (but not for login/register/quiz forms) document.querySelectorAll('.btn').forEach(button => { if (button.type === 'submit' || button.hasAttribute('data-loading')) { // Skip login, register, and quiz form buttons const form = button.closest('form'); if (form && (form.action.includes('/login') || form.action.includes('/register') || form.id === 'quizForm')) { return; } button.addEventListener('click', function() { if (!this.disabled) { const originalText = this.innerHTML; this.innerHTML = ` Loading... `; this.disabled = true; // Re-enable after 5 seconds (fallback) setTimeout(() => { this.innerHTML = originalText; this.disabled = false; }, 5000); } }); } }); // Status polling for document processing let statusPollingInterval = null; function startStatusPolling(fileId) { // Clear any existing polling if (statusPollingInterval) { clearInterval(statusPollingInterval); } // Poll every 3 seconds statusPollingInterval = setInterval(() => { checkUploadStatus(fileId, true); }, 3000); } function stopStatusPolling() { if (statusPollingInterval) { clearInterval(statusPollingInterval); statusPollingInterval = null; } } async function checkUploadStatus(fileId, isPolling = false) { try { const response = await fetch(`/api/files/${fileId}/status`); const result = await response.json(); if (result.success) { const file = result.file; const uploadResult = document.getElementById('upload-result'); if (file.status === 'processed') { // Processing completed successfully if (uploadResult) { uploadResult.innerHTML = `
Processing Complete! Your document has been processed and is ready for AI analysis.
${file.processingResult ? `${file.processingResult.successfulChunks}/${file.processingResult.totalChunks} chunks processed` : 'Processed successfully'}
${file.processingResult && file.processingResult.processedAt ? new Date(file.processingResult.processedAt).toLocaleTimeString() : 'Just now'}
Revise with AI View Dashboard
`; } stopStatusPolling(); } else if (file.status === 'failed') { // Processing failed if (uploadResult) { uploadResult.innerHTML = `
Processing Failed! There was an error processing your document. ${file.processingError ? `
Error: ${file.processingError}` : ''}
View Dashboard
`; } stopStatusPolling(); } else if (file.status === 'processing' && !isPolling) { // Still processing, manual check let progressInfo = ''; if (file.processingResult) { progressInfo = `
Progress: ${file.processingResult.successfulChunks || 0}/${file.processingResult.totalChunks || '?'} chunks`; } if (uploadResult) { uploadResult.innerHTML = `
Still Processing... Your document is being processed for AI analysis. ${progressInfo}
View Dashboard
`; } // Start polling if not already polling if (!statusPollingInterval) { startStatusPolling(fileId); } } } else { console.error('Error checking status:', result.error); if (!isPolling) { alert('Error checking status: ' + result.error); } } } catch (error) { console.error('Error checking upload status:', error); if (!isPolling) { alert('Error checking upload status: ' + error.message); } } } async function retryDocumentProcessing(fileId) { try { const response = await fetch(`/api/files/${fileId}/retry`, { method: 'POST', headers: { 'Content-Type': 'application/json', } }); const result = await response.json(); if (result.success) { // Update UI to show retry started const uploadResult = document.getElementById('upload-result'); if (uploadResult) { uploadResult.innerHTML = `
Retry Started! Processing your document again...
View Dashboard
`; } // Start polling again startStatusPolling(fileId); } else { alert('Error retrying processing: ' + result.error); } } catch (error) { console.error('Error retrying processing:', error); alert('Error retrying processing: ' + error.message); } } // Clean up polling when page unloads window.addEventListener('beforeunload', function() { stopStatusPolling(); });