From ee77b2989f69164aedc3ec9bd2421b0f44cd4ed7 Mon Sep 17 00:00:00 2001
From: Drakonis96 <41027003+Drakonis96@users.noreply.github.com>
Date: Mon, 10 Mar 2025 11:21:21 +0100
Subject: [PATCH] 0.5.1
App upload
---
Dockerfile | 38 +++
README.md | 81 +++++
backend/.DS_Store | Bin 0 -> 6148 bytes
backend/app.py | 257 ++++++++++++++++
backend/models.py | 53 ++++
backend/requirements.txt | 9 +
backend/static/logo.png | Bin 0 -> 603755 bytes
backend/utils.py | 358 ++++++++++++++++++++++
docker-compose.yml | 11 +
frontend/.DS_Store | Bin 0 -> 6148 bytes
frontend/package.json | 17 +
frontend/public/index.html | 13 +
frontend/src/App.css | 124 ++++++++
frontend/src/App.js | 66 ++++
frontend/src/components/Configurations.js | 256 ++++++++++++++++
frontend/src/components/FileList.js | 94 ++++++
frontend/src/components/FileUpload.js | 198 ++++++++++++
frontend/src/components/ModelSelector.js | 17 +
frontend/src/components/Notifications.js | 66 ++++
frontend/src/components/ProgressBar.js | 37 +++
frontend/src/components/TxtToPdf.js | 68 ++++
frontend/src/index.js | 11 +
22 files changed, 1774 insertions(+)
create mode 100644 Dockerfile
create mode 100644 README.md
create mode 100644 backend/.DS_Store
create mode 100644 backend/app.py
create mode 100644 backend/models.py
create mode 100644 backend/requirements.txt
create mode 100644 backend/static/logo.png
create mode 100644 backend/utils.py
create mode 100644 docker-compose.yml
create mode 100644 frontend/.DS_Store
create mode 100644 frontend/package.json
create mode 100644 frontend/public/index.html
create mode 100644 frontend/src/App.css
create mode 100644 frontend/src/App.js
create mode 100644 frontend/src/components/Configurations.js
create mode 100644 frontend/src/components/FileList.js
create mode 100644 frontend/src/components/FileUpload.js
create mode 100644 frontend/src/components/ModelSelector.js
create mode 100644 frontend/src/components/Notifications.js
create mode 100644 frontend/src/components/ProgressBar.js
create mode 100644 frontend/src/components/TxtToPdf.js
create mode 100644 frontend/src/index.js
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..b1480cb
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,38 @@
+# Stage 1: Build the frontend
+FROM node:16-alpine as frontend-build
+WORKDIR /app/frontend
+COPY frontend/package.json frontend/package-lock.json* ./
+RUN npm install
+COPY frontend/ .
+RUN npm run build
+
+# Stage 2: Build the backend
+FROM python:3.9-slim
+WORKDIR /app
+
+# Install system dependencies
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ build-essential \
+ poppler-utils \
+ tesseract-ocr \
+ ghostscript \
+ libffi-dev \
+ libssl-dev \
+ && rm -rf /var/lib/apt/lists/*
+
+# Install OCRmyPDF
+RUN pip install --no-cache-dir ocrmypdf
+
+# Copy backend code
+COPY backend/ /app/backend/
+
+# Copy built frontend assets into the backend's static folder
+COPY --from=frontend-build /app/frontend/build/ /app/backend/static/
+
+# Set working directory to backend and install Python dependencies
+WORKDIR /app/backend
+COPY backend/requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+
+EXPOSE 5015
+CMD ["python", "app.py"]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8f0da9c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,81 @@
+
+
+
+
+# ocrAI 🤖
+
+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!).
+
+## Key Features
+
+- **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.
+
+- **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** 🌐
+ - 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** ⚙️
+ - 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).
+
+## How to Use the Application
+
+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.
+
+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.
+
+3. **View Processed Files:**
+ - Go to the **Processed Files** tab.
+ - Download or delete files (with confirmation prompts).
+
+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.
+
+## How to Run ocrAI
+
+### Prerequisites
+- Docker
+- Docker Compose
+
+### Build and Run
+
+```bash
+docker-compose up --build
+Then, open your browser at http://localhost:5015 to start using ocrAI.
+
+Technologies Used
+Frontend: React, Axios
+Backend: Flask, Python
+OCR: Tesseract, pdf2image, OCRmyPDF
+AI: OpenAI, Gemini, Mistral APIs
+Containerization: Docker, Docker Compose
\ No newline at end of file
diff --git a/backend/.DS_Store b/backend/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..79b22124f752124be8bc830f0ff4801f3d9bcfd0
GIT binary patch
literal 6148
zcmeHKO>fgM7=FE#Heo`_fk|*cio~^y7RJQHC6saCN)Q|Xm1Lbrs1O#_jAIwqe#3D^o*vX-tw58c{~&
znT+oZMhEdzs?ay5<;wnm{>80Ev_}*4{Ry$D%8gOVnTKap^o6Ph|67CKMr4KgRNx;s
zVt<_%8_&wZ>{~zLB1_Av*ZV29wp&-)9jD{$IB&xbau!xmH7y6xF@oDO6yuy7Ir
zCC#N!OMSYs)Q(!P^xTDBjbDz!;w4eQ{EeKQOEO+NOsjxZ;NleE{lSMb1_o=5YU@Cv
zt^mLms+A$npAF3M4F(2ljc9=hO$BPIFjowr>8STjTwt))sOcokgnj;
zSK%ZAjW)FkSOwM<*w#gl&;O@?zyGh3Y{@EM75J|d5Ur!|Xn-l1vvp%~eAfE#cW^e2
ot2HVL5_24@3?Ice;L6bEa|Rd~tTmzrW', methods=['GET'])
+def get_progress(job_id):
+ if job_id in active_jobs:
+ return jsonify({
+ "progress": active_jobs[job_id]["progress"],
+ "status": active_jobs[job_id]["status"],
+ "result": active_jobs[job_id]["result"]
+ })
+ else:
+ return jsonify({"error": "Job not found"}), 404
+
+@app.route('/api/stop/', methods=['POST'])
+def stop_job(job_id):
+ if job_id in active_jobs:
+ active_jobs[job_id]["cancelled"] = True
+ update_progress(job_id, active_jobs[job_id]["progress"], "⏹️ Cancelled")
+ return jsonify({"message": "Job cancellation requested"})
+ else:
+ return jsonify({"error": "Job not found"}), 404
+
+@app.route('/api/models', methods=['GET'])
+def models():
+ api = request.args.get('api')
+ if api:
+ models_list = get_models(api)
+ return jsonify({"models": models_list})
+ else:
+ return jsonify({"error": "Must specify API"}), 400
+
+@app.route('/api/languages', methods=['GET'])
+def languages():
+ langs = get_languages()
+ return jsonify({"languages": langs})
+
+@app.route('/api/add-model', methods=['POST'])
+def add_new_model():
+ data = request.get_json()
+ api = data.get("api")
+ model = data.get("model")
+ if not api or not model:
+ return jsonify({"error": "Missing api or model"}), 400
+ add_model(api, model)
+ return jsonify({"message": f"Model {model} added for {api}"}), 200
+
+# Nuevo endpoint para eliminar un modelo
+@app.route('/api/delete-model', methods=['DELETE'])
+def delete_model_endpoint():
+ data = request.get_json()
+ api_name = data.get("api")
+ model_name = data.get("model")
+ if not api_name or not model_name:
+ return jsonify({"error": "Missing api or model"}), 400
+ from models import delete_model
+ if delete_model(api_name, model_name):
+ return jsonify({"message": f"Model {model_name} deleted from {api_name}."}), 200
+ else:
+ return jsonify({"error": "Model not found."}), 404
+
+@app.route('/api/prompts', methods=['GET'])
+def get_prompts_endpoint():
+ prompts = {}
+ prompts.update(default_prompts)
+ from models import custom_prompts
+ prompts.update(custom_prompts)
+ return jsonify({"prompts": prompts})
+
+@app.route('/api/prompts', methods=['POST'])
+def update_prompts_endpoint():
+ data = request.get_json()
+ key = data.get("key")
+ new_prompt = data.get("prompt")
+ if not key or not new_prompt:
+ return jsonify({"error": "Missing key or prompt"}), 400
+ update_prompt(key, new_prompt)
+ return jsonify({"message": f"Prompt for '{key}' updated."})
+
+@app.route('/api/prompts/', methods=['DELETE'])
+def delete_prompt_endpoint(key):
+ if delete_prompt(key):
+ return jsonify({"message": f"Prompt '{key}' deleted."})
+ else:
+ return jsonify({"error": "Prompt not found or cannot be deleted."}), 404
+
+@app.route('/api/files', methods=['GET'])
+def list_files():
+ files = os.listdir(OUTPUT_FOLDER)
+ return jsonify({"files": files})
+
+@app.route('/api/files/', methods=['GET'])
+def download_file(filename):
+ return send_from_directory(OUTPUT_FOLDER, filename, as_attachment=True)
+
+@app.route('/api/files/', methods=['DELETE'])
+def delete_file(filename):
+ file_path = os.path.join(OUTPUT_FOLDER, filename)
+ if os.path.exists(file_path):
+ os.remove(file_path)
+ return jsonify({"message": "File deleted"}), 200
+ else:
+ return jsonify({"error": "File not found"}), 404
+
+# Endpoint modificado: borrar todos los archivos tanto de la carpeta outputs como de uploads.
+@app.route('/api/files/all', methods=['DELETE'])
+def delete_all_files():
+ try:
+ # Borrar archivos de OUTPUT_FOLDER
+ output_files = os.listdir(OUTPUT_FOLDER)
+ for file in output_files:
+ file_path = os.path.join(OUTPUT_FOLDER, file)
+ os.remove(file_path)
+ # Borrar archivos de UPLOAD_FOLDER
+ upload_files = os.listdir(UPLOAD_FOLDER)
+ for file in upload_files:
+ file_path = os.path.join(UPLOAD_FOLDER, file)
+ os.remove(file_path)
+ return jsonify({"message": "All files in outputs and uploads deleted"}), 200
+ except Exception as e:
+ return jsonify({"error": str(e)}), 500
+
+@app.route('/api/config', methods=['GET'])
+def download_config():
+ from models import custom_prompts, available_models
+ config = {
+ "custom_prompts": custom_prompts,
+ "available_models": available_models
+ }
+ return jsonify(config)
+
+@app.route('/api/config', methods=['POST'])
+def upload_config():
+ if 'config' not in request.files:
+ return jsonify({"error": "No config file provided"}), 400
+ file = request.files['config']
+ try:
+ config_data = json.load(file)
+ from models import custom_prompts, available_models
+ custom_prompts.clear()
+ custom_prompts.update(config_data.get("custom_prompts", {}))
+ available_models.clear()
+ available_models.update(config_data.get("available_models", {}))
+ return jsonify({"message": "Configuration updated successfully"}), 200
+ except Exception as e:
+ return jsonify({"error": str(e)}), 400
+
+@app.route('/api/txttopdf', methods=['POST'])
+def txt_to_pdf_endpoint():
+ data = request.get_json()
+ filename = data.get("filename")
+ if not filename:
+ return jsonify({"error": "Missing filename parameter"}), 400
+ txt_path = os.path.join(OUTPUT_FOLDER, filename)
+ if not os.path.exists(txt_path):
+ return jsonify({"error": "File not found"}), 404
+ try:
+ pdf_path = convert_txt_to_pdf(txt_path)
+ return jsonify({"message": "TXT to PDF conversion completed", "pdf_file": os.path.basename(pdf_path)})
+ except Exception as e:
+ return jsonify({"error": str(e)}), 500
+
+@app.route('/', defaults={'path': ''})
+@app.route('/')
+def serve(path):
+ if path != "" and os.path.exists(os.path.join(app.static_folder, path)):
+ return send_from_directory(app.static_folder, path)
+ else:
+ return send_from_directory(app.static_folder, 'index.html')
+
+if __name__ == '__main__':
+ app.run(host='0.0.0.0', port=5015)
diff --git a/backend/models.py b/backend/models.py
new file mode 100644
index 0000000..f74607b
--- /dev/null
+++ b/backend/models.py
@@ -0,0 +1,53 @@
+# backend/models.py
+default_prompts = {
+ "ocr_correction": "Correct the following text. Begin your answer directly with the corrected text. Format the result as HTML, indicating where each page begins, each paragraph begins, and marking titles with the appropriate HTML tags (e.g.,