""" FastAPI Server-Sent Events (SSE) Streaming Server für Math & Handwritten OCR Studio Pro. Ermöglicht seitenweises Echtzeit-Streaming von OCR-Ergebnissen an das iPadOS Frontend. """ import os import sys import json import asyncio from fastapi import FastAPI, File, UploadFile, Form, HTTPException from fastapi.responses import StreamingResponse, JSONResponse from fastapi.middleware.cors import CORSMiddleware import fitz # Importiere Core-Pipeline sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from gradio_api import process_pdf, get_gemini_api_keys app = FastAPI(title="Math & Handwritten OCR Studio Pro API", version="2.6.0") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get("/health") async def health_check(): return {"status": "ok", "version": "2.6.0", "streaming": "SSE active"} @app.post("/api/stream_ocr") async def stream_ocr( file: UploadFile = File(...), mode: str = Form("Schnell (Gemini Full-Page)"), api_key: str = Form("") ): """ Asynchroner SSE-Endpunkt: Streamt OCR-Ergebnisse seitenweise als Server-Sent Events. """ if not file.filename.endswith(".pdf"): raise HTTPException(status_code=400, detail="Nur PDF-Dateien werden unterstützt.") # Speichere Upload temporär temp_dir = "temp_uploads" os.makedirs(temp_dir, exist_ok=True) temp_path = os.path.join(temp_dir, file.filename) with open(temp_path, "wb") as f: f.write(await file.read()) async def event_generator(): try: # Sende Initial Event yield f"data: {json.dumps({'event': 'start', 'file': file.filename})}\n\n" await asyncio.sleep(0.01) # Verarbeite PDF in der Pipeline out_pdf_path = await asyncio.to_thread( process_pdf, temp_path, mode, False, # smart_skip api_key if api_key.strip() else None ) # Lese generiertes PDF für Vorschau-Seiten doc = fitz.open(out_pdf_path) num_pages = len(doc) for p_idx in range(num_pages): page = doc[p_idx] text = page.get_text() payload = { 'event': 'page_completed', 'page': p_idx + 1, 'total_pages': num_pages, 'text_snippet': text[:300], 'pdf_url': out_pdf_path if p_idx == num_pages - 1 else None } yield f"data: {json.dumps(payload)}\n\n" await asyncio.sleep(0.05) doc.close() yield f"data: {json.dumps({'event': 'finished', 'output_pdf': out_pdf_path})}\n\n" except Exception as err: yield f"data: {json.dumps({'event': 'error', 'message': str(err)})}\n\n" return StreamingResponse(event_generator(), media_type="text/event-stream") if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=7860)