Improvements to chat UI and dashboard and revise pages #15

Merged
kqjy merged 3 commits from next into master 2025-07-12 05:53:59 +00:00
10 changed files with 2131 additions and 224 deletions

225
README.md
View File

@@ -1,5 +1,50 @@
# EduCat - AI-Powered Note Revision Platform
EduCat is a modern web application that helps students improve their study notes using AI. Built with Node.js, Express, and EJS, it features secure file upload capabilities, AI-powered note revision, interactive quizzes, and an intelligent chatbot.
## Features
### 🔐 **Security & Authentication**
- Secure login and registration system
- Session-based authentication with flash messages
- Password hashing with bcrypt
- Strict file type whitelisting for secure uploads
### 📁 **Document Processing**
- Upload notes in multiple formats (PDF, DOC, DOCX, XLSX, XLS, TXT, MD, JSON, CSV, XML)
- Secure document extraction with proper error handling
- File preview with extraction information and metadata
- Separate storage for original uploads and AI-revised notes
### 🤖 **AI-Powered Features**
- **Note Revision**: Automatically improve, summarize, and generate study questions
- **Interactive Chatbot**: Chat with AI for study assistance
- **Quiz Generation**: Create interactive quizzes from uploaded documents
- **Smart Content Extraction**: Extract text from various document formats
### 📊 **Dashboard & Management**
- Modern dashboard to manage uploaded files
- Separate sections for original files and AI-revised notes
- File preview with extraction status and metadata
- Download and delete functionality for all file types
### 🎯 **Quiz System**
- Generate quizzes from uploaded documents
- Multiple question types (multiple choice, true/false, short answer)
- Comprehensive quiz review with explanations
- Statistics tracking for quiz performance
### 🎨 **Modern UI/UX**
- Beautiful, responsive design with Bootstrap 5
- Custom SVG favicon and branded interface
- Improved file icons and visual indicators
- Mobile-friendly responsive layout
### 🔗 **AI Integration**
- Connected to Flowise at https://flowise.suika.cc/
- RAG (Retrieval-Augmented Generation) capabilities
- Secure API integration with proper error handling-Powered Note Revision Platform
EduCat is a modern web application that helps students improve their study notes using AI. Built with Node.js, Express, and EJS, it features file upload capabilities, AI-powered note revision, and an interactive chatbot.
## Features
@@ -17,10 +62,15 @@ EduCat is a modern web application that helps students improve their study notes
- **Backend**: Node.js, Express.js
- **Frontend**: EJS templates, Bootstrap 5, Font Awesome
- **Authentication**: bcrypt for password hashing, express-session
- **File Handling**: Multer for file uploads
- **AI Integration**: Flowise API integration
- **File Handling**: Multer for secure file uploads with type validation
- **Document Processing**:
- PDFParse for PDF extraction
- Mammoth for Word document processing
- ExcelJS for secure Excel file handling (replaced vulnerable xlsx)
- **AI Integration**: Flowise API integration with RAG capabilities
- **Session Management**: Express Session with flash messages
- **Styling**: Custom CSS with Bootstrap
- **Security**: File type whitelisting, secure extraction methods
- **Styling**: Custom CSS with Bootstrap and custom SVG favicon
## Installation
@@ -35,6 +85,17 @@ EduCat is a modern web application that helps students improve their study notes
npm install
```
**Key Dependencies:**
- `express` - Web framework
- `ejs` - Template engine
- `multer` - File upload handling
- `bcrypt` - Password hashing
- `express-session` - Session management
- `pdf-parse` - PDF text extraction
- `mammoth` - Word document processing
- `exceljs` - Secure Excel file handling
- `axios` - HTTP client for API calls
3. **Configure environment variables**:
- Copy `.env.example` to `.env` (if exists) or create a new `.env` file
- Update the following variables:
@@ -75,8 +136,12 @@ EduCat is a modern web application that helps students improve their study notes
### Uploading Notes
1. After logging in, click "Upload Notes" in the navigation
2. Drag and drop your file or click to browse
3. Select a file (PDF, DOC, TXT, or image)
3. Select a supported file type:
- **Documents**: PDF, DOC, DOCX
- **Spreadsheets**: XLSX, XLS
- **Text Files**: TXT, MD, JSON, CSV, XML
4. Click "Upload & Process"
5. View file preview with extraction information
### Revising Notes
1. Go to your Dashboard to see uploaded files
@@ -86,11 +151,19 @@ EduCat is a modern web application that helps students improve their study notes
- **Summarize**: Creates concise summaries
- **Generate Questions**: Creates study questions
4. Click "Revise with AI" to process
5. Save and download revised notes from the "AI-Revised Notes" section
### Quiz System
1. Upload a document to generate quizzes from
2. Navigate to the quiz section
3. Take interactive quizzes with multiple question types
4. Review answers with detailed explanations
5. Track your quiz performance and statistics
### Using the Chatbot
1. Navigate to the "Chat" section
2. Type your questions about study materials or academic topics
3. Get instant AI-powered responses
3. Get instant AI-powered responses using RAG technology
## Project Structure
@@ -101,8 +174,10 @@ EduCat/
│ │ └── style.css
│ ├── js/
│ │ └── main.js
── images/
└── logo.png
── images/
└── favicon.svg
│ ├── favicon.svg
│ └── favicon-32x32.svg
├── views/
│ ├── partials/
│ │ ├── header.ejs
@@ -112,8 +187,14 @@ EduCat/
│ ├── revise.ejs
│ ├── chat.ejs
│ ├── dashboard.ejs
│ ├── quiz.ejs
│ └── error.ejs
├── uploads/
│ └── revised-notes/
├── data/
│ ├── user-files/
│ ├── revised-files/
│ └── quiz-results/
├── server.js
├── package.json
└── .env
@@ -121,14 +202,38 @@ EduCat/
## API Endpoints
### Authentication & Core Routes
- `GET /` - Home page
- `GET /login` - Login page
- `POST /login` - Handle login
- `GET /register` - Registration page
- `POST /register` - Handle registration
- `GET /logout` - Logout user
### File Management
- `GET /upload` - File upload page
- `POST /upload` - Handle file uploads
- `POST /upload` - Handle file uploads with validation
- `GET /dashboard` - User dashboard with file management
- `GET /api/files/:fileId/preview` - File preview with extraction info
- `DELETE /api/files/:fileId` - Delete uploaded file
### AI-Powered Features
- `GET /revise/:fileId` - Note revision page
- `POST /api/revise` - AI revision endpoint
- `POST /api/save-revised` - Save revised notes
- `GET /api/download-revised/:fileId` - Download revised notes
- `GET /api/revised-files/:fileId/info` - Get revised file info
- `DELETE /api/revised-files/:fileId` - Delete revised file
### Quiz System
- `GET /quiz` - Quiz interface
- `POST /api/quiz/generate` - Generate quiz from document
- `POST /api/quiz/submit` - Submit quiz answers
- `GET /api/quiz/results` - Get quiz statistics
### Chat Integration
- `GET /chat` - Chat interface
- `POST /api/chat` - Chat API endpoint
- `GET /dashboard` - User dashboard
- `POST /api/chat` - Chat API endpoint with RAG support
## Configuration
@@ -144,24 +249,37 @@ EduCat/
### File Upload Settings
- **Maximum file size**: 10MB
- **Allowed formats**: PDF, DOC, DOCX, TXT, JPG, JPEG, PNG, GIF
- **Upload directory**: `uploads/`
- **Allowed formats**: PDF, DOC, DOCX, XLSX, XLS, TXT, MD, JSON, CSV, XML
- **Upload directory**: `uploads/` (original files)
- **Revised notes directory**: `uploads/revised-notes/`
- **Security**: Strict file type whitelisting with MIME type validation
- **Processing**: Automatic text extraction with error handling
## Customization
### Styling
- Edit `public/css/style.css` to customize the appearance
- The design uses Bootstrap 5 with custom CSS variables
- Custom SVG favicon and file icons for better visual consistency
- Responsive design optimized for mobile and desktop
### AI Integration
- Modify the Flowise API calls in `server.js`
- Update prompts in the `/api/revise` endpoint
- Customize chat responses in the `/api/chat` endpoint
- Configure RAG settings for document-based queries
### Security
- File type restrictions configured in server.js and main.js
- MIME type validation for uploaded files
- Secure document extraction methods
- Session security with proper secret management
### Adding Features
- Add new routes in `server.js`
- Create corresponding EJS templates in `views/`
- Add client-side JavaScript in `public/js/main.js`
- Update CSS in `public/css/style.css`
## Troubleshooting
@@ -169,17 +287,38 @@ EduCat/
1. **File upload fails**:
- Check file size (max 10MB)
- Verify file format is supported
- Ensure `uploads/` directory exists
- Verify file format is supported (PDF, DOC, DOCX, XLSX, XLS, TXT, MD, JSON, CSV, XML)
- Ensure `uploads/` and `uploads/revised-notes/` directories exist
- Check file type validation in both client and server
2. **AI responses don't work**:
2. **Document extraction errors**:
- Verify document is not corrupted
- Check extraction status in file preview
- Ensure proper permissions for file access
- Review server logs for specific extraction errors
3. **AI responses don't work**:
- Verify Flowise API URL is correct
- Check if your chatflow ID is valid
- Ensure Flowise server is accessible
- Verify RAG configuration for document-based queries
3. **Session issues**:
- Verify SESSION_SECRET is set
4. **Quiz generation fails**:
- Ensure document has sufficient text content
- Check if document extraction was successful
- Verify AI service is properly connected
- Review quiz generation prompts
5. **Session issues**:
- Verify SESSION_SECRET is set in .env
- Check if sessions are properly configured
- Clear browser cookies and try again
6. **Revised notes not saving**:
- Ensure `uploads/revised-notes/` directory exists
- Check file permissions
- Verify sufficient disk space
- Review server logs for save errors
### Development
@@ -189,6 +328,58 @@ npm install -g nodemon
npm run dev
```
## Recent Updates & Security Improvements
### Version 2.0 Security Enhancements
- **🔒 Enhanced Security**: Replaced vulnerable `xlsx` library with secure `exceljs` for Excel processing
- **🛡️ File Type Whitelisting**: Implemented strict file type validation to prevent malicious uploads
- **🔍 MIME Type Validation**: Added comprehensive file type checking on both client and server
- **🗂️ Secure Document Processing**: Improved extraction methods with proper error handling
### UI/UX Improvements
- **🎨 Custom Favicon**: Added custom SVG favicon for brand consistency
- **📱 Responsive Design**: Enhanced mobile-friendly interface with improved layouts
- **🔧 File Icons**: Updated file type icons for better visual clarity
- **📊 Dashboard Enhancements**: Separate sections for original and revised files
- **🏷️ Badge System**: Improved status indicators and badge alignment
### New Features
- **💾 Revised Notes Management**: Save, download, and manage AI-revised notes separately
- **🎯 Enhanced Quiz System**: Fixed short-answer questions with explanations
- **📈 Quiz Statistics**: Comprehensive tracking of quiz performance
- **🔍 File Preview**: Detailed extraction information and metadata display
- **📁 Persistent Storage**: Improved file tracking and session management
### Performance & Reliability
- **⚡ Optimized Extraction**: Faster and more reliable document processing
- **🔄 Error Handling**: Comprehensive error handling for all file operations
- **💪 Robust API**: Improved API endpoints with better validation
- **🧹 Code Refactoring**: Cleaner, more maintainable codebase
## Deployment & Production
### Production Considerations
- Set strong `SESSION_SECRET` in production environment
- Configure proper file upload limits based on server capacity
- Set up proper logging and monitoring
- Implement rate limiting for API endpoints
- Configure HTTPS for secure file uploads
- Set up backup procedures for uploaded files and data
### Environment Setup
- Ensure Node.js 14+ is installed
- Create proper directory structure with correct permissions
- Configure environment variables for production
- Set up reverse proxy (nginx/Apache) if needed
- Configure SSL certificates for HTTPS
### Monitoring & Maintenance
- Monitor disk space for uploads directory
- Set up log rotation for application logs
- Regular security updates for dependencies
- Monitor API usage and performance
- Backup user data and quiz results regularly
## Contributing
1. Fork the repository

