yasmine110's picture
Update backend/main.py
b8db779 verified
raw
history blame
9.36 kB
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 init_translator
translator = init_translator('en') # Initialise avec l'anglais par défaut
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 init_translator
translator = init_translator('en') # Vérifie le modèle anglais
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,
"supported_languages": ["en", "es", "ar", "de"] # Liste des langues supportées
}
@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
)