yasmine110's picture
Update backend/main.py
4604cbe verified
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="""
<div style='text-align:center; padding:2rem'>
<h1>404 - Ressource non trouvée</h1>
<p>La page que vous cherchez n'existe pas ou a été déplacée</p>
<a href="/">Retour à l'accueil</a>
</div>
""",
status_code=404
)