575
package-lock.json generated
View File

@@ -14,6 +14,7 @@
"body-parser": "^1.20.2",
"connect-flash": "^0.1.1",
"cors": "^2.8.5",
"dompurify": "^3.2.6",
"dotenv": "^16.4.5",
"ejs": "^3.1.10",
"exceljs": "^4.4.0",
@@ -21,7 +22,9 @@
"express-session": "^1.18.0",
"form-data": "^4.0.3",
"fs-extra": "^11.2.0",
"jsdom": "^26.1.0",
"mammoth": "^1.9.1",
"marked": "^16.0.0",
"multer": "^2.0.0",
"pdf-parse": "^1.1.1",
"uuid": "^10.0.0"
@@ -30,6 +33,129 @@
"nodemon": "^3.0.1"
}
},
"node_modules/@asamuzakjp/css-color": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
"integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
"license": "MIT",
"dependencies": {
"@csstools/css-calc": "^2.1.3",
"@csstools/css-color-parser": "^3.0.9",
"@csstools/css-parser-algorithms": "^3.0.4",
"@csstools/css-tokenizer": "^3.0.3",
"lru-cache": "^10.4.3"
}
},
"node_modules/@csstools/color-helpers": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
"integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT-0",
"engines": {
"node": ">=18"
}
},
"node_modules/@csstools/css-calc": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
"integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-color-parser": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz",
"integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"dependencies": {
"@csstools/color-helpers": "^5.0.2",
"@csstools/css-calc": "^2.1.4"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-parser-algorithms": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
"integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-tokenizer": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
"integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@fast-csv/format": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz",
@@ -85,6 +211,13 @@
"integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
"license": "MIT"
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
@@ -794,6 +927,66 @@
"node": ">= 10"
}
},
"node_modules/cssstyle": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
"integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
"license": "MIT",
"dependencies": {
"@asamuzakjp/css-color": "^3.2.0",
"rrweb-cssom": "^0.8.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/data-urls": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
"integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
"license": "MIT",
"dependencies": {
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/data-urls/node_modules/tr46": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/data-urls/node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/data-urls/node_modules/whatwg-url": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"license": "MIT",
"dependencies": {
"tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
@@ -809,6 +1002,12 @@
"ms": "2.0.0"
}
},
"node_modules/decimal.js": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
"license": "MIT"
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -858,6 +1057,15 @@
"integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==",
"license": "BSD-2-Clause"
},
"node_modules/dompurify": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
"integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
@@ -977,6 +1185,18 @@
"once": "^1.4.0"
}
},
"node_modules/entities": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -1541,6 +1761,18 @@
"node": ">= 0.4"
}
},
"node_modules/html-encoding-sniffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
"integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
"license": "MIT",
"dependencies": {
"whatwg-encoding": "^3.1.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -1557,6 +1789,51 @@
"node": ">= 0.8"
}
},
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/http-proxy-agent/node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/http-proxy-agent/node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/http-proxy-agent/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
@@ -1719,6 +1996,12 @@
"node": ">=0.12.0"
}
},
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
"license": "MIT"
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -1743,6 +2026,136 @@
"node": ">=10"
}
},
"node_modules/jsdom": {
"version": "26.1.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
"license": "MIT",
"dependencies": {
"cssstyle": "^4.2.1",
"data-urls": "^5.0.0",
"decimal.js": "^10.5.0",
"html-encoding-sniffer": "^4.0.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"is-potential-custom-element-name": "^1.0.1",
"nwsapi": "^2.2.16",
"parse5": "^7.2.1",
"rrweb-cssom": "^0.8.0",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^5.1.1",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^7.0.0",
"whatwg-encoding": "^3.1.1",
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.1.1",
"ws": "^8.18.0",
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"canvas": "^3.0.0"
},
"peerDependenciesMeta": {
"canvas": {
"optional": true
}
}
},
"node_modules/jsdom/node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/jsdom/node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/jsdom/node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/jsdom/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/jsdom/node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
"license": "ISC",
"dependencies": {
"xmlchars": "^2.2.0"
},
"engines": {
"node": ">=v12.22.7"
}
},
"node_modules/jsdom/node_modules/tr46": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/jsdom/node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/jsdom/node_modules/whatwg-url": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"license": "MIT",
"dependencies": {
"tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@@ -1944,6 +2357,12 @@
"underscore": "^1.13.1"
}
},
"node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -1992,6 +2411,18 @@
"node": ">=12.0.0"
}
},
"node_modules/marked": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-16.0.0.tgz",
"integrity": "sha512-MUKMXDjsD/eptB7GPzxo4xcnLS6oo7/RHimUMHEDRhUooPwmN9BEpMl7AEOJv3bmso169wHI2wUF9VQgL7zfmA==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -2307,6 +2738,12 @@
"set-blocking": "^2.0.0"
}
},
"node_modules/nwsapi": {
"version": "2.2.20",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
"integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
"license": "MIT"
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -2370,6 +2807,18 @@
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/parse5": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
"license": "MIT",
"dependencies": {
"entities": "^6.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -2467,6 +2916,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
@@ -2588,6 +3046,12 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rrweb-cssom": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
"integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
"license": "MIT"
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -2871,6 +3335,12 @@
"node": ">=8"
}
},
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"license": "MIT"
},
"node_modules/tar": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
@@ -2916,6 +3386,24 @@
"node": ">=10"
}
},
"node_modules/tldts": {
"version": "6.1.86",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
"integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
"license": "MIT",
"dependencies": {
"tldts-core": "^6.1.86"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
"version": "6.1.86",
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
"license": "MIT"
},
"node_modules/tmp": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
@@ -2957,6 +3445,18 @@
"nodetouch": "bin/nodetouch.js"
}
},
"node_modules/tough-cookie": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
"integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
"license": "BSD-3-Clause",
"dependencies": {
"tldts": "^6.1.32"
},
"engines": {
"node": ">=16"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -3119,12 +3619,57 @@
"node": ">= 0.8"
}
},
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
"license": "MIT",
"dependencies": {
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
"license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-encoding/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/whatwg-mimetype": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@@ -3150,6 +3695,36 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
"license": "Apache-2.0",
"engines": {
"node": ">=18"
}
},
"node_modules/xmlbuilder": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz",

