Compare commits

..

2 Commits

181
server.js
View File

@@ -18,7 +18,6 @@ const pdfParse = require('pdf-parse'); // For PDF files
const ExcelJS = require('exceljs'); // For Excel files const ExcelJS = require('exceljs'); // For Excel files
// Markdown and HTML processing // Markdown and HTML processing
const { marked } = require('marked');
const createDOMPurify = require('dompurify'); const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom'); const { JSDOM } = require('jsdom');
@@ -26,6 +25,16 @@ const { JSDOM } = require('jsdom');
const window = new JSDOM('').window; const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window); const DOMPurify = createDOMPurify(window);
// Dynamic import for marked (ES module)
let marked = null;
async function initializeMarked() {
if (!marked) {
const markedModule = await import('marked');
marked = markedModule.marked;
}
return marked;
}
// Helper function to extract text from various document formats // Helper function to extract text from various document formats
async function extractTextFromDocument(filePath, fileExtension) { async function extractTextFromDocument(filePath, fileExtension) {
try { try {
@@ -152,10 +161,16 @@ app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public')); app.use(express.static('public'));
app.use(session({ app.use(session({
secret: process.env.SESSION_SECRET || 'educat-secret-key', secret: process.env.SESSION_SECRET || 'educat-secret-key-2025',
resave: false, resave: true,
saveUninitialized: false, saveUninitialized: true,
cookie: { secure: false, maxAge: 24 * 60 * 60 * 1000 } // 24 hours rolling: true,
cookie: {
secure: false,
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days instead of 1 day
httpOnly: true
},
name: 'educat.session.id' // Custom session name
})); }));
app.use(flash()); app.use(flash());
@@ -768,23 +783,24 @@ app.get('/dashboard', requireAuth, async (req, res) => {
// Chat route // Chat route
app.get('/chat', requireAuth, async (req, res) => { app.get('/chat', requireAuth, async (req, res) => {
try { try {
// Load user chat history // Initialize chat session ID if it doesn't exist
let chatHistory = []; if (!req.session.chatSessionId) {
try { req.session.chatSessionId = `educat-${req.session.userId}-${Date.now()}`;
chatHistory = await loadChatHistory(req.session.userId); }
} catch (error) {
console.error('Error loading chat history:', error); // Initialize chat history in session if it doesn't exist
chatHistory = []; if (!req.session.chatHistory) {
req.session.chatHistory = [];
} }
res.render('chat', { res.render('chat', {
title: 'AI Chat - EduCat', title: 'Chat with EduCat AI',
chatHistory: chatHistory chatHistory: req.session.chatHistory
}); });
} catch (error) { } catch (error) {
console.error('Chat route error:', error); console.error('Chat route error:', error);
res.render('chat', { res.render('chat', {
title: 'AI Chat - EduCat', title: 'Chat with EduCat AI',
chatHistory: [] chatHistory: []
}); });
} }
@@ -1092,41 +1108,54 @@ app.post('/api/chat', requireAuth, async (req, res) => {
}); });
} }
// Load existing chat history from storage // Initialize chat history in session if it doesn't exist
let existingHistory = []; if (!req.session.chatHistory) {
try { req.session.chatHistory = [];
existingHistory = await loadChatHistory(req.session.userId);
} catch (error) {
console.log('No existing chat history found, starting fresh');
} }
// Prepare history for API call (last 10 conversations) // Initialize or get persistent chat session ID for this user
const recentHistory = existingHistory.slice(-10).map(conv => [ if (!req.session.chatSessionId) {
{ role: 'human', content: conv.human }, req.session.chatSessionId = `${req.session.userId}-${Date.now()}`;
{ role: 'ai', content: conv.ai } }
]).flat();
// Prepare the request payload for Flowise with sessionId and chatId
// Call Flowise API for chat const flowisePayload = {
const response = await axios.post(`${FLOWISE_API_URL}/${FLOWISE_CHATFLOW_ID}`, { question: message,
question: message.trim(), history: req.session.chatHistory,
history: recentHistory sessionId: req.session.chatSessionId
});
const botResponse = response.data.text || response.data.answer || 'Sorry, I could not process your request.';
// Save the conversation to history
const conversation = {
human: message.trim(),
ai: botResponse,
timestamp: new Date().toISOString()
}; };
existingHistory.push(conversation); // Add chatId if we have one from previous conversations
await saveChatHistory(req.session.userId, existingHistory); if (req.session.chatId) {
flowisePayload.chatId = req.session.chatId;
}
// Call Flowise API for chat with session history and sessionId
const response = await axios.post(`${FLOWISE_API_URL}/${FLOWISE_CHATFLOW_ID}`, flowisePayload);
const aiResponse = response.data.text || response.data.answer || 'No response received';
// Save the chatId from Flowise response for future requests
if (response.data.chatId) {
req.session.chatId = response.data.chatId;
}
// Add the conversation to session history
req.session.chatHistory.push({
human: message,
ai: aiResponse,
timestamp: new Date().toISOString()
});
// Save session
req.session.save((err) => {
if (err) {
console.error('Error saving session:', err);
}
});
res.json({ res.json({
success: true, success: true,
response: botResponse response: aiResponse
}); });
} catch (error) { } catch (error) {
console.error('Chat error:', error); console.error('Chat error:', error);
@@ -1138,13 +1167,47 @@ app.post('/api/chat', requireAuth, async (req, res) => {
} }
}); });
// Delete chat history endpoint // Get chat history endpoint
app.delete('/api/chat/history', requireAuth, async (req, res) => { app.get('/api/chat/history', requireAuth, (req, res) => {
try { try {
await clearChatHistory(req.session.userId); const chatHistory = req.session.chatHistory || [];
res.json({ res.json({
success: true, success: true,
message: 'Chat history cleared successfully' chatHistory: chatHistory
});
} catch (error) {
console.error('Error getting chat history:', error);
res.json({
success: false,
error: 'Failed to get chat history',
details: error.message
});
}
});
// Delete chat history endpoint
app.delete('/api/chat/history', requireAuth, (req, res) => {
try {
req.session.chatHistory = [];
// Reset the session ID to start a fresh conversation
req.session.chatSessionId = `${req.session.userId}-${Date.now()}`;
// Clear the Flowise chatId
delete req.session.chatId;
req.session.save((err) => {
if (err) {
console.error('Error clearing chat session:', err);
return res.json({
success: false,
error: 'Failed to clear chat history'
});
}
res.json({
success: true,
message: 'Chat history cleared'
});
}); });
} catch (error) { } catch (error) {
console.error('Error clearing chat history:', error); console.error('Error clearing chat history:', error);
@@ -1454,7 +1517,7 @@ app.post('/api/render-revised-content', requireAuth, async (req, res) => {
case 'html': case 'html':
if (isMarkdownContent || autoDetect === false) { if (isMarkdownContent || autoDetect === false) {
// Convert markdown to safe HTML // Convert markdown to safe HTML
renderedContent = markdownToSafeHtml(content); renderedContent = await markdownToSafeHtml(content);
} else { } else {
// Just escape HTML and preserve line breaks for plain text // Just escape HTML and preserve line breaks for plain text
renderedContent = escapeHtml(content).replace(/\n/g, '<br>'); renderedContent = escapeHtml(content).replace(/\n/g, '<br>');
@@ -2705,10 +2768,13 @@ app.listen(PORT, () => {
}); });
// Helper function to convert markdown to safe HTML // Helper function to convert markdown to safe HTML
function markdownToSafeHtml(markdownText) { async function markdownToSafeHtml(markdownText) {
try { try {
// Initialize marked with dynamic import
const markedInstance = await initializeMarked();
// Configure marked options for better security and features // Configure marked options for better security and features
marked.setOptions({ markedInstance.setOptions({
gfm: true, // GitHub Flavored Markdown gfm: true, // GitHub Flavored Markdown
breaks: true, // Convert line breaks to <br> breaks: true, // Convert line breaks to <br>
sanitize: false, // We'll use DOMPurify instead for better control sanitize: false, // We'll use DOMPurify instead for better control
@@ -2718,7 +2784,7 @@ function markdownToSafeHtml(markdownText) {
}); });
// Convert markdown to HTML // Convert markdown to HTML
const rawHtml = marked.parse(markdownText); const rawHtml = markedInstance.parse(markdownText);
// Sanitize the HTML with DOMPurify // Sanitize the HTML with DOMPurify
const cleanHtml = DOMPurify.sanitize(rawHtml, { const cleanHtml = DOMPurify.sanitize(rawHtml, {
@@ -2793,9 +2859,11 @@ async function ensureChatHistoryDirectory() {
// Save chat history to persistent storage // Save chat history to persistent storage
async function saveChatHistory(userId, chatHistory) { async function saveChatHistory(userId, chatHistory) {
try { try {
console.log(`Saving chat history for user ${userId}, ${chatHistory.length} messages`);
await ensureChatHistoryDirectory(); await ensureChatHistoryDirectory();
const historyPath = path.join(CHAT_HISTORY_DIR, `chat-${userId}.json`); const historyPath = path.join(CHAT_HISTORY_DIR, `chat-${userId}.json`);
await fs.writeJSON(historyPath, chatHistory, { spaces: 2 }); await fs.writeJSON(historyPath, chatHistory, { spaces: 2 });
console.log(`Chat history saved successfully to ${historyPath}`);
} catch (error) { } catch (error) {
console.error('Error saving chat history:', error); console.error('Error saving chat history:', error);
throw error; throw error;
@@ -2805,12 +2873,16 @@ async function saveChatHistory(userId, chatHistory) {
// Load chat history from persistent storage // Load chat history from persistent storage
async function loadChatHistory(userId) { async function loadChatHistory(userId) {
try { try {
console.log(`Loading chat history for user ${userId}`);
await ensureChatHistoryDirectory(); await ensureChatHistoryDirectory();
const historyPath = path.join(CHAT_HISTORY_DIR, `chat-${userId}.json`); const historyPath = path.join(CHAT_HISTORY_DIR, `chat-${userId}.json`);
if (await fs.pathExists(historyPath)) { if (await fs.pathExists(historyPath)) {
return await fs.readJSON(historyPath); const history = await fs.readJSON(historyPath);
console.log(`Loaded ${history.length} chat messages for user ${userId}`);
return history;
} else { } else {
console.log(`No chat history file found for user ${userId}`);
return []; return [];
} }
} catch (error) { } catch (error) {
@@ -2844,6 +2916,7 @@ async function initializeDataDirectories() {
// Call initialization // Call initialization
initializeDataDirectories().catch(console.error); initializeDataDirectories().catch(console.error);
initializeDataDirectories().catch(console.error);