Docfile commited on
Commit
f1cc6e8
·
verified ·
1 Parent(s): 3acc4b9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +142 -79
app.py CHANGED
@@ -12,7 +12,6 @@ import time
12
  import tempfile # Added
13
  import subprocess # Added
14
  import shutil # Added
15
- # import platform # Not strictly needed for app.py modifications
16
  import re # Added
17
 
18
  app = Flask(__name__)
@@ -24,7 +23,6 @@ TELEGRAM_BOT_TOKEN = "8004545342:AAGcZaoDjYg8dmbbXRsR1N3TfSSbEiAGz88"
24
  TELEGRAM_CHAT_ID = "-1002497861230"
25
 
26
  # Initialize Gemini client
27
- # Assuming genai.Client and client.models.generate_content are part of user's specific library setup
28
  if GOOGLE_API_KEY:
29
  try:
30
  client = genai.Client(api_key=GOOGLE_API_KEY)
@@ -145,6 +143,43 @@ rend le très espacer. Ça doit être très aéré
145
 
146
  """
147
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  # Dictionnaire pour stocker les résultats des tâches en cours
149
  task_results = {}
150
 
@@ -152,7 +187,6 @@ task_results = {}
152
  def check_latex_installation():
153
  """Vérifie si pdflatex est installé sur le système"""
154
  try:
155
- # Using timeout to prevent hanging if pdflatex is unresponsive
156
  subprocess.run(["pdflatex", "-version"], capture_output=True, check=True, timeout=10)
157
  print("INFO: pdflatex est installé et accessible.")
158
  return True
@@ -165,17 +199,15 @@ IS_LATEX_INSTALLED = check_latex_installation()
165
 
166
  def clean_latex_code(latex_code):
167
  """Removes markdown code block fences (```latex ... ``` or ``` ... ```) if present."""
168
- # Pattern for ```latex ... ```
169
  match_latex = re.search(r"```(?:latex|tex)\s*(.*?)\s*```", latex_code, re.DOTALL | re.IGNORECASE)
170
  if match_latex:
171
  return match_latex.group(1).strip()
172
 
173
- # Pattern for generic ``` ... ```, checking if it likely contains LaTeX
174
  match_generic = re.search(r"```\s*(\\documentclass.*?)\s*```", latex_code, re.DOTALL | re.IGNORECASE)
175
  if match_generic:
176
  return match_generic.group(1).strip()
177
 
178
- return latex_code.strip() # Default to stripping whitespace if no fences found
179
 
180
  def latex_to_pdf(latex_code, output_filename_base="document"):
181
  """
@@ -186,7 +218,6 @@ def latex_to_pdf(latex_code, output_filename_base="document"):
186
  if not IS_LATEX_INSTALLED:
187
  return None, "pdflatex n'est pas disponible sur le système."
188
 
189
- # Create a temporary directory for LaTeX compilation files
190
  with tempfile.TemporaryDirectory() as temp_dir_compile:
191
  tex_filename = f"{output_filename_base}.tex"
192
  tex_path = os.path.join(temp_dir_compile, tex_filename)
@@ -197,28 +228,23 @@ def latex_to_pdf(latex_code, output_filename_base="document"):
197
  tex_file.write(latex_code)
198
 
199
  my_env = os.environ.copy()
200
- my_env["LC_ALL"] = "C.UTF-8" # Ensure UTF-8 locale for pdflatex
201
  my_env["LANG"] = "C.UTF-8"
202
 
203
  last_result = None
204
- for i in range(2): # Run pdflatex twice for references, TOC, etc.
205
- # Added -file-line-error for more precise error messages
206
- # Added -halt-on-error to stop on first error (more efficient)
207
- # Note: -halt-on-error might prevent a second pass that could fix some issues.
208
- # For robustness, -interaction=nonstopmode is often preferred to try to complete.
209
  process = subprocess.run(
210
  ["pdflatex", "-interaction=nonstopmode", "-output-directory", temp_dir_compile, tex_path],
211
- capture_output=True, # Capture stdout and stderr
212
- text=True, # Decode output as text
213
- check=False, # Do not raise exception on non-zero exit code
214
- encoding="utf-8", # Specify encoding for output decoding
215
- errors="replace", # Replace undecodable characters
216
  env=my_env,
217
- timeout=120 # Timeout for pdflatex execution (e.g., 2 minutes)
218
  )
219
  last_result = process
220
  if not os.path.exists(pdf_path_in_compile_dir) and process.returncode != 0:
221
- # If PDF not created and there was an error, no point in second pass
222
  break
223
 
224
  if os.path.exists(pdf_path_in_compile_dir):