View File

@@ -13,6 +13,7 @@
"body-parser": "^1.20.2",
"connect-flash": "^0.1.1",
"cors": "^2.8.5",
"dompurify": "^3.2.6",
"dotenv": "^16.4.5",
"ejs": "^3.1.10",
"exceljs": "^4.4.0",
@@ -20,7 +21,9 @@
"express-session": "^1.18.0",
"form-data": "^4.0.3",
"fs-extra": "^11.2.0",
"jsdom": "^26.1.0",
"mammoth": "^1.9.1",
"marked": "^16.0.0",
"multer": "^2.0.0",
"pdf-parse": "^1.1.1",
"uuid": "^10.0.0"

View File

@@ -614,3 +614,357 @@ body {
border-radius: 0.75rem;
overflow: hidden;
}
/* Enhanced Chat Message Formatting */
.message-text {
line-height: 1.6;
word-wrap: break-word;
overflow-wrap: break-word;
}
.message-text h3,
.message-text h4,
.message-text h5 {
color: inherit;
font-weight: 600;
margin-bottom: 0.5rem;
}
.message-text h3 {
font-size: 1.1rem;
border-bottom: 1px solid rgba(0,0,0,0.1);
padding-bottom: 0.25rem;
}
.message-text h4 {
font-size: 1.05rem;
}
.message-text h5 {
font-size: 1rem;
}
/* Code formatting */
.message-text .code-block {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 0.375rem;
padding: 0.75rem;
margin: 0.5rem 0;
font-family: 'Courier New', monospace;
font-size: 0.875rem;
white-space: pre-wrap;
overflow-x: auto;
max-width: 100%;
}
.message-text .inline-code {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 0.25rem;
padding: 0.125rem 0.25rem;
font-family: 'Courier New', monospace;
font-size: 0.875rem;
color: #d63384;
}
/* List formatting */
.message-text .formatted-list {
margin: 0.5rem 0;
padding-left: 1.5rem;
}
.message-text .formatted-list li {
margin-bottom: 0.25rem;
line-height: 1.5;
}
.message-text ul.formatted-list {
list-style-type: disc;
}
.message-text ol.formatted-list {
list-style-type: decimal;
}
.message-text .bullet-item,
.message-text .list-item {
display: list-item;
}
/* Bold and italic text */
.message-text strong {
font-weight: 600;
}
.message-text em {
font-style: italic;
}
/* Paragraph spacing */
.message-text br + br {
display: block;
margin: 0.5rem 0;
content: "";
}
/* Special styling for bot messages */
.bot-message .message-text .code-block {
background-color: #f1f3f4;
border-color: #dadce0;
}
.bot-message .message-text .inline-code {
background-color: #f1f3f4;
border-color: #dadce0;
color: #1a73e8;
}
/* Special styling for user messages */
.user-message .message-text .code-block {
background-color: rgba(255,255,255,0.1);
border-color: rgba(255,255,255,0.2);
color: #ffffff;
}
.user-message .message-text .inline-code {
background-color: rgba(255,255,255,0.1);
border-color: rgba(255,255,255,0.2);
color: #ffffff;
}
.user-message .message-text h3,
.user-message .message-text h4,
.user-message .message-text h5 {
color: #ffffff;
border-color: rgba(255,255,255,0.3);
}
/* Rendered Markdown Styles */
.rendered-markdown {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
max-width: 100%;
word-wrap: break-word;
}
.rendered-markdown h1,
.rendered-markdown h2,
.rendered-markdown h3,
.rendered-markdown h4,
.rendered-markdown h5,
.rendered-markdown h6 {
margin-top: 1.5rem;
margin-bottom: 0.75rem;
font-weight: 600;
line-height: 1.25;
}
.rendered-markdown h1 {
font-size: 1.75rem;
border-bottom: 2px solid #e1e4e8;
padding-bottom: 0.5rem;
}
.rendered-markdown h2 {
font-size: 1.5rem;
border-bottom: 1px solid #e1e4e8;
padding-bottom: 0.3rem;
}
.rendered-markdown h3 {
font-size: 1.25rem;
}
.rendered-markdown h4 {
font-size: 1.1rem;
}
.rendered-markdown h5,
.rendered-markdown h6 {
font-size: 1rem;
}
.rendered-markdown p {
margin-bottom: 1rem;
}
.rendered-markdown strong,
.rendered-markdown b {
font-weight: 600;
}
.rendered-markdown em,
.rendered-markdown i {
font-style: italic;
}
.rendered-markdown ul,
.rendered-markdown ol {
margin-bottom: 1rem;
padding-left: 2rem;
}
.rendered-markdown li {
margin-bottom: 0.25rem;
}
.rendered-markdown li > p {
margin-bottom: 0.5rem;
}
.rendered-markdown ul {
list-style-type: disc;
}
.rendered-markdown ol {
list-style-type: decimal;
}
.rendered-markdown blockquote {
margin: 1rem 0;
padding: 0.5rem 1rem;
border-left: 4px solid #dfe2e5;
background-color: #f6f8fa;
color: #6a737d;
}
.rendered-markdown blockquote p:last-child {
margin-bottom: 0;
}
.rendered-markdown pre {
background-color: #f6f8fa;
border-radius: 6px;
font-size: 0.875rem;
line-height: 1.45;
overflow: auto;
padding: 1rem;
margin-bottom: 1rem;
font-family: 'Courier New', Consolas, monospace;
}
.rendered-markdown code {
background-color: rgba(27, 31, 35, 0.05);
border-radius: 3px;
font-size: 0.875rem;
margin: 0;
padding: 0.2em 0.4em;
font-family: 'Courier New', Consolas, monospace;
}
.rendered-markdown pre code {
background-color: transparent;
border-radius: 0;
font-size: inherit;
margin: 0;
padding: 0;
word-break: normal;
white-space: pre;
word-wrap: normal;
}
.rendered-markdown table {
border-collapse: collapse;
margin-bottom: 1rem;
width: 100%;
}
.rendered-markdown table th,
.rendered-markdown table td {
border: 1px solid #dfe2e5;
padding: 6px 13px;
text-align: left;
}
.rendered-markdown table th {
background-color: #f6f8fa;
font-weight: 600;
}
.rendered-markdown table tr:nth-child(2n) {
background-color: #f6f8fa;
}
.rendered-markdown a {
color: #0366d6;
text-decoration: none;
}
.rendered-markdown a:hover {
text-decoration: underline;
}
.rendered-markdown hr {
border: none;
border-top: 1px solid #e1e4e8;
height: 1px;
margin: 1.5rem 0;
}
.rendered-markdown img {
max-width: 100%;
height: auto;
border-radius: 6px;
margin: 0.5rem 0;
}
/* Display mode toggle styling */
#display-mode-toggle .btn-outline-secondary {
border-color: #6c757d;
color: #6c757d;
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
#display-mode-toggle .btn-check:checked + .btn-outline-secondary {
background-color: #6c757d;
border-color: #6c757d;
color: white;
}
#display-mode-toggle .btn-outline-secondary:hover {
background-color: #5c636a;
border-color: #565e64;
color: white;
}
/* Alert for format detection */
.alert-sm {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.rendered-markdown {
font-size: 0.9rem;
}
.rendered-markdown h1 {
font-size: 1.5rem;
}
.rendered-markdown h2 {
font-size: 1.3rem;
}
.rendered-markdown h3 {
font-size: 1.1rem;
}
.rendered-markdown pre {
padding: 0.75rem;
font-size: 0.8rem;
}
.rendered-markdown table {
font-size: 0.875rem;
}
.rendered-markdown ul,
.rendered-markdown ol {
padding-left: 1.5rem;
}
}

