Improve dashboard UI and some fixes

This commit is contained in:
inubimambo
2025-07-08 23:14:31 +08:00
parent 9644f62dc5
commit bcd5a52995
5 changed files with 918 additions and 97 deletions

439
server.js
View File

@@ -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);