@@ -229,7 +255,6 @@ def latex_to_pdf(latex_code, output_filename_base="document"):
229
  else:
230
  error_log = last_result.stdout + "\n" + last_result.stderr if last_result else "Aucun résultat de compilation."
231
 
232
- # Try to extract more specific error messages
233
  if "LaTeX Error: File `" in error_log:
234
  match = re.search(r"LaTeX Error: File `(.*?)' not found.", error_log)
235
  if match:
@@ -239,12 +264,11 @@ def latex_to_pdf(latex_code, output_filename_base="document"):
239
  return None, "Erreur de compilation PDF: Commande LaTeX non définie. Vérifiez le code LaTeX pour des erreurs de syntaxe."
240
 
241
  if "! LaTeX Error:" in error_log:
242
- match = re.search(r"! LaTeX Error: (.*?)\n", error_log) # Get first line of error
243
  if match:
244
  return None, f"Erreur de compilation PDF: {match.group(1).strip()}"
245
 
246
- # Generic error if specific parsing fails
247
- log_preview = error_log[-1000:] # Last 1000 characters of log for preview
248
  print(f"Erreur de compilation PDF complète pour {output_filename_base}:\n{error_log}")
249
  return None, f"Erreur lors de la compilation du PDF. Détails dans les logs du serveur. Aperçu: ...{log_preview}"
250
 
@@ -259,6 +283,9 @@ def latex_to_pdf(latex_code, output_filename_base="document"):
259
  # --- Telegram Functions ---
260
  def send_to_telegram(image_data, caption="Nouvelle image uploadée"):
261
  """Envoie l'image à un chat Telegram spécifié"""
 
 
 
262
  try:
263
  url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendPhoto"
264
  files = {'photo': ('image.png', image_data)}
@@ -280,6 +307,9 @@ def send_to_telegram(image_data, caption="Nouvelle image uploadée"):
280
 
281
  def send_document_to_telegram(content_or_path, filename="reponse.txt", caption="Réponse", is_pdf=False):
282
  """Envoie un fichier (texte ou PDF) à un chat Telegram spécifié"""
 
 
 
283
  try:
284
  url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendDocument"
285
  files = None