911
server.js

File diff suppressed because it is too large Load Diff

View File

@@ -94,6 +94,16 @@ document.addEventListener('DOMContentLoaded', function() {
function addWelcomeMessage() {
const messageDiv = document.createElement('div');
messageDiv.className = 'chat-message bot-message mb-3';
const welcomeText = `Hello! I'm **EduCat AI**, your study assistant. I can help you with:
• Questions about your notes and study materials
• Study techniques and academic strategies
• Explanations of complex concepts
• Creating study plans and schedules
How can I assist you today?`;
const formattedWelcome = formatMessage(welcomeText, true);
messageDiv.innerHTML = `
<div class="d-flex align-items-start">
<div class="avatar bg-primary text-white rounded-circle d-flex align-items-center justify-content-center me-3 flex-shrink-0" style="width: 45px; height: 45px; min-width: 45px;">
@@ -101,7 +111,7 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
<div class="message-content flex-grow-1">
<div class="message-bubble bg-light p-3 rounded-3 shadow-sm border">
<p class="mb-0">Hello! I'm EduCat AI, your study assistant. I can help you with questions about your notes, study techniques, and academic topics. How can I assist you today?</p>
<div class="message-text">${formattedWelcome}</div>
</div>
<small class="text-muted d-block mt-1">Just now</small>
</div>
@@ -125,18 +135,81 @@ document.addEventListener('DOMContentLoaded', function() {
sendToAI(message);
}
function formatMessage(text, isBot = false) {
if (!isBot) {
// For user messages, just escape HTML and preserve basic formatting
return text.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>');
}
// For bot messages, apply rich formatting
let formatted = text
// Escape HTML first
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
// Convert markdown-style formatting
// Bold text **text** or __text__
formatted = formatted.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
formatted = formatted.replace(/__(.*?)__/g, '<strong>$1</strong>');
// Italic text *text* or _text_
formatted = formatted.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, '<em>$1</em>');
formatted = formatted.replace(/(?<!_)_([^_]+)_(?!_)/g, '<em>$1</em>');
// Code blocks ```code```
formatted = formatted.replace(/```([\s\S]*?)```/g, '<pre class="code-block"><code>$1</code></pre>');
// Inline code `code`
formatted = formatted.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>');
// Headers
formatted = formatted.replace(/^### (.*$)/gm, '<h5 class="mt-3 mb-2">$1</h5>');
formatted = formatted.replace(/^## (.*$)/gm, '<h4 class="mt-3 mb-2">$1</h4>');
formatted = formatted.replace(/^# (.*$)/gm, '<h3 class="mt-3 mb-2">$1</h3>');
// Convert bullet points and numbered lists
// Handle bullet points: - item, * item, • item
formatted = formatted.replace(/^[\s]*[-*•]\s+(.+)$/gm, '<li class="bullet-item">$1</li>');
// Handle numbered lists: 1. item, 2. item, etc.
formatted = formatted.replace(/^[\s]*\d+\.\s+(.+)$/gm, '<li class="numbered-item">$1</li>');
// Wrap consecutive list items in proper list containers
formatted = formatted.replace(/(<li class="bullet-item">.*?<\/li>)(?:\s*<li class="bullet-item">.*?<\/li>)*/gs, function(match) {
return '<ul class="formatted-list">' + match + '</ul>';
});
formatted = formatted.replace(/(<li class="numbered-item">.*?<\/li>)(?:\s*<li class="numbered-item">.*?<\/li>)*/gs, function(match) {
return '<ol class="formatted-list">' + match.replace(/class="numbered-item"/g, 'class="list-item"') + '</ol>';
});
// Convert line breaks to <br> but not inside <pre> or list tags
let parts = formatted.split(/(<pre[\s\S]*?<\/pre>|<ul[\s\S]*?<\/ul>|<ol[\s\S]*?<\/ol>)/);
for (let i = 0; i < parts.length; i += 2) {
parts[i] = parts[i].replace(/\n\s*\n/g, '<br><br>').replace(/\n/g, '<br>');
}
formatted = parts.join('');
return formatted;
}
function addMessageToChat(sender, message, withScroll = true) {
const messageDiv = document.createElement('div');
messageDiv.className = `chat-message ${sender}-message mb-3`;
const time = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
const formattedMessage = formatMessage(message, sender === 'bot');
if (sender === 'user') {
messageDiv.innerHTML = `
<div class="d-flex align-items-start justify-content-end">
<div class="message-content me-3 flex-grow-1" style="max-width: 70%;">
<div class="message-bubble bg-primary text-white p-3 rounded-3 shadow-sm">
<p class="mb-0">${message}</p>
<div class="message-text">${formattedMessage}</div>
</div>
<small class="text-muted d-block text-end mt-1">${time}</small>
</div>
@@ -153,7 +226,7 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
<div class="message-content flex-grow-1" style="max-width: 70%;">
<div class="message-bubble bg-light p-3 rounded-3 shadow-sm border">
<p class="mb-0">${message}</p>
<div class="message-text">${formattedMessage}</div>
</div>
<small class="text-muted d-block mt-1">${time}</small>
</div>

View File

@@ -890,25 +890,100 @@ async function previewRevisedFile(fileId) {
const file = fileInfo.file;
// Download the file content
const contentResponse = await fetch(`/uploads/revised-notes/${file.filename}`);
// Get file content using the new API endpoint
const contentResponse = await fetch(`/api/revised-files/${fileId}/content`);
if (!contentResponse.ok) {
throw new Error('Failed to load file content');
}
const content = await contentResponse.text();
const contentResult = await contentResponse.json();
if (!contentResult.success) {
throw new Error('Failed to load file content');
}
const content = contentResult.content;
const modal = new bootstrap.Modal(document.getElementById('previewModal'));
// Create preview content with display mode toggle
document.getElementById('preview-content').innerHTML = `
<div class="alert alert-info">
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="alert alert-info mb-0 flex-grow-1 me-3">
<i class="fas fa-brain me-2"></i>
<strong>AI-Revised Content</strong> • ${file.revisionType} • From: ${file.originalFileName || 'Unknown'}
</div>
<div class="border p-3 bg-light rounded" style="max-height: 400px; overflow-y: auto;">
<pre style="white-space: pre-wrap; word-wrap: break-word;">${escapeHtml(content)}</pre>
<div class="btn-group btn-group-sm" role="group" id="preview-display-mode">
<input type="radio" class="btn-check" name="previewDisplayMode" id="preview-mode-markdown" value="markdown" checked>
<label class="btn btn-outline-secondary" for="preview-mode-markdown" title="Show as Markdown">
<i class="fab fa-markdown"></i>
</label>
<input type="radio" class="btn-check" name="previewDisplayMode" id="preview-mode-html" value="html">
<label class="btn btn-outline-secondary" for="preview-mode-html" title="Render as HTML">
<i class="fas fa-code"></i>
</label>
</div>
</div>
<div id="preview-content-container" class="border p-3 bg-light rounded" style="max-height: 400px; overflow-y: auto;">
<pre style="white-space: pre-wrap; word-wrap: break-word; margin: 0;">${escapeHtml(content)}</pre>
</div>
`;
// Add event listeners for display mode toggle
const modeMarkdown = document.getElementById('preview-mode-markdown');
const modeHtml = document.getElementById('preview-mode-html');
const contentContainer = document.getElementById('preview-content-container');
async function updatePreviewMode() {
const selectedMode = document.querySelector('input[name="previewDisplayMode"]:checked').value;
try {
const renderResponse = await fetch('/api/render-revised-content', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
content: content,
displayMode: selectedMode,
autoDetect: true
})
});
const renderResult = await renderResponse.json();
if (renderResult.success) {
if (selectedMode === 'html') {
let htmlContent = `<div class="rendered-markdown">${renderResult.renderedContent}</div>`;
if (renderResult.isMarkdownContent) {
htmlContent = `
<div class="alert alert-info alert-sm mb-2">
<small><i class="fas fa-info-circle me-1"></i>Markdown formatting detected and rendered</small>
</div>
${htmlContent}
`;
}
contentContainer.innerHTML = htmlContent;
contentContainer.style.backgroundColor = '#ffffff';
} else {
contentContainer.innerHTML = `<pre style="white-space: pre-wrap; word-wrap: break-word; margin: 0;">${escapeHtml(renderResult.renderedContent)}</pre>`;
contentContainer.style.backgroundColor = '#f8f9fa';
}
} else {
throw new Error(renderResult.error || 'Failed to render content');
}
} catch (error) {
console.error('Error rendering preview:', error);
// Fallback to escaped text
contentContainer.innerHTML = `<pre style="white-space: pre-wrap; word-wrap: break-word; margin: 0;">${escapeHtml(content)}</pre>`;
contentContainer.style.backgroundColor = '#f8f9fa';
}
}
modeMarkdown.addEventListener('change', updatePreviewMode);
modeHtml.addEventListener('change', updatePreviewMode);
modal.show();
} catch (error) {
console.error('Error previewing revised file:', error);

View File

@@ -4,6 +4,13 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="icon" type="image/svg+xml" sizes="32x32" href="/favicon-32x32.svg">
<link rel="apple-touch-icon" sizes="180x180" href="/images/favicon.svg">
<meta name="theme-color" content="#007bff">
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="/css/style.css" rel="stylesheet">

View File

@@ -35,7 +35,20 @@
</div>
</div>
<div class="col-md-6">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5>AI-Revised Notes</h5>
<div class="btn-group btn-group-sm" role="group" id="display-mode-toggle" style="display: none;">
<input type="radio" class="btn-check" name="displayMode" id="mode-markdown" value="markdown" checked>
<label class="btn btn-outline-secondary" for="mode-markdown" title="Show as Markdown">
<i class="fab fa-markdown"></i>
</label>
<input type="radio" class="btn-check" name="displayMode" id="mode-html" value="html">
<label class="btn btn-outline-secondary" for="mode-html" title="Render as HTML">
<i class="fas fa-code"></i>
</label>
</div>
</div>
<div id="revised-content" class="border p-3 bg-white rounded" style="height: 400px; overflow-y: auto;">
<p class="text-muted text-center mt-5">Select a revision type and click "Revise" to see AI-enhanced notes here.</p>
</div>
@@ -139,11 +152,72 @@ document.addEventListener('DOMContentLoaded', function() {
const revisionType = document.getElementById('revision-type');
const saveBtn = document.getElementById('save-btn');
const downloadBtn = document.getElementById('download-btn');
const displayModeToggle = document.getElementById('display-mode-toggle');
const modeMarkdown = document.getElementById('mode-markdown');
const modeHtml = document.getElementById('mode-html');
const fileId = '<%= file.id %>';
const content = <%- JSON.stringify(content) %>;
let currentRevisedContent = '';
let currentRevisionType = '';
let currentDisplayMode = 'markdown';
// Handle display mode changes
function updateDisplayMode() {
if (!currentRevisedContent) return;
const selectedMode = document.querySelector('input[name="displayMode"]:checked').value;
currentDisplayMode = selectedMode;
renderRevisedContent(currentRevisedContent, selectedMode);
}
// Render revised content based on display mode
async function renderRevisedContent(content, displayMode = 'markdown') {
try {
const response = await fetch('/api/render-revised-content', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
content: content,
displayMode: displayMode,
autoDetect: true
})
});
const result = await response.json();
if (result.success) {
if (displayMode === 'html') {
// Render as HTML
revisedContent.innerHTML = `<div class="rendered-markdown">${result.renderedContent}</div>`;
// Show format detection info if available
if (result.isMarkdownContent) {
const formatInfo = document.createElement('div');
formatInfo.className = 'alert alert-info alert-sm mb-2';
formatInfo.innerHTML = '<small><i class="fas fa-info-circle me-1"></i>Markdown formatting detected and rendered</small>';
revisedContent.insertBefore(formatInfo, revisedContent.firstChild);
}
} else {
// Show as raw markdown/text
revisedContent.innerHTML = `<pre class="mb-0" style="white-space: pre-wrap; word-wrap: break-word;">${escapeHtml(result.renderedContent)}</pre>`;
}
} else {
throw new Error(result.error || 'Failed to render content');
}
} catch (error) {
console.error('Error rendering content:', error);
// Fallback to escaped text
revisedContent.innerHTML = `<pre class="mb-0" style="white-space: pre-wrap; word-wrap: break-word;">${escapeHtml(content)}</pre>`;
}
}
// Add event listeners for display mode toggle
modeMarkdown.addEventListener('change', updateDisplayMode);
modeHtml.addEventListener('change', updateDisplayMode);
reviseBtn.addEventListener('click', async function() {
const type = revisionType.value;
@@ -153,6 +227,7 @@ document.addEventListener('DOMContentLoaded', function() {
reviseBtn.disabled = true;
revisionProgress.classList.remove('d-none');
displayModeToggle.style.display = 'none'; // Hide toggle during processing
revisedContent.innerHTML = '<div class="text-center mt-5"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Processing...</span></div><p class="mt-2">AI is processing your notes...</p></div>';
try {
@@ -175,9 +250,15 @@ document.addEventListener('DOMContentLoaded', function() {
console.log('Revision result:', result);
if (result.success) {
revisedContent.innerHTML = '<pre class="mb-0" style="white-space: pre-wrap; word-wrap: break-word;">' + escapeHtml(result.revisedContent) + '</pre>';
currentRevisedContent = result.revisedContent;
currentRevisionType = type;
// Show display mode toggle
displayModeToggle.style.display = 'block';
// Render content based on current display mode
await renderRevisedContent(currentRevisedContent, currentDisplayMode);
saveBtn.disabled = false;
downloadBtn.disabled = false;
} else {

View File

@@ -14,12 +14,18 @@
<i class="fas fa-cloud-upload-alt fa-3x text-primary mb-3"></i>
<h5>Drag & Drop your files here</h5>
<p class="text-muted">or click to browse</p>
<input type="file" id="noteFile" name="noteFile" class="d-none" accept=".pdf,.doc,.docx,.txt,.jpg,.jpeg,.png,.gif">
<input type="file" id="noteFile" name="noteFile" class="d-none" accept=".pdf,.doc,.docx,.txt,.xlsx,.xls,.md,.json,.csv,.xml">
<button type="button" class="btn btn-primary" onclick="document.getElementById('noteFile').click()">
<i class="fas fa-folder-open me-2"></i>Choose File
</button>
</div>
<div class="alert alert-info mb-4">
<i class="fas fa-info-circle me-2"></i>
<strong>Supported file types:</strong> PDF, Word (.doc, .docx), Excel (.xlsx, .xls), text files (.txt, .md, .json, .csv, .xml)
<br><small class="text-muted">Only text-extractable documents are allowed to ensure optimal AI processing and prevent RAG corruption.</small>
</div>
<div id="file-info" class="d-none mb-4">
<div class="alert alert-info">
<i class="fas fa-file me-2"></i>
@@ -46,25 +52,32 @@
</div>
<div class="row mt-5">
<div class="col-md-4">
<div class="col-md-3">
<div class="text-center">
<i class="fas fa-file-pdf fa-2x text-danger mb-2"></i>
<h6>PDF Files</h6>
<p class="text-muted small">Upload PDF documents</p>
</div>
</div>
<div class="col-md-4">
<div class="col-md-3">
<div class="text-center">
<i class="fas fa-file-word fa-2x text-primary mb-2"></i>
<h6>Word Documents</h6>
<p class="text-muted small">DOC & DOCX files</p>
</div>
</div>
<div class="col-md-4">
<div class="col-md-3">
<div class="text-center">
<i class="fas fa-file-alt fa-2x text-success mb-2"></i>
<i class="fas fa-file-excel fa-2x text-success mb-2"></i>
<h6>Excel Files</h6>
<p class="text-muted small">XLSX & XLS files</p>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<i class="fas fa-file-alt fa-2x text-info mb-2"></i>
<h6>Text Files</h6>
<p class="text-muted small">Plain text documents</p>
<p class="text-muted small">TXT, MD, JSON, CSV, XML</p>
</div>
</div>
</div>