Rename
This commit is contained in:
parent
a3ef26ff29
commit
990d60d90f
16 changed files with 1113 additions and 80 deletions
203
README.md
203
README.md
|
|
@ -1,81 +1,170 @@
|
|||
<div align="center">
|
||||
<img src="/backend/static/logo.png" alt="ocrAI Logo" width="250">
|
||||
<img src="/backend/static/logo.png" alt="DocuLens Logo" width="250">
|
||||
</div>
|
||||
|
||||
# ocrAI 🤖
|
||||
# DocuLens 🤖
|
||||
|
||||
ocrAI is a unified web application that combines Optical Character Recognition (OCR) and Artificial Intelligence (AI) to process and translate documents, offering a simple, intuitive interface with real-time feedback (even with emojis!).
|
||||
DocuLens is a unified web application that combines Optical Character Recognition (OCR) and Artificial Intelligence (AI) to process and translate documents, offering a simple, intuitive interface with real-time feedback.
|
||||
|
||||
## Key Features
|
||||
|
||||
### Document Processing 📄
|
||||
|
||||
- **Multiple Processing Modes** 🔍
|
||||
- **OCR Mode:** Uses Tesseract for text extraction
|
||||
- **OCR + AI Mode:** Combines Tesseract with AI correction
|
||||
- **Full AI OCR Mode:** Complete AI-powered processing
|
||||
- Real-time progress tracking with visual feedback
|
||||
|
||||
- **File Management** 📤
|
||||
- Upload PDF or image files using drag & drop or manual selection.
|
||||
- Files are saved with unique names to avoid overwrites.
|
||||
- The "Delete All Files" button removes all files from both the "uploads" and "outputs" folders.
|
||||
- Support for PDF and image uploads
|
||||
- Automatic file naming to prevent overwrites
|
||||
- Bulk file management capabilities
|
||||
- Secure file handling and sanitization
|
||||
|
||||
- **OCR Processing Modes** 🔍
|
||||
- **OCR (Tesseract Only):**
|
||||
Extracts text with Tesseract and embeds it into the PDF using OCRmyPDF. The TXT file contains the raw OCR output.
|
||||
- **OCR + AI (Tesseract + AI):**
|
||||
Uses Tesseract to extract text and then sends it to an AI model (e.g., Gemini) to correct and format the content. The TXT file shows the corrected and structured text, while the PDF retains the original Tesseract output.
|
||||
- **AI (Full AI OCR):**
|
||||
Leverages the AI model's OCR capabilities to process the document page by page. The TXT file includes clear page markers, making it easy to compare with the original document, and the original PDF is preserved.
|
||||
- All modes display real-time progress updates with emojis (e.g., 📤, ✅, 🤖, 🎉) and run in the background.
|
||||
- **Translation System** 🌐
|
||||
- Page-by-page translation
|
||||
- Support for processed or new files
|
||||
- Multiple language support
|
||||
- Progress tracking per page
|
||||
- Translation file generation with page markers
|
||||
|
||||
- **Translation** 🌐
|
||||
- Translates PDF or TXT documents page by page.
|
||||
- You can upload a new file or select one from the list of processed files.
|
||||
- Progress updates are displayed, and a TXT file with the final translation (including page markers) is generated.
|
||||
### Configuration & Customization ⚙️
|
||||
|
||||
- **Configuration** ⚙️
|
||||
- Manage and add new AI models (including the ability to add or delete Gemini models) and languages.
|
||||
- Update or add custom prompts for OCR, correction, and translation functions.
|
||||
- Download or upload the complete configuration (which includes prompts and models).
|
||||
- **AI Integration**
|
||||
- Support for multiple AI providers:
|
||||
- OpenAI
|
||||
- Google Gemini
|
||||
- Mistral
|
||||
- Custom model management
|
||||
- Configurable prompts system
|
||||
|
||||
## How to Use the Application
|
||||
- **System Settings**
|
||||
- Language configuration
|
||||
- Import/export functionality
|
||||
- Runtime model updates
|
||||
- Custom prompt management
|
||||
|
||||
1. **Upload and Process Files:**
|
||||
- Go to the **OCR** tab.
|
||||
- Select your file (PDF or image).
|
||||
- Choose one of the processing modes:
|
||||
- **OCR** (Tesseract Only)
|
||||
- **OCR + AI** (Tesseract + AI for correction)
|
||||
- **AI** (Full AI OCR)
|
||||
- Select the desired prompt.
|
||||
- Click **Upload and process** and watch the real-time progress.
|
||||
### Security & Authentication 🔒
|
||||
|
||||
2. **Translate Documents:**
|
||||
- Go to the **Translation** tab.
|
||||
- Upload a new file or select one from the list of processed files.
|
||||
- Choose the target language and translation prompt.
|
||||
- Click **Translate** and observe the progress as each page is processed.
|
||||
- The result is saved in a TXT file with page markers.
|
||||
- **User Authentication**
|
||||
- JSON file-based user management
|
||||
- Secure password hashing with bcrypt
|
||||
- Session-based authentication
|
||||
- Protected API routes
|
||||
- Remember me functionality
|
||||
- Password change capability
|
||||
|
||||
3. **View Processed Files:**
|
||||
- Go to the **Processed Files** tab.
|
||||
- Download or delete files (with confirmation prompts).
|
||||
### User Interface 🎨
|
||||
|
||||
4. **Configure the Application:**
|
||||
- Go to the **Configurations** tab.
|
||||
- Add, edit, or delete custom prompts.
|
||||
- Manage Gemini models: add new models or delete existing ones.
|
||||
- Configure languages and download or upload the complete configuration.
|
||||
- **Real-time Feedback**
|
||||
- Progress tracking with emoji indicators
|
||||
- Job status notifications
|
||||
- Error handling and display
|
||||
- Responsive design
|
||||
|
||||
## How to Run ocrAI
|
||||
## Technical Architecture
|
||||
|
||||
### Frontend (React)
|
||||
- Single Page Application (SPA) architecture
|
||||
- Component-based structure:
|
||||
- Login & Authentication
|
||||
- File Upload & Processing
|
||||
- File Management
|
||||
- Model Selection
|
||||
- Configuration Management
|
||||
- Progress Tracking
|
||||
- Notifications System
|
||||
- Password Management
|
||||
|
||||
### Backend (Flask)
|
||||
- RESTful API architecture
|
||||
- Modular design:
|
||||
- Authentication system
|
||||
- File processing
|
||||
- OCR integration
|
||||
- AI model management
|
||||
- Translation services
|
||||
- Configuration handling
|
||||
|
||||
### Key Technical Features
|
||||
- Asynchronous job processing
|
||||
- Real-time progress updates
|
||||
- Multiple processing modes
|
||||
- Secure file handling
|
||||
- Comprehensive error handling
|
||||
- Session management
|
||||
- API route protection
|
||||
|
||||
## Setup & Installation
|
||||
|
||||
### Prerequisites
|
||||
- Docker
|
||||
- Docker Compose
|
||||
|
||||
### Build and Run
|
||||
### Environment Setup
|
||||
1. Clone the repository
|
||||
2. Configure environment variables for AI providers
|
||||
3. Build and run with Docker Compose:
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
4. Access the application at http://localhost:5015
|
||||
|
||||
```bash
|
||||
docker-compose up --build
|
||||
Then, open your browser at http://localhost:5015 to start using ocrAI.
|
||||
### Initial Login
|
||||
- Default users: chaulmark, ekragh
|
||||
- Default password: changeme123
|
||||
- **Important:** Change your password after first login using the "Change Password" button in the header
|
||||
|
||||
Technologies Used
|
||||
Frontend: React, Axios
|
||||
Backend: Flask, Python
|
||||
OCR: Tesseract, pdf2image, OCRmyPDF
|
||||
AI: OpenAI, Gemini, Mistral APIs
|
||||
Containerization: Docker, Docker Compose
|
||||
## Security Considerations
|
||||
|
||||
### Authentication
|
||||
- Secure password hashing with bcrypt
|
||||
- Session-based authentication
|
||||
- Protected API routes
|
||||
- Remember me functionality
|
||||
- Password change capability
|
||||
- Session management and protection
|
||||
|
||||
### File Security
|
||||
- Secure file uploads with sanitization
|
||||
- Unique file naming
|
||||
- Separate upload and output directories
|
||||
|
||||
### API Security
|
||||
- CORS configuration with credentials
|
||||
- Environment-based configuration
|
||||
- API route protection
|
||||
- Session state persistence
|
||||
- Secure cookie handling
|
||||
|
||||
## Development Status
|
||||
|
||||
### Complete Features
|
||||
- Core document processing functionality
|
||||
- Authentication system
|
||||
- File management
|
||||
- Translation system
|
||||
- Configuration management
|
||||
- User interface
|
||||
- Docker containerization
|
||||
|
||||
### Planned Enhancements
|
||||
- Batch processing for multiple files
|
||||
- Additional AI model integrations
|
||||
- Advanced error recovery
|
||||
- API rate limiting
|
||||
- Enhanced progress visualization
|
||||
- PDF preview functionality
|
||||
- Custom language model training
|
||||
|
||||
## Technologies Used
|
||||
- **Frontend:** React 18.2.0, axios 0.27.2
|
||||
- **Backend:** Flask, Python
|
||||
- **OCR:** Tesseract, OCRmyPDF, pdf2image
|
||||
- **AI Integration:** OpenAI, Google Gemini, Mistral
|
||||
- **Authentication:** Flask-Login, bcrypt
|
||||
- **Containerization:** Docker, Docker Compose
|
||||
|
||||
## Contributing
|
||||
Contributions are welcome! Please ensure you follow the existing code structure and patterns. All new features should include appropriate error handling and user feedback mechanisms.
|
||||
|
|
|
|||
|
|
@ -3,15 +3,29 @@ import os
|
|||
import uuid
|
||||
import threading
|
||||
import json
|
||||
from flask import Flask, request, jsonify, send_from_directory
|
||||
from flask import Flask, request, jsonify, send_from_directory, session
|
||||
from flask_cors import CORS
|
||||
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
|
||||
from auth import User, verify_user, init_default_users
|
||||
from werkzeug.utils import secure_filename
|
||||
from models import get_models, add_model, get_languages, update_prompt, get_prompt, add_language, delete_prompt, custom_prompts, default_prompts
|
||||
from utils import process_file, translate_file_by_pages, convert_txt_to_pdf
|
||||
import time
|
||||
|
||||
app = Flask(__name__, static_folder="static", static_url_path="")
|
||||
CORS(app)
|
||||
CORS(app, supports_credentials=True)
|
||||
app.secret_key = 'your-secret-key-replace-in-production' # Replace with a secure key in production
|
||||
|
||||
# Initialize Flask-Login
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.get(user_id)
|
||||
|
||||
# Initialize default users
|
||||
init_default_users()
|
||||
|
||||
UPLOAD_FOLDER = "uploads"
|
||||
OUTPUT_FOLDER = "outputs"
|
||||
|
|
@ -57,7 +71,58 @@ def run_translation(job_id, file_path, api, model, target_language, prompt_key):
|
|||
except Exception as e:
|
||||
update_progress(job_id, active_jobs[job_id]["progress"], f"❌ Error: {str(e)}")
|
||||
|
||||
# Login endpoint
|
||||
@app.route('/api/login', methods=['POST'])
|
||||
def login():
|
||||
data = request.get_json()
|
||||
username = data.get('username')
|
||||
password = data.get('password')
|
||||
remember = data.get('remember', False)
|
||||
|
||||
if not username or not password:
|
||||
return jsonify({"error": "Missing username or password"}), 400
|
||||
|
||||
if verify_user(username, password):
|
||||
user = User(username)
|
||||
login_user(user, remember=remember)
|
||||
return jsonify({"message": "Login successful", "username": username})
|
||||
|
||||
return jsonify({"error": "Invalid credentials"}), 401
|
||||
|
||||
# Logout endpoint
|
||||
@app.route('/api/logout', methods=['POST'])
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
return jsonify({"message": "Logout successful"})
|
||||
|
||||
# Get current user
|
||||
@app.route('/api/user', methods=['GET'])
|
||||
@login_required
|
||||
def get_user():
|
||||
return jsonify({
|
||||
"username": current_user.username,
|
||||
"authenticated": True
|
||||
})
|
||||
|
||||
@app.route('/api/change-password', methods=['POST'])
|
||||
@login_required
|
||||
def change_password_endpoint():
|
||||
data = request.get_json()
|
||||
current_password = data.get('current_password')
|
||||
new_password = data.get('new_password')
|
||||
|
||||
if not current_password or not new_password:
|
||||
return jsonify({"error": "Missing current or new password"}), 400
|
||||
|
||||
from auth import change_password
|
||||
if change_password(current_user.username, current_password, new_password):
|
||||
return jsonify({"message": "Password changed successfully"})
|
||||
else:
|
||||
return jsonify({"error": "Current password is incorrect"}), 401
|
||||
|
||||
@app.route('/api/upload', methods=['POST'])
|
||||
@login_required
|
||||
def upload_file():
|
||||
if 'file' not in request.files:
|
||||
return jsonify({"error": "No file found"}), 400
|
||||
|
|
@ -87,6 +152,7 @@ def upload_file():
|
|||
return jsonify({"message": "File uploaded, processing started", "job_id": job_id})
|
||||
|
||||
@app.route('/api/progress/<job_id>', methods=['GET'])
|
||||
@login_required
|
||||
def get_progress(job_id):
|
||||
if job_id in active_jobs:
|
||||
return jsonify({
|
||||
|
|
@ -98,6 +164,7 @@ def get_progress(job_id):
|
|||
return jsonify({"error": "Job not found"}), 404
|
||||
|
||||
@app.route('/api/stop/<job_id>', methods=['POST'])
|
||||
@login_required
|
||||
def stop_job(job_id):
|
||||
if job_id in active_jobs:
|
||||
active_jobs[job_id]["cancelled"] = True
|
||||
|
|
@ -107,6 +174,7 @@ def stop_job(job_id):
|
|||
return jsonify({"error": "Job not found"}), 404
|
||||
|
||||
@app.route('/api/models', methods=['GET'])
|
||||
@login_required
|
||||
def models():
|
||||
api = request.args.get('api')
|
||||
if api:
|
||||
|
|
@ -116,11 +184,13 @@ def models():
|
|||
return jsonify({"error": "Must specify API"}), 400
|
||||
|
||||
@app.route('/api/languages', methods=['GET'])
|
||||
@login_required
|
||||
def languages():
|
||||
langs = get_languages()
|
||||
return jsonify({"languages": langs})
|
||||
|
||||
@app.route('/api/add-model', methods=['POST'])
|
||||
@login_required
|
||||
def add_new_model():
|
||||
data = request.get_json()
|
||||
api = data.get("api")
|
||||
|
|
@ -132,6 +202,7 @@ def add_new_model():
|
|||
|
||||
# Nuevo endpoint para eliminar un modelo
|
||||
@app.route('/api/delete-model', methods=['DELETE'])
|
||||
@login_required
|
||||
def delete_model_endpoint():
|
||||
data = request.get_json()
|
||||
api_name = data.get("api")
|
||||
|
|
@ -145,6 +216,7 @@ def delete_model_endpoint():
|
|||
return jsonify({"error": "Model not found."}), 404
|
||||
|
||||
@app.route('/api/prompts', methods=['GET'])
|
||||
@login_required
|
||||
def get_prompts_endpoint():
|
||||
prompts = {}
|
||||
prompts.update(default_prompts)
|
||||
|
|
@ -153,6 +225,7 @@ def get_prompts_endpoint():
|
|||
return jsonify({"prompts": prompts})
|
||||
|
||||
@app.route('/api/prompts', methods=['POST'])
|
||||
@login_required
|
||||
def update_prompts_endpoint():
|
||||
data = request.get_json()
|
||||
key = data.get("key")
|
||||
|
|
@ -163,6 +236,7 @@ def update_prompts_endpoint():
|
|||
return jsonify({"message": f"Prompt for '{key}' updated."})
|
||||
|
||||
@app.route('/api/prompts/<key>', methods=['DELETE'])
|
||||
@login_required
|
||||
def delete_prompt_endpoint(key):
|
||||
if delete_prompt(key):
|
||||
return jsonify({"message": f"Prompt '{key}' deleted."})
|
||||
|
|
@ -170,15 +244,18 @@ def delete_prompt_endpoint(key):
|
|||
return jsonify({"error": "Prompt not found or cannot be deleted."}), 404
|
||||
|
||||
@app.route('/api/files', methods=['GET'])
|
||||
@login_required
|
||||
def list_files():
|
||||
files = os.listdir(OUTPUT_FOLDER)
|
||||
return jsonify({"files": files})
|
||||
|
||||
@app.route('/api/files/<filename>', methods=['GET'])
|
||||
@login_required
|
||||
def download_file(filename):
|
||||
return send_from_directory(OUTPUT_FOLDER, filename, as_attachment=True)
|
||||
|
||||
@app.route('/api/files/<filename>', methods=['DELETE'])
|
||||
@login_required
|
||||
def delete_file(filename):
|
||||
file_path = os.path.join(OUTPUT_FOLDER, filename)
|
||||
if os.path.exists(file_path):
|
||||
|
|
@ -189,6 +266,7 @@ def delete_file(filename):
|
|||
|
||||
# Endpoint modificado: borrar todos los archivos tanto de la carpeta outputs como de uploads.
|
||||
@app.route('/api/files/all', methods=['DELETE'])
|
||||
@login_required
|
||||
def delete_all_files():
|
||||
try:
|
||||
# Borrar archivos de OUTPUT_FOLDER
|
||||
|
|
@ -206,6 +284,7 @@ def delete_all_files():
|
|||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/api/config', methods=['GET'])
|
||||
@login_required
|
||||
def download_config():
|
||||
from models import custom_prompts, available_models
|
||||
config = {
|
||||
|
|
@ -215,6 +294,7 @@ def download_config():
|
|||
return jsonify(config)
|
||||
|
||||
@app.route('/api/config', methods=['POST'])
|
||||
@login_required
|
||||
def upload_config():
|
||||
if 'config' not in request.files:
|
||||
return jsonify({"error": "No config file provided"}), 400
|
||||
|
|
@ -231,6 +311,7 @@ def upload_config():
|
|||
return jsonify({"error": str(e)}), 400
|
||||
|
||||
@app.route('/api/txttopdf', methods=['POST'])
|
||||
@login_required
|
||||
def txt_to_pdf_endpoint():
|
||||
data = request.get_json()
|
||||
filename = data.get("filename")
|
||||
|
|
|
|||
85
backend/auth.py
Normal file
85
backend/auth.py
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import json
|
||||
import bcrypt
|
||||
from flask_login import UserMixin
|
||||
import os
|
||||
|
||||
class User(UserMixin):
|
||||
def __init__(self, username):
|
||||
self.id = username
|
||||
self.username = username
|
||||
|
||||
@staticmethod
|
||||
def get(user_id):
|
||||
users = load_users()
|
||||
if user_id in users:
|
||||
return User(user_id)
|
||||
return None
|
||||
|
||||
def init_auth_file():
|
||||
"""Initialize auth.json if it doesn't exist"""
|
||||
if not os.path.exists('auth.json'):
|
||||
with open('auth.json', 'w') as f:
|
||||
json.dump({}, f)
|
||||
|
||||
def load_users():
|
||||
"""Load users from auth.json"""
|
||||
init_auth_file()
|
||||
with open('auth.json', 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
def save_users(users):
|
||||
"""Save users to auth.json"""
|
||||
with open('auth.json', 'w') as f:
|
||||
json.dump(users, f, indent=2)
|
||||
|
||||
def hash_password(password):
|
||||
"""Hash a password using bcrypt"""
|
||||
return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
|
||||
|
||||
def verify_password(stored_password_hash, provided_password):
|
||||
"""Verify a password against its hash"""
|
||||
return bcrypt.checkpw(
|
||||
provided_password.encode('utf-8'),
|
||||
stored_password_hash.encode('utf-8')
|
||||
)
|
||||
|
||||
def create_user(username, password):
|
||||
"""Create a new user with hashed password"""
|
||||
users = load_users()
|
||||
if username not in users:
|
||||
users[username] = {
|
||||
'password_hash': hash_password(password)
|
||||
}
|
||||
save_users(users)
|
||||
return True
|
||||
return False
|
||||
|
||||
def verify_user(username, password):
|
||||
"""Verify user credentials"""
|
||||
users = load_users()
|
||||
if username in users:
|
||||
stored_hash = users[username]['password_hash']
|
||||
return verify_password(stored_hash, password)
|
||||
return False
|
||||
|
||||
def change_password(username, current_password, new_password):
|
||||
"""Change user password if current password is correct"""
|
||||
if verify_user(username, current_password):
|
||||
users = load_users()
|
||||
users[username]['password_hash'] = hash_password(new_password)
|
||||
save_users(users)
|
||||
return True
|
||||
return False
|
||||
|
||||
# Initialize default users with secure passwords
|
||||
def init_default_users():
|
||||
"""Initialize default users if they don't exist"""
|
||||
init_auth_file()
|
||||
users = load_users()
|
||||
|
||||
default_users = ['chaulmark', 'ekragh']
|
||||
default_password = 'changeme123' # Temporary password that users should change
|
||||
|
||||
for username in default_users:
|
||||
if username not in users:
|
||||
create_user(username, default_password)
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
Flask
|
||||
flask-cors
|
||||
pytesseract
|
||||
Pillow
|
||||
pdf2image
|
||||
google-genai
|
||||
openai
|
||||
mistralai
|
||||
beautifulsoup4
|
||||
Flask==2.3.3
|
||||
Flask-Login==0.6.3
|
||||
flask-cors==4.0.0
|
||||
bcrypt==4.1.2
|
||||
pytesseract==0.3.10
|
||||
ocrmypdf==14.4.0
|
||||
pdf2image==1.16.3
|
||||
Pillow==10.0.0
|
||||
google-genai==0.2.0
|
||||
openai==1.3.0
|
||||
mistralai==0.0.7
|
||||
beautifulsoup4==4.12.2
|
||||
|
|
|
|||
23
cline_docs/activeContext.md
Normal file
23
cline_docs/activeContext.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Active Context
|
||||
|
||||
## Current Work
|
||||
- Implementing authentication system for DocuLens
|
||||
- Securing all API endpoints
|
||||
- Adding user management functionality
|
||||
|
||||
## Recent Changes
|
||||
- Added Flask-Login for backend authentication
|
||||
- Created auth.py for user management
|
||||
- Added Login component to frontend
|
||||
- Protected all API routes
|
||||
- Added session management
|
||||
- Implemented user authentication flow
|
||||
|
||||
## Next Steps
|
||||
1. Test authentication system thoroughly
|
||||
2. Add password change functionality
|
||||
3. Consider adding:
|
||||
- Password reset capability
|
||||
- Account lockout after failed attempts
|
||||
- Session timeout settings
|
||||
4. Update documentation with authentication details
|
||||
36
cline_docs/productContext.md
Normal file
36
cline_docs/productContext.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# Product Context
|
||||
|
||||
## Purpose
|
||||
DocuLens is a unified web application that combines Optical Character Recognition (OCR) and Artificial Intelligence (AI) to process and translate documents. It aims to provide an intuitive interface with real-time feedback for document processing tasks.
|
||||
|
||||
## Problems Solved
|
||||
1. Complex document processing made simple through a unified interface
|
||||
2. Multiple processing modes to handle different use cases:
|
||||
- Basic OCR (Tesseract only)
|
||||
- Enhanced OCR (Tesseract + AI correction)
|
||||
- Full AI OCR processing
|
||||
3. Document translation with support for multiple languages
|
||||
4. Real-time progress tracking with visual feedback
|
||||
|
||||
## How It Works
|
||||
1. File Management:
|
||||
- Supports PDF and image uploads
|
||||
- Automatic file naming to prevent overwrites
|
||||
- Bulk file management capabilities
|
||||
|
||||
2. Processing Modes:
|
||||
- OCR Mode: Uses Tesseract for text extraction
|
||||
- OCR + AI Mode: Combines Tesseract with AI correction
|
||||
- AI Mode: Full AI-powered OCR processing
|
||||
|
||||
3. Translation Features:
|
||||
- Page-by-page translation
|
||||
- Support for processed or new files
|
||||
- Progress tracking
|
||||
- Outputs translated text with page markers
|
||||
|
||||
4. Configuration System:
|
||||
- AI model management
|
||||
- Custom prompt configuration
|
||||
- Language settings
|
||||
- Import/export of configurations
|
||||
86
cline_docs/progress.md
Normal file
86
cline_docs/progress.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# Progress Status
|
||||
|
||||
## What Works
|
||||
1. File Management
|
||||
- ✅ File upload system
|
||||
- ✅ Automatic file naming
|
||||
- ✅ File deletion (single and bulk)
|
||||
- ✅ File download functionality
|
||||
|
||||
2. OCR Processing
|
||||
- ✅ Tesseract OCR integration
|
||||
- ✅ AI-enhanced OCR
|
||||
- ✅ Full AI OCR mode
|
||||
- ✅ Progress tracking
|
||||
|
||||
3. Translation
|
||||
- ✅ Page-by-page translation
|
||||
- ✅ Multiple language support
|
||||
- ✅ Progress tracking
|
||||
- ✅ Translation file generation
|
||||
|
||||
4. Configuration
|
||||
- ✅ AI model management
|
||||
- ✅ Custom prompt system
|
||||
- ✅ Language configuration
|
||||
- ✅ Config import/export
|
||||
|
||||
5. UI/UX
|
||||
- ✅ Real-time progress updates
|
||||
- ✅ Emoji status indicators
|
||||
- ✅ Error handling and display
|
||||
- ✅ Responsive design
|
||||
|
||||
6. Authentication
|
||||
- ✅ User authentication system
|
||||
- ✅ Protected API routes
|
||||
- ✅ Session management
|
||||
- ✅ Remember me functionality
|
||||
|
||||
## Current Status
|
||||
- Application is fully functional
|
||||
- Core features implemented
|
||||
- Docker containerization complete
|
||||
- Basic error handling in place
|
||||
- Project rebranded from ocrAI to DocuLens
|
||||
|
||||
## What's Left to Build
|
||||
1. Potential Enhancements
|
||||
- [ ] Batch processing for multiple files
|
||||
- [ ] Additional AI model integrations
|
||||
- [ ] Advanced error recovery
|
||||
- [ ] Password change functionality
|
||||
- [ ] Password reset system
|
||||
- [ ] Account lockout protection
|
||||
- [ ] API rate limiting
|
||||
- [ ] Enhanced progress visualization
|
||||
- [ ] PDF preview functionality
|
||||
- [ ] Custom language model training
|
||||
|
||||
2. Documentation Improvements
|
||||
- [ ] API documentation
|
||||
- [ ] User guide
|
||||
- [ ] Development guide
|
||||
- [ ] Deployment guide
|
||||
|
||||
3. Testing
|
||||
- [ ] Unit tests
|
||||
- [ ] Integration tests
|
||||
- [ ] Load testing
|
||||
- [ ] Security testing
|
||||
|
||||
## Progress Metrics
|
||||
- Core Features: 100% complete
|
||||
- Authentication: 90% complete
|
||||
- Documentation: 75% complete
|
||||
- Testing: 40% complete
|
||||
- Overall Progress: ~85% complete
|
||||
|
||||
## Known Issues
|
||||
- None reported at this time
|
||||
|
||||
## Next Milestone Goals
|
||||
1. Implement batch processing
|
||||
2. Add comprehensive testing suite
|
||||
3. Complete documentation
|
||||
4. Add user authentication
|
||||
84
cline_docs/systemPatterns.md
Normal file
84
cline_docs/systemPatterns.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# System Patterns
|
||||
|
||||
## Architecture Overview
|
||||
The application follows a client-server architecture with containerized deployment and secure authentication:
|
||||
|
||||
### Authentication System
|
||||
- JSON file-based user management
|
||||
- Secure password hashing with bcrypt
|
||||
- Session-based authentication using Flask-Login
|
||||
- Protected API routes with login_required decorator
|
||||
- Remember me functionality for persistent sessions
|
||||
|
||||
### Frontend (React)
|
||||
- Single Page Application (SPA) architecture
|
||||
- Authentication state management
|
||||
- Protected route handling
|
||||
- Component-based structure:
|
||||
- Login: User authentication interface
|
||||
- FileUpload: Handles file input and processing
|
||||
- FileList: Manages processed files
|
||||
- ModelSelector: AI model selection
|
||||
- Configurations: System settings and prompts
|
||||
- ProgressBar: Real-time processing feedback
|
||||
- Notifications: User feedback system
|
||||
- TxtToPdf: Document conversion utility
|
||||
|
||||
### Backend (Flask)
|
||||
- RESTful API architecture
|
||||
- Modular design with separate concerns:
|
||||
- app.py: Main application and route handlers
|
||||
- models.py: Data models and AI model management
|
||||
- utils.py: Processing utilities and helpers
|
||||
|
||||
## Key Technical Decisions
|
||||
|
||||
### Background Processing
|
||||
- Asynchronous job processing using threading
|
||||
- Job tracking system with unique IDs
|
||||
- Real-time progress updates via polling
|
||||
|
||||
### File Management
|
||||
- Secure file handling with unique naming
|
||||
- Separate upload and output directories
|
||||
- Support for multiple file formats
|
||||
|
||||
### AI Integration
|
||||
- Modular AI model system
|
||||
- Support for multiple AI providers:
|
||||
- OpenAI
|
||||
- Gemini
|
||||
- Mistral
|
||||
- Configurable prompts system
|
||||
|
||||
### OCR Processing
|
||||
- Multiple processing modes:
|
||||
- Pure OCR (Tesseract)
|
||||
- Hybrid (OCR + AI)
|
||||
- Full AI OCR
|
||||
- Page-by-page processing for large documents
|
||||
|
||||
### Translation System
|
||||
- Language management system
|
||||
- Page-by-page translation
|
||||
- Progress tracking per page
|
||||
|
||||
### Configuration Management
|
||||
- JSON-based configuration storage
|
||||
- Import/export functionality
|
||||
- Runtime model and prompt updates
|
||||
|
||||
## Error Handling
|
||||
- Comprehensive error catching
|
||||
- User-friendly error messages
|
||||
- Job cancellation support
|
||||
|
||||
## Security Patterns
|
||||
- Secure file uploads with sanitization
|
||||
- CORS configuration with credentials support
|
||||
- Environment-based configuration
|
||||
- Password hashing and verification
|
||||
- Session management and protection
|
||||
- API route protection
|
||||
- Authentication state persistence
|
||||
- Secure cookie handling
|
||||
84
cline_docs/techContext.md
Normal file
84
cline_docs/techContext.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# Technical Context
|
||||
|
||||
## Technology Stack
|
||||
|
||||
### Frontend
|
||||
- **Framework**: React 18.2.0
|
||||
- **Key Dependencies**:
|
||||
- axios 0.27.2: HTTP client for API requests
|
||||
- react-dom 18.2.0: React rendering
|
||||
- react-scripts 5.0.1: Development and build tools
|
||||
|
||||
### Backend
|
||||
- **Framework**: Flask (Python)
|
||||
- **Key Dependencies**:
|
||||
- flask-cors: Cross-origin resource sharing
|
||||
- Tesseract: OCR engine
|
||||
- OCRmyPDF: PDF processing
|
||||
- pdf2image: PDF to image conversion
|
||||
|
||||
### AI Integration
|
||||
- **Supported AI Providers**:
|
||||
- OpenAI
|
||||
- Google Gemini
|
||||
- Mistral
|
||||
|
||||
### Containerization
|
||||
- Docker
|
||||
- Docker Compose
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Prerequisites
|
||||
- Docker
|
||||
- Docker Compose
|
||||
|
||||
### Local Development
|
||||
1. Clone repository
|
||||
2. Build and run with Docker Compose:
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
3. Access application at http://localhost:5015
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
/
|
||||
├── backend/
|
||||
│ ├── app.py # Main Flask application
|
||||
│ ├── models.py # Data models and AI integration
|
||||
│ ├── utils.py # Utility functions
|
||||
│ ├── requirements.txt
|
||||
│ └── static/ # Static assets
|
||||
├── frontend/
|
||||
│ ├── src/
|
||||
│ │ ├── components/ # React components
|
||||
│ │ ├── App.js # Main React component
|
||||
│ │ └── index.js # Entry point
|
||||
│ ├── public/ # Public assets
|
||||
│ └── package.json # Frontend dependencies
|
||||
├── docker-compose.yml # Container orchestration
|
||||
└── Dockerfile # Container definition
|
||||
```
|
||||
|
||||
## Technical Constraints
|
||||
|
||||
### System Requirements
|
||||
- Docker environment for containerization
|
||||
- Sufficient disk space for file processing
|
||||
- Memory for concurrent processing jobs
|
||||
|
||||
### API Limitations
|
||||
- File size limits based on server configuration
|
||||
- Processing time varies with document complexity
|
||||
- Concurrent job limits based on server resources
|
||||
|
||||
### Security Considerations
|
||||
- CORS configured for frontend-backend communication
|
||||
- File upload restrictions and sanitization
|
||||
- Environment variable management for API keys
|
||||
|
||||
### Performance Considerations
|
||||
- Asynchronous processing for large files
|
||||
- Progress tracking for long-running operations
|
||||
- Memory management for concurrent jobs
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
services:
|
||||
webapp:
|
||||
build: .
|
||||
container_name: ocrai
|
||||
container_name: doculens
|
||||
ports:
|
||||
- "5015:5015"
|
||||
volumes:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "ocrai-frontend",
|
||||
"name": "doculens-frontend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/logo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>ocrAI</title>
|
||||
<title>DocuLens</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
|
|
|||
|
|
@ -4,15 +4,133 @@ body {
|
|||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* Login Styles */
|
||||
.login-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.login-box {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.login-box h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.form-group input[type="text"],
|
||||
.form-group input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.form-group.checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.form-group.checkbox input[type="checkbox"] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #dc3545;
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-box button {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.login-box button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.login-box button:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.login-info {
|
||||
margin-top: 1rem;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Main App Styles */
|
||||
.app-container {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Header: Logo and title centered */
|
||||
/* Header: Logo, title, and user info */
|
||||
.app-header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.user-info span {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.logout-button {
|
||||
padding: 5px 10px;
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.logout-button:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
|
|
@ -110,6 +228,97 @@ main {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Change Password Styles */
|
||||
.change-password-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.change-password-box {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.change-password-box h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
color: #28a745;
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.button-group button {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.button-group button[type="submit"] {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.button-group button[type="submit"]:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.button-group button[type="button"] {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.button-group button[type="button"]:hover {
|
||||
background-color: #545b62;
|
||||
}
|
||||
|
||||
.button-group button:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Change Password Button in Header */
|
||||
.change-password-button {
|
||||
padding: 5px 10px;
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: background-color 0.2s;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.change-password-button:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 600px) {
|
||||
.app-header {
|
||||
|
|
@ -121,4 +330,7 @@ main {
|
|||
.file-group {
|
||||
width: 90%;
|
||||
}
|
||||
.button-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,55 @@
|
|||
// frontend/src/App.js
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
import FileUpload from './components/FileUpload';
|
||||
import Login from './components/Login';
|
||||
import FileList from './components/FileList';
|
||||
import Configurations from './components/Configurations';
|
||||
import TxtToPdf from './components/TxtToPdf';
|
||||
import Notifications from './components/Notifications';
|
||||
import ChangePassword from './components/ChangePassword';
|
||||
|
||||
function App() {
|
||||
const [activeTab, setActiveTab] = useState('ocrAI');
|
||||
const [activeTab, setActiveTab] = useState('DocuLens');
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const [username, setUsername] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [showChangePassword, setShowChangePassword] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
checkAuthStatus();
|
||||
}, []);
|
||||
|
||||
const checkAuthStatus = async () => {
|
||||
try {
|
||||
const response = await axios.get('/api/user', { withCredentials: true });
|
||||
if (response.data.authenticated) {
|
||||
setIsAuthenticated(true);
|
||||
setUsername(response.data.username);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Not authenticated');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginSuccess = (username) => {
|
||||
setIsAuthenticated(true);
|
||||
setUsername(username);
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await axios.post('/api/logout', {}, { withCredentials: true });
|
||||
setIsAuthenticated(false);
|
||||
setUsername('');
|
||||
setActiveTab('DocuLens');
|
||||
} catch (error) {
|
||||
console.error('Logout failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleJobCompleted = (notification) => {
|
||||
setNotifications(prev => [...prev, notification]);
|
||||
|
|
@ -18,20 +59,35 @@ function App() {
|
|||
setNotifications([]);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <Login onLoginSuccess={handleLoginSuccess} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app-container">
|
||||
<header className="app-header">
|
||||
<div className="user-info">
|
||||
<span>Welcome, {username}</span>
|
||||
<button onClick={() => setShowChangePassword(true)} className="change-password-button">
|
||||
Change Password
|
||||
</button>
|
||||
<button onClick={handleLogout} className="logout-button">Logout</button>
|
||||
</div>
|
||||
<div className="logo-container">
|
||||
<img src="/logo.png" alt="Logo" className="app-logo" />
|
||||
<h1 className="app-title">ocrAI</h1>
|
||||
<h1 className="app-title">DocuLens</h1>
|
||||
</div>
|
||||
</header>
|
||||
<nav className="app-nav">
|
||||
<button
|
||||
onClick={() => setActiveTab('ocrAI')}
|
||||
className={activeTab === 'ocrAI' ? 'active tab-processing' : 'tab-processing'}
|
||||
onClick={() => setActiveTab('DocuLens')}
|
||||
className={activeTab === 'DocuLens' ? 'active tab-processing' : 'tab-processing'}
|
||||
>
|
||||
💡 ocrAI
|
||||
💡 DocuLens
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('files')}
|
||||
|
|
@ -53,12 +109,15 @@ function App() {
|
|||
</button>
|
||||
</nav>
|
||||
<main>
|
||||
{activeTab === 'ocrAI' && <FileUpload onJobCompleted={handleJobCompleted} />}
|
||||
{activeTab === 'DocuLens' && <FileUpload onJobCompleted={handleJobCompleted} />}
|
||||
{activeTab === 'files' && <FileList />}
|
||||
{activeTab === 'configurations' && <Configurations />}
|
||||
{activeTab === 'txttopdf' && <TxtToPdf />}
|
||||
</main>
|
||||
<Notifications notifications={notifications} onClear={clearNotifications} />
|
||||
{showChangePassword && (
|
||||
<ChangePassword onClose={() => setShowChangePassword(false)} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
104
frontend/src/components/ChangePassword.js
Normal file
104
frontend/src/components/ChangePassword.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import React, { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
function ChangePassword({ onClose }) {
|
||||
const [currentPassword, setCurrentPassword] = useState('');
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [success, setSuccess] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
setSuccess('');
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
setError('New passwords do not match');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
await axios.post('/api/change-password', {
|
||||
current_password: currentPassword,
|
||||
new_password: newPassword
|
||||
}, {
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
setSuccess('Password changed successfully');
|
||||
setCurrentPassword('');
|
||||
setNewPassword('');
|
||||
setConfirmPassword('');
|
||||
|
||||
// Close the form after a short delay
|
||||
setTimeout(() => {
|
||||
if (onClose) onClose();
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
setError(err.response?.data?.error || 'Failed to change password');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="change-password-container">
|
||||
<div className="change-password-box">
|
||||
<h2>Change Password</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="form-group">
|
||||
<label htmlFor="currentPassword">Current Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="currentPassword"
|
||||
value={currentPassword}
|
||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||
required
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="newPassword">New Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="newPassword"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
required
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="confirmPassword">Confirm New Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="confirmPassword"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
required
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
{error && <div className="error-message">{error}</div>}
|
||||
{success && <div className="success-message">{success}</div>}
|
||||
<div className="button-group">
|
||||
<button type="submit" disabled={isLoading}>
|
||||
{isLoading ? 'Changing...' : 'Change Password'}
|
||||
</button>
|
||||
{onClose && (
|
||||
<button type="button" onClick={onClose} disabled={isLoading}>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChangePassword;
|
||||
87
frontend/src/components/Login.js
Normal file
87
frontend/src/components/Login.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import React, { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
function Login({ onLoginSuccess }) {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [remember, setRemember] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await axios.post('/api/login', {
|
||||
username,
|
||||
password,
|
||||
remember
|
||||
}, {
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
if (response.data.username) {
|
||||
onLoginSuccess(response.data.username);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.response?.data?.error || 'Login failed. Please try again.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="login-container">
|
||||
<div className="login-box">
|
||||
<h2>Welcome to DocuLens</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="form-group">
|
||||
<label htmlFor="username">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
required
|
||||
autoComplete="username"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="password">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group checkbox">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={remember}
|
||||
onChange={(e) => setRemember(e.target.checked)}
|
||||
/>
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
{error && <div className="error-message">{error}</div>}
|
||||
<button type="submit" disabled={isLoading}>
|
||||
{isLoading ? 'Logging in...' : 'Log In'}
|
||||
</button>
|
||||
</form>
|
||||
<p className="login-info">
|
||||
Default password: changeme123
|
||||
<br />
|
||||
Please change your password after first login.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
||||
Loading…
Add table
Reference in a new issue