File: //proc/thread-self/root/opt/mistral/app.py
"""Simple Flask web interface for Mistral OCR
Features
--------
* Upload an arbitrary number of image files via browser (multi‑file input).
* Each image is sent to Mistral’s OCR endpoint (`https://api.mistral.ai/v1/ocr`).
* OCR output is written to `results/<original‑name>.json` (one JSON file per image).
* Uploaded images are saved in `uploads/` for traceability.
Prerequisites
-------------
$ pip install flask requests python-dotenv
You also need your Mistral API key available as an environment variable:
$ export MISTRAL_API_KEY="sk‑..."
Run the app locally on http://127.0.0.1:5000/:
$ python app.py
"""
import base64
import json
import mimetypes
import os
import uuid
from pathlib import Path
import requests
from flask import Flask, render_template_string, request, send_from_directory
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
MISTRAL_API_KEY = os.getenv("MISTRAL_API_KEY")
if not MISTRAL_API_KEY:
raise EnvironmentError("Please set the MISTRAL_API_KEY environment variable")
UPLOAD_DIR = Path("uploads")
RESULT_DIR = Path("results")
UPLOAD_DIR.mkdir(exist_ok=True)
RESULT_DIR.mkdir(exist_ok=True)
HTML_FORM = """
<!doctype html>
<title>Mistral OCR Uploader</title>
<h1>Upload image(s) for OCR</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="images" multiple accept="image/*">
<input type="submit" value="Upload">
</form>
{% if results %}
<h2>Processed Files</h2>
<ul>
{% for name in results %}
<li><a href="{{ url_for('download_result', filename=name) }}">{{ name }}</a></li>
{% endfor %}
</ul>
{% endif %}
"""
app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = 100 * 1024 * 1024 # 100 MB per request
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _image_to_data_url(path: Path) -> str:
"""Return a data: URL containing base64‑encoded image data."""
mime_type, _ = mimetypes.guess_type(path)
with path.open("rb") as f:
encoded = base64.b64encode(f.read()).decode()
return f"data:{mime_type or 'application/octet-stream'};base64,{encoded}"
def _call_mistral_ocr(image_path: Path) -> dict:
"""Invoke Mistral OCR and return the parsed JSON response."""
url = "https://api.mistral.ai/v1/ocr"
headers = {
"Authorization": f"Bearer {MISTRAL_API_KEY}",
"Content-Type": "application/json",
}
payload = {
"model": "mistral-ocr-latest",
"document": {
"type": "image_url",
"image_url": _image_to_data_url(image_path),
},
}
resp = requests.post(url, headers=headers, json=payload, timeout=120)
resp.raise_for_status()
return resp.json()
# ---------------------------------------------------------------------------
# Routes
# ---------------------------------------------------------------------------
@app.route("/", methods=["GET"])
def index():
return render_template_string(HTML_FORM, results=[])
@app.route("/upload", methods=["POST"])
def upload():
files = request.files.getlist("images")
processed = []
for fs in files:
if not fs.filename:
continue
unique_name = f"{uuid.uuid4().hex}_{fs.filename}"
save_path = UPLOAD_DIR / unique_name
fs.save(save_path)
try:
ocr_data = _call_mistral_ocr(save_path)
result_name = f"{save_path.stem}.json"
(RESULT_DIR / result_name).write_text(
json.dumps(ocr_data, ensure_ascii=False, indent=2),
encoding="utf-8",
)
processed.append(result_name)
except Exception as exc:
print(f"[ERROR] {fs.filename}: {exc}")
return render_template_string(HTML_FORM, results=processed)
@app.route("/results/<path:filename>")
def download_result(filename):
return send_from_directory(RESULT_DIR, filename, as_attachment=True)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)