diff --git a/public/css/style.css b/public/css/style.css index 5b66cff..9a91bdc 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -156,8 +156,72 @@ body { } } -.file-icon { - font-size: 1.2rem; +.dashboard-file-icon { + font-size: 1.25rem !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + min-width: 50px !important; + width: 50px !important; + height: 50px !important; + flex-shrink: 0 !important; +} + +.dashboard-file-icon i { + font-size: 1.25rem !important; + line-height: 1 !important; + margin: 0 !important; + padding: 0 !important; + vertical-align: middle !important; +} + +/* Force proper icon rendering to prevent compression */ +.revised-files-section .file-icon { + display: flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 50% !important; + overflow: hidden; + position: relative; +} + +.revised-files-section .file-icon::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 50%; + background: inherit; + z-index: -1; +} + +/* AI text icon styling */ +.revised-files-section .ai-icon { + background: linear-gradient(135deg, #28a745 0%, #20c997 100%) !important; + color: white !important; + font-weight: bold !important; + font-size: 1.2rem !important; + letter-spacing: 1px !important; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) !important; + border: 2px solid rgba(255, 255, 255, 0.2) !important; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; +} + +/* Ensure icon font doesn't get compressed */ +.revised-files-section .fa-brain { + transform: none !important; + font-weight: 900 !important; + font-family: "Font Awesome 5 Free" !important; + vertical-align: middle !important; + display: inline-block !important; + font-style: normal !important; + font-variant: normal !important; + text-rendering: auto !important; + line-height: 1 !important; + margin: 0 !important; + padding: 0 !important; } .progress-bar-animated { @@ -190,50 +254,203 @@ body { margin-top: auto; } -/* Responsive adjustments */ +/* Revised Files Section Styling */ +.revised-files-section .card { + transition: all 0.3s ease; +} + +.revised-files-section .card:hover { + transform: translateY(-2px); + box-shadow: 0 0.75rem 2rem rgba(0, 0, 0, 0.15) !important; +} + +.revised-files-section .file-icon { + min-width: 50px !important; + width: 50px !important; + height: 50px !important; + flex-shrink: 0 !important; + font-size: 1.25rem !important; + line-height: 1 !important; +} + +.revised-files-section .file-icon i { + font-size: 1.25rem !important; + line-height: 1 !important; +} + +.revised-files-section .text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.revised-files-section .min-w-0 { + min-width: 0; +} + +.revised-files-section .card-body { + overflow: hidden; +} + +.revised-files-section .btn-group { + width: 100%; +} + +.revised-files-section .btn-group .btn { + flex: 1; +} + +.revised-files-section .btn-group .btn:last-child { + flex: 0 0 auto; + min-width: 40px; +} + +/* File name truncation for long names */ +.file-name-truncate { + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: inline-block; + vertical-align: bottom; +} + +/* Badge styling improvements */ +.revised-files-section .badge { + font-size: 0.7rem !important; + padding: 0.25em 0.5em; + white-space: nowrap; +} + +/* Button spacing and sizing improvements */ +.revised-files-section .d-grid .btn { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Ensure cards have consistent height */ +.revised-files-section .card { + height: 100%; + display: flex; + flex-direction: column; +} + +.revised-files-section .card-body { + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +/* Responsive adjustments for small screens */ @media (max-width: 768px) { - .hero-section { - min-height: 50vh; - text-align: center; + .revised-files-section .file-name-truncate { + max-width: 150px; } - .hero-section .display-4 { - font-size: 2rem; - } - - .message-content { - max-width: 85% !important; - } - - .avatar { - width: 40px !important; - height: 40px !important; - min-width: 40px !important; - } - - .avatar i { + .revised-files-section .file-icon { + width: 45px !important; + height: 45px !important; + min-width: 45px !important; font-size: 1rem !important; } - .message-bubble { - padding: 0.75rem !important; - font-size: 0.9rem; + .revised-files-section .ai-icon { + font-size: 1rem !important; + letter-spacing: 0.5px !important; } - .card-body { - padding: 1.5rem; + .revised-files-section .file-icon i { + font-size: 1.1rem !important; } - #chat-container { - height: 400px !important; - padding: 1rem !important; + .revised-files-section .btn-group .btn { + font-size: 0.8rem; + padding: 0.375rem 0.5rem; + } +} + +/* Mobile responsive file icons */ +@media (max-width: 768px) { + .dashboard-file-icon { + min-width: 45px !important; + width: 45px !important; + height: 45px !important; + font-size: 1.1rem !important; } - .chat-message { - margin-bottom: 1rem; + .dashboard-file-icon i { + font-size: 1.1rem !important; } } +/* Dashboard section spacing */ +.dashboard-section { + margin-bottom: 2rem; +} + +.dashboard-section h3 { + font-weight: 600; + color: var(--dark-color); +} + +/* Enhanced visual separation between sections */ +.ai-revised-section { + background: linear-gradient(135deg, rgba(40, 167, 69, 0.05) 0%, rgba(40, 167, 69, 0.02) 100%); + border-radius: 1rem; + padding: 1.5rem; + margin-top: 2rem; +} + +/* Badge positioning fixes for dashboard cards */ +.card .badge { + position: relative; + z-index: 1; + display: inline-flex !important; + align-items: center !important; + white-space: nowrap !important; +} + +.card .text-end { + text-align: right !important; + position: relative; + z-index: 1; +} + +/* Ensure card body contains all content properly */ +.card-body { + position: relative; + overflow: hidden; + padding: 1rem !important; +} + +/* Prevent badge overflow */ +.card .d-flex .text-end { + flex-shrink: 0; + min-width: fit-content; +} + +.card .d-flex .flex-grow-1 { + overflow: hidden; + min-width: 0; +} + +/* Dashboard card container fixes */ +.dashboard-section .card { + position: relative; + overflow: visible; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.375rem; +} + +.dashboard-section .card-body { + display: flex; + flex-direction: column; + height: 100%; + position: relative; +} + /* Loading spinner */ .spinner-border { width: 3rem; diff --git a/server.js b/server.js index c002880..61f5622 100644 --- a/server.js +++ b/server.js @@ -868,13 +868,41 @@ app.get('/revise/:fileId', requireAuth, async (req, res) => { }); } - // Read file content - const fileContent = await fs.readFile(file.path, 'utf-8'); + const filePath = path.join(__dirname, file.path); + const fileExtension = path.extname(file.originalName).toLowerCase(); + + // Extract text from the document using the same method as file preview + const extractionResult = await extractTextFromDocument(filePath, fileExtension); + + let fileContent = ''; + let extractionInfo = null; + let extractionError = null; + + if (extractionResult.success) { + fileContent = extractionResult.text; + extractionInfo = { + method: extractionResult.method, + totalLength: extractionResult.extractedLength, + pages: extractionResult.pages || null, + sheets: extractionResult.sheets || null + }; + } else { + // Try to read as plain text if extraction failed + try { + fileContent = await fs.readFile(filePath, 'utf-8'); + extractionInfo = { method: 'fallback-text', totalLength: fileContent.length }; + } catch (readError) { + extractionError = `Failed to extract or read file: ${extractionResult.error}`; + fileContent = `Error: Unable to read file content. ${extractionResult.error}`; + } + } res.render('revise', { title: 'Revise Notes - EduCat', file: file, - content: fileContent + content: fileContent, + extractionInfo: extractionInfo, + extractionError: extractionError }); } catch (error) { console.error('Error loading file:', error); @@ -899,10 +927,9 @@ app.post('/api/revise', requireAuth, async (req, res) => { if (await fs.pathExists(documentPath)) { const documentData = await fs.readJSON(documentPath); contextContent = documentData.content; - } } catch (error) { - + console.log('Could not load stored document, using provided content'); } } @@ -932,13 +959,236 @@ app.post('/api/revise', requireAuth, async (req, res) => { }); } catch (error) { console.error('Revision error:', error); - res.status(500).json({ - error: 'Failed to revise notes', + res.json({ + success: false, + error: 'Failed to revise notes. Please try again.', details: error.message }); } }); +// Save revised notes endpoint +app.post('/api/save-revised', requireAuth, async (req, res) => { + try { + const { fileId, revisedContent, revisionType } = req.body; + + if (!fileId || !revisedContent) { + return res.json({ + success: false, + error: 'File ID and revised content are required' + }); + } + + // Find the original file + const files = req.session.uploadedFiles || []; + const originalFile = files.find(f => f.id === fileId); + + if (!originalFile) { + return res.json({ + success: false, + error: 'Original file not found' + }); + } + + // Create revised notes directory if it doesn't exist + const revisedNotesDir = path.join(__dirname, 'uploads', 'revised-notes'); + await fs.ensureDir(revisedNotesDir); + + // Create revised file info + const timestamp = Date.now(); + const originalExt = path.extname(originalFile.originalName); + const fileName = `${path.parse(originalFile.originalName).name}_${revisionType}_revised_${timestamp}.txt`; + const filePath = path.join(revisedNotesDir, fileName); + + const revisedFileInfo = { + id: uuidv4(), + originalName: fileName, + filename: fileName, + path: filePath, + size: Buffer.byteLength(revisedContent, 'utf8'), + mimetype: 'text/plain', + uploadDate: new Date().toISOString(), + userId: req.session.userId, + status: 'processed', + isRevised: true, + originalFileId: fileId, + revisionType: revisionType, + originalFileName: originalFile.originalName + }; + + // Save revised content to file + await fs.writeFile(revisedFileInfo.path, revisedContent, 'utf8'); + + // Don't add revised files to regular uploadedFiles - store separately + if (!req.session.revisedFiles) { + req.session.revisedFiles = []; + } + req.session.revisedFiles.push(revisedFileInfo); + + // Save to persistent storage in a separate category + await saveRevisedFile(req.session.userId, revisedFileInfo); + + res.json({ + success: true, + message: 'Revised notes saved successfully!', + fileInfo: revisedFileInfo + }); + + } catch (error) { + console.error('Save revised notes error:', error); + res.json({ + success: false, + error: 'Failed to save revised notes', + details: error.message + }); + } +}); + +// Download revised notes endpoint +app.get('/api/download-revised/:fileId', requireAuth, async (req, res) => { + try { + const fileId = req.params.fileId; + const { content } = req.query; + + if (!content) { + return res.status(400).json({ + success: false, + error: 'No content provided for download' + }); + } + + // Find the original file to get naming info + const files = req.session.uploadedFiles || []; + const originalFile = files.find(f => f.id === fileId); + + // Get the revision type from the query if available + const revisionType = req.query.revisionType || 'revised'; + + const fileName = originalFile + ? `${path.parse(originalFile.originalName).name}_${revisionType}.txt` + : `revised_notes_${Date.now()}.txt`; + + // Set headers for file download + res.setHeader('Content-Type', 'text/plain; charset=utf-8'); + res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`); + res.setHeader('Content-Length', Buffer.byteLength(content, 'utf8')); + + // Send the content + res.send(content); + + } catch (error) { + console.error('Download revised notes error:', error); + res.status(500).json({ + success: false, + error: 'Failed to download revised notes', + details: error.message + }); + } +}); + +// Serve revised files for direct download +app.get('/uploads/revised-notes/:filename', requireAuth, (req, res) => { + try { + const filename = req.params.filename; + const filePath = path.join(__dirname, 'uploads', 'revised-notes', filename); + + // Check if file exists + if (!fs.existsSync(filePath)) { + return res.status(404).json({ + success: false, + error: 'File not found' + }); + } + + // Serve the file + res.download(filePath, filename); + } catch (error) { + console.error('Error serving revised file:', error); + res.status(500).json({ + success: false, + error: 'Failed to serve file' + }); + } +}); + +// Delete revised file endpoint +app.delete('/api/revised-files/:fileId', requireAuth, async (req, res) => { + try { + const fileId = req.params.fileId; + const revisedFiles = req.session.revisedFiles || []; + const fileIndex = revisedFiles.findIndex(f => f.id === fileId); + + if (fileIndex === -1) { + return res.status(404).json({ success: false, error: 'File not found' }); + } + + const file = revisedFiles[fileIndex]; + + // Delete the physical file + if (await fs.pathExists(file.path)) { + await fs.unlink(file.path); + } + + // Remove from session + req.session.revisedFiles.splice(fileIndex, 1); + + // Remove from persistent storage + await removeRevisedFile(req.session.userId, fileId); + + res.json({ + success: true, + message: 'Revised file deleted successfully' + }); + } catch (error) { + console.error('Error deleting revised file:', error); + res.status(500).json({ + success: false, + error: 'Failed to delete file', + details: error.message + }); + } +}); + +// Get revised file info endpoint +app.get('/api/revised-files/:fileId/info', requireAuth, async (req, res) => { + try { + const fileId = req.params.fileId; + const revisedFiles = req.session.revisedFiles || []; + const file = revisedFiles.find(f => f.id === fileId); + + if (!file) { + // Try to load from persistent storage + const persistentRevisedFiles = await loadRevisedFiles(req.session.userId); + const persistentFile = persistentRevisedFiles.find(f => f.id === fileId); + + if (!persistentFile) { + return res.status(404).json({ + success: false, + error: 'File not found' + }); + } + + return res.json({ + success: true, + file: persistentFile + }); + } + + res.json({ + success: true, + file: file + }); + } catch (error) { + console.error('Error getting revised file info:', error); + res.status(500).json({ + success: false, + error: 'Failed to get file info', + details: error.message + }); + } +}); + +// ChatGPT integration routes app.get('/chat', requireAuth, (req, res) => { // Initialize chat history if it doesn't exist if (!req.session.chatHistory) { @@ -1089,6 +1339,9 @@ app.get('/dashboard', requireAuth, async (req, res) => { // Load persistent files for this user const persistentFiles = await loadUserFiles(req.session.userId); + // Load revised files separately + const revisedFiles = await loadRevisedFiles(req.session.userId); + // Merge with session files (in case there are newly uploaded files not yet saved) const sessionFiles = req.session.uploadedFiles || []; const allFiles = [...persistentFiles]; @@ -1100,18 +1353,30 @@ app.get('/dashboard', requireAuth, async (req, res) => { } }); + // Merge revised files from session + const sessionRevisedFiles = req.session.revisedFiles || []; + const allRevisedFiles = [...revisedFiles]; + sessionRevisedFiles.forEach(sessionFile => { + if (!revisedFiles.find(f => f.id === sessionFile.id)) { + allRevisedFiles.push(sessionFile); + } + }); + // Update session with merged files for current session use req.session.uploadedFiles = allFiles; + req.session.revisedFiles = allRevisedFiles; res.render('dashboard', { title: 'Dashboard - EduCat', - files: allFiles + files: allFiles, + revisedFiles: allRevisedFiles }); } catch (error) { console.error('Error loading dashboard:', error); res.render('dashboard', { title: 'Dashboard - EduCat', - files: req.session.uploadedFiles || [] + files: req.session.uploadedFiles || [], + revisedFiles: [] }); } }); @@ -1454,6 +1719,7 @@ app.post('/api/generate-quiz', requireAuth, async (req, res) => { { "question": "What is the process by which plants make their own food?", "answer": "Photosynthesis", + "explanation": "Photosynthesis is the process by which plants use sunlight, water, and carbon dioxide to produce glucose and oxygen.", "keywords": ["photosynthesis", "sunlight", "chlorophyll"] } ] @@ -1615,13 +1881,15 @@ app.post('/api/submit-quiz', requireAuth, async (req, res) => { quiz.forEach((question, index) => { const userAnswer = answers[index]; - const isCorrect = userAnswer === question.correct; + // Handle different question types - short-answer uses 'answer', others use 'correct' + const correctAnswer = question.answer || question.correct; + const isCorrect = userAnswer === correctAnswer; if (isCorrect) score++; results.push({ question: question.question, userAnswer: userAnswer, - correctAnswer: question.correct, + correctAnswer: correctAnswer, isCorrect: isCorrect, explanation: question.explanation || '', options: question.options || null // Include options for multiple choice @@ -1732,53 +2000,128 @@ async function removeUserFile(userId, fileId) { await saveUserFiles(userId, filteredFiles); } -// Quiz results persistence -const QUIZ_RESULTS_FILE = path.join(__dirname, 'data', 'quiz-results.json'); +// Revised files storage persistence +const REVISED_FILES_DIR = path.join(__dirname, 'data', 'revised-files'); -// Ensure data directory exists -async function ensureDataDirectory() { - await fs.ensureDir(path.join(__dirname, 'data')); +// Ensure revised files directory exists +async function ensureRevisedFilesDirectory() { + await fs.ensureDir(REVISED_FILES_DIR); } -// Load quiz results from file +// Save revised files to persistent storage +async function saveRevisedFiles(userId, files) { + try { + await ensureRevisedFilesDirectory(); + const revisedFilePath = path.join(REVISED_FILES_DIR, `user-${userId}-revised.json`); + await fs.writeJSON(revisedFilePath, files, { spaces: 2 }); + } catch (error) { + console.error('Error saving revised files:', error); + } +} + +// Load revised files from persistent storage +async function loadRevisedFiles(userId) { + try { + await ensureRevisedFilesDirectory(); + const revisedFilePath = path.join(REVISED_FILES_DIR, `user-${userId}-revised.json`); + if (await fs.pathExists(revisedFilePath)) { + const files = await fs.readJSON(revisedFilePath); + return files || []; + } + return []; + } catch (error) { + console.error('Error loading revised files:', error); + return []; + } +} + +// Add a revised file to user's persistent storage +async function saveRevisedFile(userId, fileInfo) { + const revisedFiles = await loadRevisedFiles(userId); + revisedFiles.push(fileInfo); + await saveRevisedFiles(userId, revisedFiles); +} + +// Remove a revised file from user's persistent storage +async function removeRevisedFile(userId, fileId) { + const revisedFiles = await loadRevisedFiles(userId); + const filteredFiles = revisedFiles.filter(f => f.id !== fileId); + await saveRevisedFiles(userId, filteredFiles); +} + +// Quiz results storage persistence +const QUIZ_RESULTS_DIR = path.join(__dirname, 'data', 'quiz-results'); + +// Ensure quiz results directory exists +async function ensureQuizResultsDirectory() { + await fs.ensureDir(QUIZ_RESULTS_DIR); +} + +// Save quiz results to persistent storage +async function saveQuizResults(allResults) { + try { + await ensureQuizResultsDirectory(); + const resultsPath = path.join(QUIZ_RESULTS_DIR, 'quiz-results.json'); + await fs.writeJSON(resultsPath, allResults, { spaces: 2 }); + } catch (error) { + console.error('Error saving quiz results:', error); + throw error; + } +} + +// Load all quiz results from persistent storage async function loadQuizResults() { try { - await ensureDataDirectory(); - if (await fs.pathExists(QUIZ_RESULTS_FILE)) { - const data = await fs.readJSON(QUIZ_RESULTS_FILE); - return data || {}; + await ensureQuizResultsDirectory(); + const resultsPath = path.join(QUIZ_RESULTS_DIR, 'quiz-results.json'); + + if (await fs.pathExists(resultsPath)) { + return await fs.readJSON(resultsPath); + } else { + return {}; } - return {}; } catch (error) { console.error('Error loading quiz results:', error); return {}; } } -// Save quiz results to file -async function saveQuizResults(results) { - try { - await ensureDataDirectory(); - await fs.writeJSON(QUIZ_RESULTS_FILE, results, { spaces: 2 }); - } catch (error) { - console.error('Error saving quiz results:', error); - } -} - -// Add quiz result for user -async function addQuizResult(userId, quizResult) { - const allResults = await loadQuizResults(); - if (!allResults[userId]) { - allResults[userId] = []; - } - allResults[userId].push(quizResult); - await saveQuizResults(allResults); -} - -// Get quiz results for user +// Get quiz results for a specific user async function getUserQuizResults(userId) { - const allResults = await loadQuizResults(); - return allResults[userId] || []; + try { + const allResults = await loadQuizResults(); + return allResults[userId] || []; + } catch (error) { + console.error('Error loading user quiz results:', error); + return []; + } +} + +// Add a quiz result to user's persistent storage +async function addQuizResult(userId, quizResult) { + try { + const allResults = await loadQuizResults(); + if (!allResults[userId]) { + allResults[userId] = []; + } + allResults[userId].push(quizResult); + await saveQuizResults(allResults); + } catch (error) { + console.error('Error adding quiz result:', error); + throw error; + } +} + +// Clear quiz results for a specific user +async function clearUserQuizResults(userId) { + try { + const allResults = await loadQuizResults(); + allResults[userId] = []; + await saveQuizResults(allResults); + } catch (error) { + console.error('Error clearing user quiz results:', error); + throw error; + } } // Error handling middleware @@ -1814,11 +2157,9 @@ app.get('/quiz-history', requireAuth, async (req, res) => { app.delete('/api/quiz-history', requireAuth, async (req, res) => { try { const userId = req.session.userId; - const allResults = await loadQuizResults(); - // Clear quiz history for this user - allResults[userId] = []; - await saveQuizResults(allResults); + // Clear quiz history for this user using the dedicated function + await clearUserQuizResults(userId); diff --git a/views/dashboard.ejs b/views/dashboard.ejs index 31e1cf1..adc90e9 100644 --- a/views/dashboard.ejs +++ b/views/dashboard.ejs @@ -31,7 +31,7 @@
-
+
@@ -109,6 +109,73 @@ <% }); %>
<% } %> + + + <% if (typeof revisedFiles !== 'undefined' && revisedFiles.length > 0) { %> +
+
+

AI-Revised Notes

+ + <%= revisedFiles.length %> revised file<%= revisedFiles.length !== 1 ? 's' : '' %> + +
+
+ <% revisedFiles.forEach(function(file, index) { %> +
+
+
+
+
+ AI +
+
+
+ <%= file.originalName %> +
+
+ <%= Math.round(file.size / 1024) %> KB + + <%= file.revisionType.charAt(0).toUpperCase() + file.revisionType.slice(1) %> + +
+
+
+ +
+
+ + <%= new Date(file.uploadDate).toLocaleDateString() %> +
+ <% if (file.originalFileName) { %> +
+ + From: <%= file.originalFileName %> +
+ <% } %> +
+ +
+
+ +
+ + Download + + +
+
+
+
+
+
+ <% }); %> +
+
+ <% } %>
@@ -806,6 +873,73 @@ document.addEventListener('DOMContentLoaded', function() { }, 10000); } }); + +// Preview revised file function +async function previewRevisedFile(fileId) { + try { + // Get file information by making an API call instead of using template data + const response = await fetch(`/api/revised-files/${fileId}/info`); + if (!response.ok) { + throw new Error('Failed to get file info'); + } + + const fileInfo = await response.json(); + if (!fileInfo.success) { + throw new Error(fileInfo.error || 'Failed to get file info'); + } + + const file = fileInfo.file; + + // Download the file content + const contentResponse = await fetch(`/uploads/revised-notes/${file.filename}`); + if (!contentResponse.ok) { + throw new Error('Failed to load file content'); + } + + const content = await contentResponse.text(); + const modal = new bootstrap.Modal(document.getElementById('previewModal')); + + document.getElementById('preview-content').innerHTML = ` +
+ + AI-Revised Content • ${file.revisionType} • From: ${file.originalFileName || 'Unknown'} +
+
+
${escapeHtml(content)}
+
+ `; + + modal.show(); + } catch (error) { + console.error('Error previewing revised file:', error); + alert('Failed to preview file: ' + error.message); + } +} + +// Delete revised file function +async function deleteRevisedFile(fileId) { + if (!confirm('Are you sure you want to delete this revised file? This action cannot be undone.')) { + return; + } + + try { + const response = await fetch(`/api/revised-files/${fileId}`, { + method: 'DELETE' + }); + + const result = await response.json(); + + if (result.success) { + // Reload the page to update the file list + location.reload(); + } else { + alert('Error deleting file: ' + (result.error || 'Unknown error')); + } + } catch (error) { + console.error('Error deleting revised file:', error); + alert('Network error: ' + error.message); + } +} <%- include('partials/footer') %> diff --git a/views/quiz.ejs b/views/quiz.ejs index e6a1ec9..fa2fde8 100644 --- a/views/quiz.ejs +++ b/views/quiz.ejs @@ -377,8 +377,8 @@ document.addEventListener('DOMContentLoaded', function() { `; }); html += '
'; - } else if (question.correct === 'True' || question.correct === 'False') { - // True/False + } else if ((question.correct === 'True' || question.correct === 'False') && !question.answer) { + // True/False (only if it's not a short-answer question) html += `
@@ -766,8 +766,8 @@ document.addEventListener('DOMContentLoaded', function() { `; }); html += '
'; - } else if (question.correct === 'True' || question.correct === 'False') { - // True/False + } else if ((question.correct === 'True' || question.correct === 'False') && !question.answer) { + // True/False (only if it's not a short-answer question) const userSelected = userAnswers[index]; const correctAnswer = question.correct; @@ -803,7 +803,7 @@ document.addEventListener('DOMContentLoaded', function() { } else { // Short answer const userAnswer = userAnswers[index] || ''; - const correctAnswer = question.correct || ''; + const correctAnswer = question.answer || question.correct || ''; html += `
diff --git a/views/revise.ejs b/views/revise.ejs index b0d16d7..a6c89d0 100644 --- a/views/revise.ejs +++ b/views/revise.ejs @@ -8,11 +8,30 @@

Revise: <%= file.originalName %>

+ <% if (extractionError) { %> +
+ + <%= extractionError %> +
+ <% } else if (extractionInfo) { %> +
+ + Text extracted using <%= extractionInfo.method %> + <% if (extractionInfo.pages) { %> + | <%= extractionInfo.pages %> pages + <% } %> + <% if (extractionInfo.sheets) { %> + | <%= extractionInfo.sheets %> sheets + <% } %> + | <%= extractionInfo.totalLength %> characters +
+ <% } %> +
Original Notes
-
<%= content %>
+
<%= content %>
@@ -58,7 +77,32 @@
  • Name: <%= file.originalName %>
  • Size: <%= Math.round(file.size / 1024) %> KB
  • Uploaded: <%= new Date(file.uploadDate).toLocaleDateString() %>
  • + <% if (extractionInfo) { %> +
  • Extraction: <%= extractionInfo.method %>
  • + <% if (extractionInfo.pages) { %> +
  • Pages: <%= extractionInfo.pages %>
  • + <% } %> + <% if (extractionInfo.sheets) { %> +
  • Sheets: <%= extractionInfo.sheets %>
  • + <% } %> +
  • Characters: <%= extractionInfo.totalLength.toLocaleString() %>
  • + <% } %> + + <% if (file.status) { %> +
    + Processing Status: + <% if (file.status === 'processed') { %> + Processed + <% } else if (file.status === 'processing') { %> + Processing + <% } else if (file.status === 'failed') { %> + Failed + <% } else { %> + <%= file.status %> + <% } %> +
    + <% } %>
    @@ -96,15 +140,20 @@ document.addEventListener('DOMContentLoaded', function() { const saveBtn = document.getElementById('save-btn'); const downloadBtn = document.getElementById('download-btn'); + const fileId = '<%= file.id %>'; + const content = <%- JSON.stringify(content) %>; + let currentRevisedContent = ''; + let currentRevisionType = ''; + reviseBtn.addEventListener('click', async function() { const type = revisionType.value; - const content = `<%= content.replace(/"/g, '\\"').replace(/\n/g, '\\n') %>`; console.log('Revise button clicked with type:', type); + console.log('File ID:', fileId); reviseBtn.disabled = true; revisionProgress.classList.remove('d-none'); - revisedContent.innerHTML = '

    Processing...

    '; + revisedContent.innerHTML = '
    Processing...

    AI is processing your notes...

    '; try { console.log('Sending request to /api/revise'); @@ -115,34 +164,114 @@ document.addEventListener('DOMContentLoaded', function() { }, body: JSON.stringify({ content: content, - revisionType: type + revisionType: type, + fileId: fileId }) }); console.log('Response status:', response.status); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const result = await response.json(); console.log('Revision result:', result); if (result.success) { - revisedContent.innerHTML = '
    ' + result.revisedContent + '
    '; + revisedContent.innerHTML = '
    ' + escapeHtml(result.revisedContent) + '
    '; + currentRevisedContent = result.revisedContent; + currentRevisionType = type; saveBtn.disabled = false; downloadBtn.disabled = false; } else { - revisedContent.innerHTML = '
    Error: ' + (result.error || 'Unknown error') + '
    '; + revisedContent.innerHTML = '
    Error: ' + escapeHtml(result.error || 'Unknown error occurred') + '
    '; } } catch (error) { console.error('Revision error:', error); - revisedContent.innerHTML = '
    Error: ' + error.message + '
    '; + revisedContent.innerHTML = '
    Network Error: ' + escapeHtml(error.message) + '
    Please check your connection and try again.
    '; } finally { reviseBtn.disabled = false; revisionProgress.classList.add('d-none'); } }); + + // Save revised notes button + saveBtn.addEventListener('click', async function() { + if (!currentRevisedContent) { + alert('No revised content to save. Please revise the notes first.'); + return; + } + + saveBtn.disabled = true; + const originalText = saveBtn.innerHTML; + saveBtn.innerHTML = 'Saving...'; + + try { + const response = await fetch('/api/save-revised', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + fileId: fileId, + revisedContent: currentRevisedContent, + revisionType: currentRevisionType + }) + }); + + const result = await response.json(); + + if (result.success) { + // Show success message + const successAlert = document.createElement('div'); + successAlert.className = 'alert alert-success alert-dismissible fade show mt-3'; + successAlert.innerHTML = ` + + Success! Revised notes saved as "${result.fileInfo.originalName}" + + `; + document.querySelector('.card-body').appendChild(successAlert); + + // Auto-hide after 5 seconds + setTimeout(() => { + successAlert.remove(); + }, 5000); + } else { + alert('Error saving revised notes: ' + (result.error || 'Unknown error')); + } + } catch (error) { + console.error('Save error:', error); + alert('Network error while saving. Please try again.'); + } finally { + saveBtn.disabled = false; + saveBtn.innerHTML = originalText; + } + }); + + // Download revised notes button + downloadBtn.addEventListener('click', function() { + if (!currentRevisedContent) { + alert('No revised content to download. Please revise the notes first.'); + return; + } + + // Create download URL with content and revision type as query parameters + const encodedContent = encodeURIComponent(currentRevisedContent); + const encodedRevisionType = encodeURIComponent(currentRevisionType); + const downloadUrl = `/api/download-revised/${fileId}?content=${encodedContent}&revisionType=${encodedRevisionType}`; + + // Create temporary link and trigger download + const link = document.createElement('a'); + link.href = downloadUrl; + link.style.display = 'none'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }); + + // Helper function to escape HTML + function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } });