File size: 9,032 Bytes
4604cbe
 
751bbae
5ac2c89
6133ae1
751bbae
 
4604cbe
5ac2c89
536f43e
5ac2c89
 
b8f1c3f
 
6133ae1
431a9d7
b8f1c3f
4604cbe
 
431a9d7
6133ae1
4604cbe
 
 
 
 
b8f1c3f
5ac2c89
66cd178
751bbae
6133ae1
4604cbe
b8f1c3f
 
 
 
 
 
 
 
 
 
 
 
4604cbe
b8f1c3f
4604cbe
b8f1c3f
399fd9f
 
4604cbe
 
 
 
 
399fd9f
4604cbe
 
 
399fd9f
6133ae1
399fd9f
4604cbe
399fd9f
 
 
b8f1c3f
 
4604cbe
 
 
 
751bbae
431a9d7
4604cbe
 
 
 
09a0354
4604cbe
 
 
 
 
431a9d7
4604cbe
 
 
5ac2c89
4604cbe
b8f1c3f
 
4604cbe
 
 
 
09a0354
4604cbe
 
09a0354
4604cbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ac2c89
399fd9f
 
5ac2c89
751bbae
 
4604cbe
 
 
751bbae
4604cbe
431a9d7
 
 
 
 
 
4604cbe
 
431a9d7
 
4604cbe
431a9d7
4604cbe
 
 
5ac2c89
4604cbe
751bbae
4604cbe
 
 
536f43e
 
5ac2c89
 
 
 
4604cbe
 
 
536f43e
4604cbe
431a9d7
 
 
 
5ac2c89
4604cbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
09a0354
431a9d7
4604cbe
 
 
 
 
536f43e
4604cbe
 
 
b8f1c3f
 
 
4604cbe
 
b8f1c3f
4604cbe
 
 
 
 
 
b8f1c3f
399fd9f
 
 
4604cbe
 
399fd9f
b8f1c3f
 
4604cbe
 
b8f1c3f
 
 
399fd9f
b8f1c3f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241


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
    )