Update app.py
Browse files
app.py
CHANGED
|
@@ -143,6 +143,71 @@ def upload_file_to_genai_api(file_data, filename, mime_type):
|
|
| 143 |
pass
|
| 144 |
return None
|
| 145 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
def get_all_tasks_info():
|
| 147 |
"""Récupère toutes les informations des tâches pour le centre de gestion."""
|
| 148 |
tasks_info = []
|
|
@@ -159,6 +224,9 @@ def get_all_tasks_info():
|
|
| 159 |
'pdf_filename': task_data.get('pdf_filename'),
|
| 160 |
'error': task_data.get('error'),
|
| 161 |
'response': task_data.get('response', ''),
|
|
|
|
|
|
|
|
|
|
| 162 |
'user_images': []
|
| 163 |
}
|
| 164 |
|
|
@@ -181,6 +249,10 @@ def get_system_stats():
|
|
| 181 |
error_tasks = sum(1 for task in task_results.values() if task['status'] == 'error')
|
| 182 |
pending_tasks = sum(1 for task in task_results.values() if task['status'] not in ['completed', 'error'])
|
| 183 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
# Compter les fichiers PDF générés
|
| 185 |
pdf_files = 0
|
| 186 |
if os.path.exists(GENERATED_PDF_DIR):
|
|
@@ -198,7 +270,9 @@ def get_system_stats():
|
|
| 198 |
'pending_tasks': pending_tasks,
|
| 199 |
'pdf_files': pdf_files,
|
| 200 |
'user_images': user_images,
|
| 201 |
-
'latex_installed': IS_LATEX_INSTALLED
|
|
|
|
|
|
|
| 202 |
}
|
| 203 |
|
| 204 |
def clean_latex_code(latex_code):
|
|
@@ -328,24 +402,11 @@ def process_files_background(task_id, files_data, resolution_style):
|
|
| 328 |
raise ValueError(f"Le fichier de prompt pour le style '{resolution_style}' est introuvable ou vide.")
|
| 329 |
contents.append(prompt_to_use)
|
| 330 |
|
| 331 |
-
|
| 332 |
-
logger.info(f"[Task {task_id}]
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
contents=contents,
|
| 337 |
-
config=types.GenerateContentConfig(tools=[types.Tool(code_execution=types.ToolCodeExecution)])
|
| 338 |
-
)
|
| 339 |
-
|
| 340 |
-
logger.info(f"[Task {task_id}] Réponse reçue de Gemini.")
|
| 341 |
-
full_latex_response = ""
|
| 342 |
-
if gemini_response.candidates and gemini_response.candidates[0].content and gemini_response.candidates[0].content.parts:
|
| 343 |
-
for part in gemini_response.candidates[0].content.parts:
|
| 344 |
-
if hasattr(part, 'text') and part.text:
|
| 345 |
-
full_latex_response += part.text
|
| 346 |
-
|
| 347 |
-
if not full_latex_response.strip():
|
| 348 |
-
raise ValueError("La réponse de Gemini était vide.")
|
| 349 |
logger.debug(f"[Task {task_id}] Réponse brute de Gemini:\n---\n{full_latex_response[:500]}...\n---")
|
| 350 |
|
| 351 |
task_results[task_id]['status'] = 'cleaning_latex'
|
|
@@ -359,8 +420,9 @@ def process_files_background(task_id, files_data, resolution_style):
|
|
| 359 |
if pdf_file_path:
|
| 360 |
task_results[task_id]['status'] = 'completed'
|
| 361 |
task_results[task_id]['pdf_filename'] = os.path.basename(pdf_file_path)
|
| 362 |
-
task_results[task_id]
|
| 363 |
-
|
|
|
|
| 364 |
else:
|
| 365 |
raise RuntimeError(f"Échec de la génération du PDF: {pdf_message}")
|
| 366 |
|
|
@@ -515,7 +577,14 @@ def get_task_status(task_id):
|
|
| 515 |
logger.warning(f"Tentative d'accès à une tâche inexistante: {task_id}")
|
| 516 |
return jsonify({'error': 'Tâche introuvable'}), 404
|
| 517 |
|
| 518 |
-
response_data = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 519 |
if task['status'] == 'completed':
|
| 520 |
response_data['download_url'] = f"/download/{task_id}"
|
| 521 |
|
|
@@ -527,6 +596,8 @@ def stream_task_progress(task_id):
|
|
| 527 |
def generate():
|
| 528 |
logger.info(f"Nouvelle connexion de streaming (SSE) pour la tâche {task_id}")
|
| 529 |
last_status_sent = None
|
|
|
|
|
|
|
| 530 |
while True:
|
| 531 |
task = task_results.get(task_id)
|
| 532 |
if not task:
|
|
@@ -535,17 +606,27 @@ def stream_task_progress(task_id):
|
|
| 535 |
break
|
| 536 |
|
| 537 |
current_status = task['status']
|
| 538 |
-
|
| 539 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 540 |
if current_status == 'completed':
|
| 541 |
data_to_send["response"] = task.get("response", "")
|
| 542 |
data_to_send["download_url"] = f"/download/{task_id}"
|
| 543 |
elif current_status == 'error':
|
| 544 |
data_to_send["error"] = task.get("error", "Erreur inconnue")
|
|
|
|
| 545 |
|
| 546 |
-
logger.info(f"[Task {task_id}] Envoi de la mise à jour de statut via SSE: {current_status}")
|
| 547 |
yield f'data: {json.dumps(data_to_send)}\n\n'
|
| 548 |
last_status_sent = current_status
|
|
|
|
| 549 |
|
| 550 |
if current_status in ['completed', 'error']:
|
| 551 |
logger.info(f"Fermeture de la connexion SSE pour la tâche terminée/échouée {task_id}")
|
|
|
|
| 143 |
pass
|
| 144 |
return None
|
| 145 |
|
| 146 |
+
def call_gemini_with_fallback(contents, task_id):
|
| 147 |
+
"""Appelle Gemini 2.5 Pro en premier, puis 2.5 Flash en cas d'échec."""
|
| 148 |
+
models_to_try = [
|
| 149 |
+
{"name": "gemini-2.5-pro", "display_name": "Gemini 2.5 Pro"},
|
| 150 |
+
{"name": "gemini-2.5-flash", "display_name": "Gemini 2.5 Flash"}
|
| 151 |
+
]
|
| 152 |
+
|
| 153 |
+
last_error = None
|
| 154 |
+
|
| 155 |
+
for i, model_info in enumerate(models_to_try):
|
| 156 |
+
model_name = model_info["name"]
|
| 157 |
+
model_display = model_info["display_name"]
|
| 158 |
+
|
| 159 |
+
try:
|
| 160 |
+
logger.info(f"[Task {task_id}] Tentative {i+1}/2: Appel de {model_display}...")
|
| 161 |
+
|
| 162 |
+
# Mettre à jour le statut pour indiquer quel modèle est en cours d'utilisation
|
| 163 |
+
if model_name == "gemini-2.5-pro":
|
| 164 |
+
task_results[task_id]['status'] = 'generating_latex_pro'
|
| 165 |
+
task_results[task_id]['current_model'] = 'Gemini 2.5 Pro'
|
| 166 |
+
else:
|
| 167 |
+
task_results[task_id]['status'] = 'generating_latex_flash'
|
| 168 |
+
task_results[task_id]['current_model'] = 'Gemini 2.5 Flash (fallback)'
|
| 169 |
+
|
| 170 |
+
# Faire l'appel à l'API
|
| 171 |
+
gemini_response = client.models.generate_content(
|
| 172 |
+
model=model_name,
|
| 173 |
+
contents=contents,
|
| 174 |
+
config=types.GenerateContentConfig(tools=[types.Tool(code_execution=types.ToolCodeExecution)])
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
# Vérifier si la réponse est valide
|
| 178 |
+
full_latex_response = ""
|
| 179 |
+
if gemini_response.candidates and gemini_response.candidates[0].content and gemini_response.candidates[0].content.parts:
|
| 180 |
+
for part in gemini_response.candidates[0].content.parts:
|
| 181 |
+
if hasattr(part, 'text') and part.text:
|
| 182 |
+
full_latex_response += part.text
|
| 183 |
+
|
| 184 |
+
if not full_latex_response.strip():
|
| 185 |
+
raise ValueError(f"Réponse vide de {model_display}")
|
| 186 |
+
|
| 187 |
+
logger.info(f"[Task {task_id}] ✓ Succès avec {model_display}")
|
| 188 |
+
task_results[task_id]['used_model'] = model_display
|
| 189 |
+
return full_latex_response
|
| 190 |
+
|
| 191 |
+
except Exception as e:
|
| 192 |
+
error_msg = str(e)
|
| 193 |
+
logger.warning(f"[Task {task_id}] ✗ Échec avec {model_display}: {error_msg}")
|
| 194 |
+
last_error = e
|
| 195 |
+
|
| 196 |
+
# Si c'est le dernier modèle et qu'il a échoué, on lève l'erreur
|
| 197 |
+
if i == len(models_to_try) - 1:
|
| 198 |
+
logger.error(f"[Task {task_id}] Tous les modèles ont échoué. Dernière erreur: {error_msg}")
|
| 199 |
+
task_results[task_id]['model_failures'] = [
|
| 200 |
+
f"{models_to_try[0]['display_name']}: Échec",
|
| 201 |
+
f"{models_to_try[1]['display_name']}: {error_msg}"
|
| 202 |
+
]
|
| 203 |
+
raise last_error
|
| 204 |
+
|
| 205 |
+
# Attendre un peu avant d'essayer le modèle suivant
|
| 206 |
+
time.sleep(2)
|
| 207 |
+
|
| 208 |
+
# Cette ligne ne devrait jamais être atteinte, mais au cas où
|
| 209 |
+
raise last_error if last_error else Exception("Erreur inconnue lors des appels aux modèles Gemini")
|
| 210 |
+
|
| 211 |
def get_all_tasks_info():
|
| 212 |
"""Récupère toutes les informations des tâches pour le centre de gestion."""
|
| 213 |
tasks_info = []
|
|
|
|
| 224 |
'pdf_filename': task_data.get('pdf_filename'),
|
| 225 |
'error': task_data.get('error'),
|
| 226 |
'response': task_data.get('response', ''),
|
| 227 |
+
'used_model': task_data.get('used_model', 'N/A'),
|
| 228 |
+
'current_model': task_data.get('current_model', ''),
|
| 229 |
+
'model_failures': task_data.get('model_failures', []),
|
| 230 |
'user_images': []
|
| 231 |
}
|
| 232 |
|
|
|
|
| 249 |
error_tasks = sum(1 for task in task_results.values() if task['status'] == 'error')
|
| 250 |
pending_tasks = sum(1 for task in task_results.values() if task['status'] not in ['completed', 'error'])
|
| 251 |
|
| 252 |
+
# Statistiques d'utilisation des modèles
|
| 253 |
+
pro_usage = sum(1 for task in task_results.values() if task.get('used_model') == 'Gemini 2.5 Pro')
|
| 254 |
+
flash_usage = sum(1 for task in task_results.values() if task.get('used_model') == 'Gemini 2.5 Flash (fallback)')
|
| 255 |
+
|
| 256 |
# Compter les fichiers PDF générés
|
| 257 |
pdf_files = 0
|
| 258 |
if os.path.exists(GENERATED_PDF_DIR):
|
|
|
|
| 270 |
'pending_tasks': pending_tasks,
|
| 271 |
'pdf_files': pdf_files,
|
| 272 |
'user_images': user_images,
|
| 273 |
+
'latex_installed': IS_LATEX_INSTALLED,
|
| 274 |
+
'pro_usage': pro_usage,
|
| 275 |
+
'flash_usage': flash_usage
|
| 276 |
}
|
| 277 |
|
| 278 |
def clean_latex_code(latex_code):
|
|
|
|
| 402 |
raise ValueError(f"Le fichier de prompt pour le style '{resolution_style}' est introuvable ou vide.")
|
| 403 |
contents.append(prompt_to_use)
|
| 404 |
|
| 405 |
+
# Appeler Gemini avec fallback Pro -> Flash
|
| 406 |
+
logger.info(f"[Task {task_id}] Début des appels aux modèles Gemini avec {len(uploaded_file_refs)} fichier(s).")
|
| 407 |
+
full_latex_response = call_gemini_with_fallback(contents, task_id)
|
| 408 |
+
|
| 409 |
+
logger.info(f"[Task {task_id}] Réponse reçue de Gemini ({task_results[task_id].get('used_model', 'modèle inconnu')}).")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
logger.debug(f"[Task {task_id}] Réponse brute de Gemini:\n---\n{full_latex_response[:500]}...\n---")
|
| 411 |
|
| 412 |
task_results[task_id]['status'] = 'cleaning_latex'
|
|
|
|
| 420 |
if pdf_file_path:
|
| 421 |
task_results[task_id]['status'] = 'completed'
|
| 422 |
task_results[task_id]['pdf_filename'] = os.path.basename(pdf_file_path)
|
| 423 |
+
used_model = task_results[task_id].get('used_model', 'modèle inconnu')
|
| 424 |
+
task_results[task_id]['response'] = f"PDF généré avec succès: {os.path.basename(pdf_file_path)} (généré par {used_model})"
|
| 425 |
+
logger.info(f"[Task {task_id}] Tâche terminée avec succès. PDF: {os.path.basename(pdf_file_path)} (modèle: {used_model})")
|
| 426 |
else:
|
| 427 |
raise RuntimeError(f"Échec de la génération du PDF: {pdf_message}")
|
| 428 |
|
|
|
|
| 577 |
logger.warning(f"Tentative d'accès à une tâche inexistante: {task_id}")
|
| 578 |
return jsonify({'error': 'Tâche introuvable'}), 404
|
| 579 |
|
| 580 |
+
response_data = {
|
| 581 |
+
'status': task['status'],
|
| 582 |
+
'response': task.get('response'),
|
| 583 |
+
'error': task.get('error'),
|
| 584 |
+
'current_model': task.get('current_model', ''),
|
| 585 |
+
'used_model': task.get('used_model', ''),
|
| 586 |
+
'model_failures': task.get('model_failures', [])
|
| 587 |
+
}
|
| 588 |
if task['status'] == 'completed':
|
| 589 |
response_data['download_url'] = f"/download/{task_id}"
|
| 590 |
|
|
|
|
| 596 |
def generate():
|
| 597 |
logger.info(f"Nouvelle connexion de streaming (SSE) pour la tâche {task_id}")
|
| 598 |
last_status_sent = None
|
| 599 |
+
last_model_sent = None
|
| 600 |
+
|
| 601 |
while True:
|
| 602 |
task = task_results.get(task_id)
|
| 603 |
if not task:
|
|
|
|
| 606 |
break
|
| 607 |
|
| 608 |
current_status = task['status']
|
| 609 |
+
current_model = task.get('current_model', '')
|
| 610 |
+
|
| 611 |
+
# Envoyer une mise à jour si le statut ou le modèle a changé
|
| 612 |
+
if current_status != last_status_sent or current_model != last_model_sent:
|
| 613 |
+
data_to_send = {
|
| 614 |
+
"status": current_status,
|
| 615 |
+
"current_model": current_model,
|
| 616 |
+
"used_model": task.get('used_model', '')
|
| 617 |
+
}
|
| 618 |
+
|
| 619 |
if current_status == 'completed':
|
| 620 |
data_to_send["response"] = task.get("response", "")
|
| 621 |
data_to_send["download_url"] = f"/download/{task_id}"
|
| 622 |
elif current_status == 'error':
|
| 623 |
data_to_send["error"] = task.get("error", "Erreur inconnue")
|
| 624 |
+
data_to_send["model_failures"] = task.get("model_failures", [])
|
| 625 |
|
| 626 |
+
logger.info(f"[Task {task_id}] Envoi de la mise à jour de statut via SSE: {current_status} ({current_model})")
|
| 627 |
yield f'data: {json.dumps(data_to_send)}\n\n'
|
| 628 |
last_status_sent = current_status
|
| 629 |
+
last_model_sent = current_model
|
| 630 |
|
| 631 |
if current_status in ['completed', 'error']:
|
| 632 |
logger.info(f"Fermeture de la connexion SSE pour la tâche terminée/échouée {task_id}")
|