@@ -287,11 +317,11 @@ def send_document_to_telegram(content_or_path, filename="reponse.txt", caption="
287
  if is_pdf:
288
  with open(content_or_path, 'rb') as f_pdf:
289
  files = {'document': (filename, f_pdf.read(), 'application/pdf')}
290
- else: # Assuming text content
291
  files = {'document': (filename, content_or_path.encode('utf-8'), 'text/plain')}
292
 
293
  data = {'chat_id': TELEGRAM_CHAT_ID, 'caption': caption}
294
- response = requests.post(url, files=files, data=data, timeout=60) # Increased timeout for larger files
295
 
296
  if response.status_code == 200:
297
  print(f"Document '{filename}' envoyé avec succès à Telegram.")
@@ -307,7 +337,7 @@ def send_document_to_telegram(content_or_path, filename="reponse.txt", caption="
307
  return False
308
 
309
  # --- Background Image Processing ---
310
- def process_image_background(task_id, image_data):
311
  """Traite l'image, génère LaTeX, convertit en PDF (si possible), et envoie via Telegram."""
312
  pdf_file_to_clean = None
313
  try:
@@ -317,38 +347,43 @@ def process_image_background(task_id, image_data):
317
  raise ConnectionError("Client Gemini non initialisé. Vérifiez la clé API et la configuration.")
318
 
319
  img = Image.open(io.BytesIO(image_data))
320
- buffered = io.BytesIO()
321
- img.save(buffered, format="PNG") # Gemini prefers PNG or JPEG
322
- img_base64_str = base64.b64encode(buffered.getvalue()).decode()
323
 
324
  full_latex_response = ""
325
 
 
 
 
 
 
 
 
326
  try:
327
  task_results[task_id]['status'] = 'generating_latex'
328
  print(f"Task {task_id}: Génération LaTeX par Gemini...")
329
 
330
- # User's original model: "gemini-2.5-pro-exp-03-25"
331
- # Using "gemini-1.5-pro-latest" as a robust alternative. User can change if needed.
332
- # The user's original Gemini call structure:
333
  gemini_response = client.models.generate_content(
334
  model="gemini-2.5-pro-exp-03-25",
335
  contents=[
336
- {'inline_data': {'mime_type': 'image/png', 'data': img_base64_str}},
337
- ppmqth
338
- ]
339
- # Consider adding generation_config for token limits, temperature if needed
340
- # generation_config=genai.types.GenerationConfig(max_output_tokens=8192)
341
  )
342
 
343
  if gemini_response.candidates:
344
  candidate = gemini_response.candidates[0]
 
 
345
  if candidate.content and candidate.content.parts:
346
  for part in candidate.content.parts:
347
  if hasattr(part, 'text') and part.text:
348
  full_latex_response += part.text
349
- elif hasattr(candidate, 'text') and candidate.text: # Fallback for simpler candidate structure
350
  full_latex_response = candidate.text
351
- elif hasattr(gemini_response, 'text') and gemini_response.text: # Fallback for direct response.text
352
  full_latex_response = gemini_response.text
353
 
354
  if not full_latex_response.strip():
@@ -359,21 +394,23 @@ def process_image_background(task_id, image_data):
359
  task_results[task_id]['status'] = 'cleaning_latex'
360
  cleaned_latex = clean_latex_code(full_latex_response)
361
  print(f"Task {task_id}: LaTeX nettoyé (longueur: {len(cleaned_latex)}).")
 
 
 
362
 
363
  if not IS_LATEX_INSTALLED:
364
  print(f"Task {task_id}: pdflatex non disponible. Envoi du .tex uniquement.")
365
  send_document_to_telegram(
366
  cleaned_latex,
367
- filename=f"solution_{task_id}.tex",
368
- caption=f"Code LaTeX pour tâche {task_id} (pdflatex non disponible)"
369
  )
370
  task_results[task_id]['status'] = 'completed_tex_only'
371
- task_results[task_id]['response'] = cleaned_latex
372
  return
373
 
374
  task_results[task_id]['status'] = 'generating_pdf'
375
  print(f"Task {task_id}: Génération du PDF...")
376
- pdf_filename_base = f"solution_{task_id}"
377
  pdf_file_to_clean, pdf_message = latex_to_pdf(cleaned_latex, output_filename_base=pdf_filename_base)
378
 
379
  if pdf_file_to_clean:
@@ -381,40 +418,59 @@ def process_image_background(task_id, image_data):
381
  send_document_to_telegram(
382
  pdf_file_to_clean,
383
  filename=f"{pdf_filename_base}.pdf",
384
- caption=f"Solution PDF pour tâche {task_id}",
385
  is_pdf=True
386
  )
387
  task_results[task_id]['status'] = 'completed'
388
- task_results[task_id]['response'] = cleaned_latex # Web UI still gets LaTeX source
389
  else:
390
  print(f"Task {task_id}: Échec de la génération PDF: {pdf_message}. Envoi du .tex en fallback.")
391
  task_results[task_id]['status'] = 'pdf_error'
392
- task_results[task_id]['error_detail'] = f"Erreur PDF: {pdf_message}" # Store PDF specific error
393
  send_document_to_telegram(
394
  cleaned_latex,
395
- filename=f"solution_{task_id}.tex",
396
- caption=f"Code LaTeX pour tâche {task_id} (Erreur PDF: {pdf_message[:150]})"
397
  )
398
- task_results[task_id]['response'] = cleaned_latex # Web UI gets LaTeX source
399
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  except Exception as e_gen:
401
- print(f"Task {task_id}: Erreur lors de la génération Gemini ou traitement PDF: {e_gen}")
 
402
  task_results[task_id]['status'] = 'error'
403
  task_results[task_id]['error'] = f"Erreur de traitement: {str(e_gen)}"
404
- # Optionally send a simple text error to Telegram
 
405
  send_document_to_telegram(
406
- f"Erreur lors du traitement de la tâche {task_id}: {str(e_gen)}",
407
  filename=f"error_{task_id}.txt",
408
  caption=f"Erreur Tâche {task_id}"
409
  )
410
- task_results[task_id]['response'] = f"Erreur: {str(e_gen)}"
411
 
 
 
 
 
 
 
412
 
413
  except Exception as e_outer:
414
- print(f"Task {task_id}: Exception majeure dans la tâche de fond: {e_outer}")
 
415
  task_results[task_id]['status'] = 'error'
416
  task_results[task_id]['error'] = f"Erreur système: {str(e_outer)}"
417
- task_results[task_id]['response'] = f"Erreur système: {str(e_outer)}"
 
418
  finally:
419
  if pdf_file_to_clean and os.path.exists(pdf_file_to_clean):
420
  try:
@@ -440,25 +496,28 @@ def solve():
440
 
441
  image_data = image_file.read()
442
 
443
- # Envoyer l'image à Telegram (confirmation d'upload)
444
- send_to_telegram(image_data, f"Nouvelle image pour résolution (Tâche à venir)")
 
445
 
446
  task_id = str(uuid.uuid4())
447
  task_results[task_id] = {
448
  'status': 'pending',
449
  'response': '',
450
  'error': None,
451
- 'time_started': time.time()
 
 
452
  }
453
 
454
  threading.Thread(
455
  target=process_image_background,
456
- args=(task_id, image_data)
457
  ).start()
458
 
459
  return jsonify({
460
  'task_id': task_id,
461
- 'status': 'pending'
462
  })
463
 
464
  except Exception as e:
@@ -472,21 +531,12 @@ def get_task_status(task_id):
472
 
473
  task = task_results[task_id]
474
 
475
- # Basic cleanup logic (can be improved with a dedicated cleanup thread)
476
- current_time = time.time()
477
- if task['status'] in ['completed', 'error', 'pdf_error', 'completed_tex_only'] and \
478
- (current_time - task.get('time_started', 0) > 3600): # Cleanup after 1 hour
479
- # del task_results[task_id] # Potentially remove, or mark for removal
480
- # For now, just don't error if it's old
481
- pass
482
-
483
  response_data = {
484
  'status': task['status'],
485
  'response': task.get('response'),
486
- 'error': task.get('error')
 
487
  }
488
- if task.get('error_detail'): # Include PDF specific error if present
489
- response_data['error_detail'] = task.get('error_detail')
490
 
491
  return jsonify(response_data)
492
 
@@ -498,9 +548,16 @@ def stream_task_progress(task_id):
498
  return
499
 
500
  last_status_sent = None
 
 
 
501
  while True:
 
 
 
 
502
  task = task_results.get(task_id)
503
- if not task: # Task might have been cleaned up
504
  yield f'data: {json.dumps({"error": "Tâche disparue ou nettoyée", "status": "error"})}\n\n'
505
  break
506
 
@@ -508,29 +565,31 @@ def stream_task_progress(task_id):
508
 
509
  if current_status != last_status_sent:
510
  data_to_send = {"status": current_status}
511
- if current_status == 'completed' or current_status == 'completed_tex_only':
512
  data_to_send["response"] = task.get("response", "")
513
- elif current_status == 'error' or current_status == 'pdf_error':
514
  data_to_send["error"] = task.get("error", "Erreur inconnue")
515
  if task.get("error_detail"):
516
  data_to_send["error_detail"] = task.get("error_detail")
517
- if task.get("response"): # Send partial response if available on error
 
518
  data_to_send["response"] = task.get("response")
519
 
 
520
  yield f'data: {json.dumps(data_to_send)}\n\n'
521
  last_status_sent = current_status
522
 
523
  if current_status in ['completed', 'error', 'pdf_error', 'completed_tex_only']:
524
- break # End stream for terminal states
525
 
526
- time.sleep(1) # Check status every second
527
 
528
  return Response(
529
  stream_with_context(generate()),
530
  mimetype='text/event-stream',
531
  headers={
532
  'Cache-Control': 'no-cache',
533
- 'X-Accel-Buffering': 'no', # Important for Nginx or other reverse proxies
534
  'Connection': 'keep-alive'
535
  }
536
  )
@@ -540,6 +599,10 @@ if __name__ == '__main__':
540
  print("CRITICAL: GEMINI_API_KEY variable d'environnement non définie. L'application risque de ne pas fonctionner.")
541
  if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
542
  print("CRITICAL: TELEGRAM_BOT_TOKEN ou TELEGRAM_CHAT_ID non définis. L'intégration Telegram échouera.")
 
 
 
 
543
 
544
  # For production, use a proper WSGI server like Gunicorn or uWSGI
545
  app.run(debug=True, host='0.0.0.0', port=5000)
 
12
  import tempfile # Added
13
  import subprocess # Added
14
  import shutil # Added
 
15
  import re # Added
16
 
17
  app = Flask(__name__)
 
23
  TELEGRAM_CHAT_ID = "-1002497861230"
24
 
25
  # Initialize Gemini client
 
26
  if GOOGLE_API_KEY:
27
  try:
28
  client = genai.Client(api_key=GOOGLE_API_KEY)
 
143
 
144
  """
145
 
146
+ PPMQTH_LIGHT = r"""
147
+ # RÔLE & OBJECTIF
148
+ Tu es un expert en mathématiques capable de fournir une solution claire et concise. Ton objectif est de générer une correction LaTeX directement compilable pour l'exercice fourni.
149
+
150
+ # CONTEXTE
151
+ * **Input:** L'énoncé de l'exercice (texte).
152
+ * **Niveau Cible:** Terminale (scientifique).
153
+ * **Output Attendu:** **Uniquement** le code source LaTeX (`.tex`) brut et complet. Pas de texte d'accompagnement en dehors du code.
154
+
155
+ # TÂCHE PRINCIPALE
156
+ 1. **Analyse:** Comprends l'énoncé.
157
+ 2. **Résolution:** Résous l'exercice.
158
+ 3. **Rédaction LaTeX:** Rédige la solution en LaTeX standard.
159
+
160
+ # SPÉCIFICATIONS TECHNIQUES DU CODE LATEX (Minimal)
161
+ * Classe: `\documentclass[12pt,a4paper]{article}`
162
+ * Encodage: `\usepackage[utf8]{inputenc}`, `\usepackage[T1]{fontenc}`
163
+ * Langue: `\usepackage[french]{babel}`
164
+ * Maths: `\usepackage{amsmath}`, `\usepackage{amssymb}`
165
+ * Optionnel: `\usepackage{graphicx}` si l'énoncé peut contenir des schémas à reproduire (rare pour du texte).
166
+ * Pas de packages de mise en page complexes (pas de tcolorbox, fancyhdr, tikz, etc. sauf si *absolument* indispensable pour représenter la solution mathématique elle-même).
167
+ * Utilise des environnements mathématiques standards (`align*`, `equation*`, `\[ ... \]`).
168
+
169
+ # CONTENU PÉDAGOGIQUE DE LA SOLUTION
170
+ * Rappeler l'énoncé.
171
+ * Structurer la solution logiquement.
172
+ * Justifier les étapes principales.
173
+ * Langage mathématique précis.
174
+
175
+ # CONTRAINTES STRICTES
176
+ * Le seul output doit être le code LaTeX brut.
177
+ * Aucun texte avant `\documentclass` ou après `\end{document}`.
178
+ * Inclure la ligne `Concu par Mariam AI` juste avant `\end{document}`.
179
+ * L'objectif est une solution fonctionnelle et lisible, sans mise en page sophistiquée.
180
+ """
181
+
182
+
183
  # Dictionnaire pour stocker les résultats des tâches en cours
184
  task_results = {}
185
 
 
187
  def check_latex_installation():
188
  """Vérifie si pdflatex est installé sur le système"""
189
  try:
 
190
  subprocess.run(["pdflatex", "-version"], capture_output=True, check=True, timeout=10)
191
  print("INFO: pdflatex est installé et accessible.")
192
  return True
 
199
 
200
  def clean_latex_code(latex_code):
201
  """Removes markdown code block fences (```latex ... ``` or ``` ... ```) if present."""
 
202
  match_latex = re.search(r"```(?:latex|tex)\s*(.*?)\s*```", latex_code, re.DOTALL | re.IGNORECASE)
203
  if match_latex:
204
  return match_latex.group(1).strip()
205
 
 
206
  match_generic = re.search(r"```\s*(\\documentclass.*?)\s*```", latex_code, re.DOTALL | re.IGNORECASE)
207
  if match_generic:
208
  return match_generic.group(1).strip()
209
 
210
+ return latex_code.strip()
211
 
212
  def latex_to_pdf(latex_code, output_filename_base="document"):
213
  """
 
218
  if not IS_LATEX_INSTALLED:
219
  return None, "pdflatex n'est pas disponible sur le système."
220
 
 
221
  with tempfile.TemporaryDirectory() as temp_dir_compile:
222
  tex_filename = f"{output_filename_base}.tex"
223
  tex_path = os.path.join(temp_dir_compile, tex_filename)
 
228
  tex_file.write(latex_code)
229
 
230
  my_env = os.environ.copy()
231
+ my_env["LC_ALL"] = "C.UTF-8"
232
  my_env["LANG"] = "C.UTF-8"
233
 
234
  last_result = None
235
+ for i in range(2):
 
 
 
 
236
  process = subprocess.run(
237
  ["pdflatex", "-interaction=nonstopmode", "-output-directory", temp_dir_compile, tex_path],
238
+ capture_output=True,
239
+ text=True,
240
+ check=False,
241
+ encoding="utf-8",
242
+ errors="replace",
243
  env=my_env,
244
+ timeout=120
245
  )
246
  last_result = process
247
  if not os.path.exists(pdf_path_in_compile_dir) and process.returncode != 0:
 
248
  break
249
 
250
  if os.path.exists(pdf_path_in_compile_dir):
 
255
  else:
256
  error_log = last_result.stdout + "\n" + last_result.stderr if last_result else "Aucun résultat de compilation."
257
 
 
258
  if "LaTeX Error: File `" in error_log:
259
  match = re.search(r"LaTeX Error: File `(.*?)' not found.", error_log)
260
  if match:
 
264
  return None, "Erreur de compilation PDF: Commande LaTeX non définie. Vérifiez le code LaTeX pour des erreurs de syntaxe."
265
 
266
  if "! LaTeX Error:" in error_log:
267
+ match = re.search(r"! LaTeX Error: (.*?)\n", error_log)
268
  if match:
269
  return None, f"Erreur de compilation PDF: {match.group(1).strip()}"
270
 
271
+ log_preview = error_log[-1000:]
 
272
  print(f"Erreur de compilation PDF complète pour {output_filename_base}:\n{error_log}")
273
  return None, f"Erreur lors de la compilation du PDF. Détails dans les logs du serveur. Aperçu: ...{log_preview}"
274
 
 
283
  # --- Telegram Functions ---
284
  def send_to_telegram(image_data, caption="Nouvelle image uploadée"):
285
  """Envoie l'image à un chat Telegram spécifié"""
286
+ if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
287
+ print("AVERTISSEMENT: TELEGRAM_BOT_TOKEN ou TELEGRAM_CHAT_ID non configuré. Envoi Telegram désactivé.")
288
+ return False
289
  try:
290
  url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendPhoto"
291
  files = {'photo': ('image.png', image_data)}
 
307
 
308
  def send_document_to_telegram(content_or_path, filename="reponse.txt", caption="Réponse", is_pdf=False):
309
  """Envoie un fichier (texte ou PDF) à un chat Telegram spécifié"""
310
+ if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
311
+ print("AVERTISSEMENT: TELEGRAM_BOT_TOKEN ou TELEGRAM_CHAT_ID non configuré. Envoi Telegram désactivé.")
312
+ return False
313
  try:
314
  url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendDocument"
315
  files = None
 
317
  if is_pdf:
318
  with open(content_or_path, 'rb') as f_pdf:
319
  files = {'document': (filename, f_pdf.read(), 'application/pdf')}
320
+ else:
321
  files = {'document': (filename, content_or_path.encode('utf-8'), 'text/plain')}
322
 
323
  data = {'chat_id': TELEGRAM_CHAT_ID, 'caption': caption}
324
+ response = requests.post(url, files=files, data=data, timeout=60)
325
 
326
  if response.status_code == 200:
327
  print(f"Document '{filename}' envoyé avec succès à Telegram.")
 
337
  return False
338
 
339
  # --- Background Image Processing ---
340
+ def process_image_background(task_id, image_data, prompt_type="refined"):
341
  """Traite l'image, génère LaTeX, convertit en PDF (si possible), et envoie via Telegram."""
342
  pdf_file_to_clean = None
343
  try:
 
347
  raise ConnectionError("Client Gemini non initialisé. Vérifiez la clé API et la configuration.")
348
 
349
  img = Image.open(io.BytesIO(image_data))
350
+ buffered_for_gemini = io.BytesIO()
351
+ img.save(buffered_for_gemini, format="PNG")
352
+ img_bytes_for_gemini = buffered_for_gemini.getvalue()
353
 
354
  full_latex_response = ""
355
 
356
+ selected_prompt = ppmqth
357
+ if prompt_type == "light":
358
+ selected_prompt = PPMQTH_LIGHT
359
+ print(f"Task {task_id}: Utilisation du prompt LÉGER.")
360
+ else:
361
+ print(f"Task {task_id}: Utilisation du prompt RAFFINÉ ET COMPLET.")
362
+
363
  try:
364
  task_results[task_id]['status'] = 'generating_latex'
365
  print(f"Task {task_id}: Génération LaTeX par Gemini...")
366
 
 
 
 
367
  gemini_response = client.models.generate_content(
368
  model="gemini-2.5-pro-exp-03-25",
369
  contents=[
370
+ {'inline_data': {'mime_type': 'image/png', 'data': base64.b64encode(img_bytes_for_gemini).decode()}},
371
+ selected_prompt
372
+ ],
373
+ generation_config={"response_mime_type": "text/plain"}
 
374
  )
375
 
376
  if gemini_response.candidates:
377
  candidate = gemini_response.candidates[0]
378
+ if candidate.finish_reason.name == "SAFETY": # Check for safety blocking
379
+ raise genai.types.BlockedPromptException("Contenu bloqué pour des raisons de sécurité.")
380
  if candidate.content and candidate.content.parts:
381
  for part in candidate.content.parts:
382
  if hasattr(part, 'text') and part.text:
383
  full_latex_response += part.text
384
+ elif hasattr(candidate, 'text') and candidate.text:
385
  full_latex_response = candidate.text
386
+ elif hasattr(gemini_response, 'text') and gemini_response.text:
387
  full_latex_response = gemini_response.text
388
 
389
  if not full_latex_response.strip():
 
394
  task_results[task_id]['status'] = 'cleaning_latex'
395
  cleaned_latex = clean_latex_code(full_latex_response)
396
  print(f"Task {task_id}: LaTeX nettoyé (longueur: {len(cleaned_latex)}).")
397
+ task_results[task_id]['response'] = cleaned_latex
398
+
399
+ caption_base = f"Solution tâche {task_id} (Style: {prompt_type})"
400
 
401
  if not IS_LATEX_INSTALLED:
402
  print(f"Task {task_id}: pdflatex non disponible. Envoi du .tex uniquement.")
403
  send_document_to_telegram(
404
  cleaned_latex,
405
+ filename=f"solution_{task_id}_{prompt_type}.tex",
406
+ caption=f"{caption_base} - Code LaTeX (pdflatex non dispo)"
407
  )
408
  task_results[task_id]['status'] = 'completed_tex_only'
 
409
  return
410
 
411
  task_results[task_id]['status'] = 'generating_pdf'
412
  print(f"Task {task_id}: Génération du PDF...")
413
+ pdf_filename_base = f"solution_{task_id}_{prompt_type}"
414
  pdf_file_to_clean, pdf_message = latex_to_pdf(cleaned_latex, output_filename_base=pdf_filename_base)
415
 
416
  if pdf_file_to_clean:
 
418
  send_document_to_telegram(
419
  pdf_file_to_clean,
420
  filename=f"{pdf_filename_base}.pdf",
421
+ caption=f"{caption_base} - PDF",
422
  is_pdf=True
423
  )
424
  task_results[task_id]['status'] = 'completed'
 
425
  else:
426
  print(f"Task {task_id}: Échec de la génération PDF: {pdf_message}. Envoi du .tex en fallback.")
427
  task_results[task_id]['status'] = 'pdf_error'
428
+ task_results[task_id]['error_detail'] = f"Erreur PDF: {pdf_message}"
429
  send_document_to_telegram(
430
  cleaned_latex,
431
+ filename=f"{pdf_filename_base}.tex",
432
+ caption=f"{caption_base} - Code LaTeX (Erreur PDF: {pdf_message[:100]})"
433
  )
 
434
 
435
+ except genai.types.BlockedPromptException as e_blocked:
436
+ error_msg = f"Prompt ou image bloqué par le filtre de sécurité Gemini: {e_blocked}"
437
+ print(f"Task {task_id}: {error_msg}")
438
+ task_results[task_id]['status'] = 'error'
439
+ task_results[task_id]['error'] = "Le prompt ou l'image a été bloqué par le filtre de sécurité de l'IA. Essayez une autre image ou un prompt différent."
440
+ if not task_results[task_id].get('response'):
441
+ task_results[task_id]['response'] = task_results[task_id]['error']
442
+ send_document_to_telegram(
443
+ f"Erreur tâche {task_id}: {error_msg}",
444
+ filename=f"error_{task_id}.txt",
445
+ caption=f"Erreur Tâche {task_id} - Prompt Bloqué"
446
+ )
447
  except Exception as e_gen:
448
+ error_msg = f"Erreur lors de la génération Gemini ou traitement PDF: {e_gen}"
449
+ print(f"Task {task_id}: {error_msg}")
450
  task_results[task_id]['status'] = 'error'
451
  task_results[task_id]['error'] = f"Erreur de traitement: {str(e_gen)}"
452
+ if not task_results[task_id].get('response'): # S'il n'y a pas de LaTeX partiel
453
+ task_results[task_id]['response'] = task_results[task_id]['error']
454
  send_document_to_telegram(
455
+ f"Erreur tâche {task_id}: {error_msg}\n\nLaTeX partiel (si disponible):\n{task_results[task_id].get('response','')}",
456
  filename=f"error_{task_id}.txt",
457
  caption=f"Erreur Tâche {task_id}"
458
  )
 
459
 
460
+ except ConnectionError as e_conn:
461
+ error_msg = f"Erreur de connexion Gemini: {e_conn}"
462
+ print(f"Task {task_id}: {error_msg}")
463
+ task_results[task_id]['status'] = 'error'
464
+ task_results[task_id]['error'] = str(e_conn)
465
+ task_results[task_id]['response'] = str(e_conn) # Mettre l'erreur dans response pour affichage
466
 
467
  except Exception as e_outer:
468
+ error_msg = f"Exception majeure dans la tâche de fond: {e_outer}"
469
+ print(f"Task {task_id}: {error_msg}")
470
  task_results[task_id]['status'] = 'error'
471
  task_results[task_id]['error'] = f"Erreur système: {str(e_outer)}"
472
+ if not task_results[task_id].get('response'):
473
+ task_results[task_id]['response'] = task_results[task_id]['error']
474
  finally:
475
  if pdf_file_to_clean and os.path.exists(pdf_file_to_clean):
476
  try:
 
496
 
497
  image_data = image_file.read()
498
 
499
+ prompt_type = request.form.get('prompt_type', 'refined')
500
+
501
+ send_to_telegram(image_data, f"Nouvelle image pour résolution (Style: {prompt_type}). Tâche à venir.")
502
 
503
  task_id = str(uuid.uuid4())
504
  task_results[task_id] = {
505
  'status': 'pending',
506
  'response': '',
507
  'error': None,
508
+ 'error_detail': None, # Pour les erreurs spécifiques PDF
509
+ 'time_started': time.time(),
510
+ 'prompt_type': prompt_type
511
  }
512
 
513
  threading.Thread(
514
  target=process_image_background,
515
+ args=(task_id, image_data, prompt_type)
516
  ).start()
517
 
518
  return jsonify({
519
  'task_id': task_id,
520
+ 'status': 'pending' # Le statut initial est pending, le thread mettra à jour
521
  })
522
 
523
  except Exception as e:
 
531
 
532
  task = task_results[task_id]
533
 
 
 
 
 
 
 
 
 
534
  response_data = {
535
  'status': task['status'],
536
  'response': task.get('response'),
537
+ 'error': task.get('error'),
538
+ 'error_detail': task.get('error_detail')
539
  }
 
 
540
 
541
  return jsonify(response_data)
542
 
 
548
  return
549
 
550
  last_status_sent = None
551
+ timeout_start = time.time() # Pour éviter que le stream ne dure indéfiniment
552
+ MAX_STREAM_DURATION = 300 # 5 minutes max pour le stream
553
+
554
  while True:
555
+ if time.time() - timeout_start > MAX_STREAM_DURATION:
556
+ yield f'data: {json.dumps({"error": "Timeout du stream de statut", "status": "error"})}\n\n'
557
+ break
558
+
559
  task = task_results.get(task_id)
560
+ if not task:
561
  yield f'data: {json.dumps({"error": "Tâche disparue ou nettoyée", "status": "error"})}\n\n'
562
  break
563
 
 
565
 
566
  if current_status != last_status_sent:
567
  data_to_send = {"status": current_status}
568
+ if current_status in ['completed', 'completed_tex_only', 'pdf_error']:
569
  data_to_send["response"] = task.get("response", "")
570
+ if current_status in ['error', 'pdf_error']: # S'il y a une erreur générale ou spécifique au PDF
571
  data_to_send["error"] = task.get("error", "Erreur inconnue")
572
  if task.get("error_detail"):
573
  data_to_send["error_detail"] = task.get("error_detail")
574
+ # Envoyer aussi la réponse (LaTeX partiel) s'il y en a une, même en cas d'erreur PDF
575
+ if task.get("response") and not data_to_send.get("response"):
576
  data_to_send["response"] = task.get("response")
577
 
578
+
579
  yield f'data: {json.dumps(data_to_send)}\n\n'
580
  last_status_sent = current_status
581
 
582
  if current_status in ['completed', 'error', 'pdf_error', 'completed_tex_only']:
583
+ break
584
 
585
+ time.sleep(1)
586
 
587
  return Response(
588
  stream_with_context(generate()),
589
  mimetype='text/event-stream',
590
  headers={
591
  'Cache-Control': 'no-cache',
592
+ 'X-Accel-Buffering': 'no',
593
  'Connection': 'keep-alive'
594
  }
595
  )
 
599
  print("CRITICAL: GEMINI_API_KEY variable d'environnement non définie. L'application risque de ne pas fonctionner.")
600
  if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
601
  print("CRITICAL: TELEGRAM_BOT_TOKEN ou TELEGRAM_CHAT_ID non définis. L'intégration Telegram échouera.")
602
+ else:
603
+ print(f"INFO: Prêt à envoyer des notifications Telegram au CHAT_ID: {TELEGRAM_CHAT_ID}")
604
+
605
+ print(f"INFO: LaTeX est {'installé et activé' if IS_LATEX_INSTALLED else 'NON installé/activé. Les PDFs ne seront pas générés.'}")
606
 
607
  # For production, use a proper WSGI server like Gunicorn or uWSGI
608
  app.run(debug=True, host='0.0.0.0', port=5000)