from fastapi import FastAPI, UploadFile, File, HTTPException from fastapi.responses import HTMLResponse, Response from fastapi.staticfiles import StaticFiles from pathlib import Path import os from backend.utils import extract_text_from_file, translate_text import io from docx import Document import asyncio from typing import Optional import logging from datetime import datetime app = FastAPI( title="DocTranslator Pro", description="Service avancé de traduction de documents avec gestion améliorée des erreurs", version="2.0.0" ) # Configuration améliorée MAX_FILE_SIZE = 15 * 1024 * 1024 # 15 Mo (augmenté de 10 Mo) MAX_TEXT_LENGTH = 75000 # 75k caractères (augmenté de 50k) PROCESSING_TIMEOUT = 600 # 10 minutes (augmenté de 5 minutes) SUPPORTED_FORMATS = ('.pdf', '.docx', '.xlsx', '.pptx') # Ajout de PPTX LOG_FILE = "translation_logs.log" # Configuration des fichiers statiques app.mount("/static", StaticFiles(directory="frontend"), name="static") # Configuration du logging avancé logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(LOG_FILE), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) class TranslationLogger: @staticmethod def log_transaction(filename: str, char_count: int, status: str): with open(LOG_FILE, "a") as f: f.write(f"{datetime.now().isoformat()}|{filename}|{char_count}|{status}\n") @app.on_event("startup") async def startup_event(): """Vérification améliorée du modèle au démarrage""" from backend.utils import translator if translator is None: logger.error("❌ ERREUR CRITIQUE: Le modèle de traduction n'a pas pu être chargé!") raise RuntimeError("Échec du chargement du modèle") else: logger.info("✅ Modèle de traduction initialisé avec succès") logger.info(f"⚙️ Configuration - Taille max fichier: {MAX_FILE_SIZE/1024/1024}MB") logger.info(f"⚙️ Configuration - Texte max: {MAX_TEXT_LENGTH} caractères") @app.get("/", response_class=HTMLResponse) async def serve_frontend(): """Endpoint amélioré pour servir l'interface frontend""" try: with open(os.path.join("frontend", "index.html"), "r", encoding="utf-8") as f: return HTMLResponse(content=f.read(), status_code=200) except Exception as e: logger.error(f"Erreur de chargement du frontend: {str(e)}") raise HTTPException( status_code=500, detail="Erreur de chargement de l'interface. Contactez l'administrateur." ) async def process_file(file: UploadFile, target_lang: str) -> dict: """ Fonction de traitement améliorée avec gestion d'erreur granulaire """ # Vérification de la taille if file.size > MAX_FILE_SIZE: error_msg = f"Fichier trop volumineux (> {MAX_FILE_SIZE/1024/1024:.1f} Mo)" logger.warning(error_msg) raise HTTPException(status_code=413, detail=error_msg) # Vérification du type de fichier if not file.filename.lower().endswith(SUPPORTED_FORMATS): error_msg = f"Format non supporté. Formats acceptés: {', '.join(SUPPORTED_FORMATS)}" logger.warning(error_msg) raise HTTPException(status_code=415, detail=error_msg) # Lecture du contenu avec gestion d'erreur try: contents = await file.read() except Exception as e: error_msg = f"Erreur de lecture du fichier: {str(e)}" logger.error(error_msg) raise HTTPException(status_code=422, detail=error_msg) # Extraction du texte extracted_text = extract_text_from_file(contents, file.filename) if extracted_text.startswith("Erreur:"): error_msg = extracted_text.replace("Erreur:", "").strip() logger.error(f"Erreur extraction: {error_msg}") raise HTTPException(status_code=422, detail=error_msg) if len(extracted_text) > MAX_TEXT_LENGTH: error_msg = f"Texte trop long ({len(extracted_text)} > {MAX_TEXT_LENGTH} caractères)" logger.warning(error_msg) raise HTTPException(status_code=413, detail=error_msg) # Traduction avec suivi logger.info(f"Début traduction - {len(extracted_text)} caractères") translated_text = translate_text(extracted_text, target_lang) if translated_text.startswith("Erreur:"): error_detail = translated_text.replace("Erreur:", "").strip() TranslationLogger.log_transaction(file.filename, len(extracted_text), "failed") logger.error(f"Échec traduction: {error_detail}") raise HTTPException(status_code=500, detail=error_detail) # Succès TranslationLogger.log_transaction(file.filename, len(extracted_text), "success") logger.info(f"Traduction réussie - {len(extracted_text)}→{len(translated_text)} caractères") return { "filename": file.filename, "original_text": extracted_text, "translated_text": translated_text, "char_count": len(extracted_text), "translation_ratio": f"{len(translated_text)/len(extracted_text):.2f}" if extracted_text else "0" } @app.post("/api/translate") async def translate_endpoint( file: UploadFile = File(...), target_lang: str = "en" ): """ Endpoint amélioré avec gestion complète des erreurs """ try: # Traitement avec timeout configurable result = await asyncio.wait_for( process_file(file, target_lang), timeout=PROCESSING_TIMEOUT ) return { "status": "success", "message": "Traduction terminée avec succès", "processing_time": f"{PROCESSING_TIMEOUT}s max", **result } except asyncio.TimeoutError: error_msg = "Temps de traitement dépassé. Veuillez réduire la taille du document." logger.error(error_msg) raise HTTPException(status_code=408, detail=error_msg) except HTTPException: raise # Already properly formatted except Exception as e: error_msg = f"Erreur inattendue: {str(e)}" logger.critical(error_msg) raise HTTPException(status_code=500, detail=error_msg) @app.post("/api/download_translated") async def download_translated( file: UploadFile = File(...), target_lang: str = "en" ): """ Endpoint de téléchargement amélioré avec gestion d'erreur """ try: # Traitement avec timeout result = await asyncio.wait_for( process_file(file, target_lang), timeout=PROCESSING_TIMEOUT ) # Génération du DOCX avec gestion d'erreur try: doc = Document() doc.add_paragraph(result["translated_text"]) file_stream = io.BytesIO() doc.save(file_stream) file_stream.seek(0) return Response( content=file_stream.getvalue(), media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document", headers={ "Content-Disposition": f"attachment; filename=TRAD_{result['filename']}", "X-File-Size": str(len(file_stream.getvalue())), "X-Char-Count": str(result["char_count"]), "X-Translation-Ratio": result["translation_ratio"] } ) except Exception as e: error_msg = f"Erreur de génération DOCX: {str(e)}" logger.error(error_msg) raise HTTPException(status_code=500, detail=error_msg) except asyncio.TimeoutError: error_msg = "Temps de génération dépassé. Document trop volumineux." logger.error(error_msg) raise HTTPException(status_code=408, detail=error_msg) except HTTPException: raise except Exception as e: error_msg = f"Erreur de téléchargement: {str(e)}" logger.error(error_msg) raise HTTPException(status_code=500, detail=error_msg) @app.get("/api/health") async def health_check(): """Endpoint de vérification de santé amélioré""" from backend.utils import translator return { "status": "OK" if translator else "ERROR", "model_loaded": bool(translator), "max_file_size": f"{MAX_FILE_SIZE/1024/1024:.1f} MB", "max_text_length": f"{MAX_TEXT_LENGTH} caractères", "supported_formats": SUPPORTED_FORMATS, "log_file": LOG_FILE } @app.exception_handler(404) async def not_found_handler(request, exc): """Gestionnaire d'erreur 404 amélioré""" logger.warning(f"404 - {request.url}") return HTMLResponse( content="""

404 - Ressource non trouvée

La page que vous cherchez n'existe pas ou a été déplacée

Retour à l'accueil
""", status_code=404 )