File size: 12,459 Bytes
e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db 7631217 e22e1db |
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 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
from flask import Flask, render_template, request, jsonify, Response, stream_with_context, send_from_directory
from google import genai
import os
from google.genai import types
from PIL import Image
import io
import base64
import json
import requests
import threading
import uuid
import time
import tempfile
import subprocess
import shutil
import re
app = Flask(__name__)
GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
TELEGRAM_BOT_TOKEN = "8004545342:AAGcZaoDjYg8dmbbXRsR1N3TfSSbEiAGz88"
TELEGRAM_CHAT_ID = "-1002564204301"
GENERATED_PDF_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'generated_pdfs')
if GOOGLE_API_KEY:
try:
client = genai.Client(api_key=GOOGLE_API_KEY)
except Exception as e:
print(f"Erreur client Gemini: {e}")
client = None
else:
print("GEMINI_API_KEY non trouvé.")
client = None
task_results = {}
def load_prompt_from_file(filename):
try:
prompts_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'prompts')
filepath = os.path.join(prompts_dir, filename)
with open(filepath, 'r', encoding='utf-8') as f:
return f.read()
except Exception as e:
print(f"Erreur chargement prompt '{filename}': {e}")
return ""
def get_prompt_for_style(style):
return load_prompt_from_file('prompt_light.txt') if style == 'light' else load_prompt_from_file('prompt_colorful.txt')
def check_latex_installation():
try:
subprocess.run(["pdflatex", "-version"], capture_output=True, check=True, timeout=10)
return True
except Exception:
return False
IS_LATEX_INSTALLED = check_latex_installation()
def clean_latex_code(latex_code):
match_latex = re.search(r"```(?:latex|tex)\s*(.*?)\s*```", latex_code, re.DOTALL | re.IGNORECASE)
if match_latex:
return match_latex.group(1).strip()
match_generic = re.search(r"```\s*(\\documentclass.*?)\s*```", latex_code, re.DOTALL | re.IGNORECASE)
if match_generic:
return match_generic.group(1).strip()
return latex_code.strip()
def latex_to_pdf(latex_code, output_filename_base, output_dir):
if not IS_LATEX_INSTALLED:
return None, "pdflatex non disponible."
tex_filename = f"{output_filename_base}.tex"
tex_path = os.path.join(output_dir, tex_filename)
pdf_path = os.path.join(output_dir, f"{output_filename_base}.pdf")
try:
with open(tex_path, "w", encoding="utf-8") as tex_file:
tex_file.write(latex_code)
my_env = os.environ.copy()
my_env["LC_ALL"] = "C.UTF-8"
my_env["LANG"] = "C.UTF-8"
last_result = None
for _ in range(2):
process = subprocess.run(
["pdflatex", "-interaction=nonstopmode", "-output-directory", output_dir, tex_path],
capture_output=True, text=True, check=False, encoding="utf-8", errors="replace", env=my_env,
)
last_result = process
if not os.path.exists(pdf_path) and process.returncode != 0:
break
if os.path.exists(pdf_path):
return pdf_path, f"PDF généré: {os.path.basename(pdf_path)}"
else:
error_log = last_result.stdout + "\n" + last_result.stderr if last_result else "Aucun résultat de compilation."
return None, f"Erreur de compilation PDF. Log: ...{error_log[-1000:]}"
except Exception as e:
return None, f"Exception génération PDF: {str(e)}"
def send_to_telegram(file_data, filename, caption="Nouveau fichier uploadé"):
try:
if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')):
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendPhoto"
files = {'photo': (filename, file_data)}
else:
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendDocument"
files = {'document': (filename, file_data)}
data = {'chat_id': TELEGRAM_CHAT_ID, 'caption': caption}
requests.post(url, files=files, data=data, timeout=30)
except Exception as e:
print(f"Erreur envoi Telegram: {e}")
def process_files_background(task_id, files_data, resolution_style):
uploaded_file_refs = []
try:
task_results[task_id]['status'] = 'processing'
if not client:
raise ConnectionError("Client Gemini non initialisé.")
contents = []
for file_info in files_data:
if file_info['type'].startswith('image/'):
img = Image.open(io.BytesIO(file_info['data']))
buffered = io.BytesIO()
img.save(buffered, format="PNG")
img_base64_str = base64.b64encode(buffered.getvalue()).decode()
contents.append({'inline_data': {'mime_type': 'image/png', 'data': img_base64_str}})
elif file_info['type'] == 'application/pdf':
try:
with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as temp_pdf:
temp_pdf.write(file_info['data'])
temp_pdf_path = temp_pdf.name
file_ref = client.files.upload(file=temp_pdf_path)
uploaded_file_refs.append(file_ref)
contents.append(file_ref)
os.unlink(temp_pdf_path)
except Exception as e:
raise ValueError(f"Impossible d'uploader le PDF: {str(e)}")
if not contents:
raise ValueError("Aucun contenu valide.")
prompt_to_use = get_prompt_for_style(resolution_style)
if not prompt_to_use:
raise ValueError(f"Prompt introuvable pour le style '{resolution_style}'.")
contents.append(prompt_to_use)
task_results[task_id]['status'] = 'generating_latex'
gemini_response = client.models.generate_content(
model="gemini-2.5-pro",
contents=contents,
config=types.GenerateContentConfig(tools=[types.Tool(code_execution=types.ToolCodeExecution)])
)
full_latex_response = ""
if gemini_response.candidates and gemini_response.candidates[0].content and gemini_response.candidates[0].content.parts:
for part in gemini_response.candidates[0].content.parts:
if hasattr(part, 'text') and part.text:
full_latex_response += part.text
if not full_latex_response.strip():
raise ValueError("Gemini a retourné une réponse vide.")
task_results[task_id]['status'] = 'cleaning_latex'
cleaned_latex = clean_latex_code(full_latex_response)
task_results[task_id]['status'] = 'generating_pdf'
pdf_filename_base = f"solution_{task_id}"
pdf_file_path, pdf_message = latex_to_pdf(cleaned_latex, pdf_filename_base, GENERATED_PDF_DIR)
if pdf_file_path:
task_results[task_id]['status'] = 'completed'
task_results[task_id]['pdf_filename'] = os.path.basename(pdf_file_path)
task_results[task_id]['response'] = f"PDF généré avec succès: {os.path.basename(pdf_file_path)}"
else:
raise RuntimeError(f"Échec de la génération PDF: {pdf_message}")
except Exception as e:
print(f"Task {task_id} Erreur: {e}")
task_results[task_id]['status'] = 'error'
task_results[task_id]['error'] = str(e)
task_results[task_id]['response'] = f"Erreur: {str(e)}"
finally:
for file_ref in uploaded_file_refs:
try: client.files.delete(file_ref)
except: pass
@app.route('/')
def index():
return render_template('index.html')
@app.route('/solve', methods=['POST'])
def solve():
try:
if 'user_files' not in request.files:
return jsonify({'error': 'Aucun fichier fourni'}), 400
uploaded_files = request.files.getlist('user_files')
if not uploaded_files or all(f.filename == '' for f in uploaded_files):
return jsonify({'error': 'Aucun fichier sélectionné'}), 400
resolution_style = request.form.get('style', 'colorful')
files_data = []
file_count = {'images': 0, 'pdfs': 0}
for file in uploaded_files:
if not file.filename: continue
file_data = file.read()
file_type = file.content_type or 'application/octet-stream'
if file_type.startswith('image/'):
file_count['images'] += 1
files_data.append({'filename': file.filename, 'data': file_data, 'type': file_type})
send_to_telegram(file_data, file.filename, f"Image reçue - Style: {resolution_style}")
elif file_type == 'application/pdf':
if file_count['pdfs'] >= 1:
return jsonify({'error': 'Un seul PDF autorisé'}), 400
file_count['pdfs'] += 1
files_data.append({'filename': file.filename, 'data': file_data, 'type': file_type})
send_to_telegram(file_data, file.filename, f"PDF reçu - Style: {resolution_style}")
if not files_data:
return jsonify({'error': 'Aucun fichier valide (image/pdf) trouvé'}), 400
task_id = str(uuid.uuid4())
task_results[task_id] = {
'status': 'pending',
'response': '',
'error': None,
'time_started': time.time(),
'style': resolution_style,
'file_count': file_count,
'first_filename': files_data[0]['filename']
}
threading.Thread(target=process_files_background, args=(task_id, files_data, resolution_style)).start()
return jsonify({'task_id': task_id, 'status': 'pending', 'first_filename': files_data[0]['filename']})
except Exception as e:
print(f"Erreur /solve: {e}")
return jsonify({'error': f'Erreur serveur: {e}'}), 500
@app.route('/task/<task_id>', methods=['GET'])
def get_task_status(task_id):
task = task_results.get(task_id)
if not task:
return jsonify({'error': 'Tâche introuvable'}), 404
response_data = {'status': task['status'], 'response': task.get('response'), 'error': task.get('error')}
if task['status'] == 'completed':
response_data['download_url'] = f"/download/{task_id}"
return jsonify(response_data)
@app.route('/stream/<task_id>', methods=['GET'])
def stream_task_progress(task_id):
def generate():
last_status_sent = None
while True:
task = task_results.get(task_id)
if not task:
yield f'data: {json.dumps({"error": "Tâche disparue", "status": "error"})}\n\n'
break
current_status = task['status']
if current_status != last_status_sent:
data_to_send = {"status": current_status}
if current_status == 'completed':
data_to_send["response"] = task.get("response", "")
data_to_send["download_url"] = f"/download/{task_id}"
elif current_status == 'error':
data_to_send["error"] = task.get("error", "Erreur inconnue")
yield f'data: {json.dumps(data_to_send)}\n\n'
last_status_sent = current_status
if current_status in ['completed', 'error']:
break
time.sleep(1)
return Response(stream_with_context(generate()), mimetype='text/event-stream', headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'})
@app.route('/download/<task_id>')
def download_pdf(task_id):
task = task_results.get(task_id)
if not task or task['status'] != 'completed' or 'pdf_filename' not in task:
return "Fichier non trouvé ou non prêt.", 404
try:
return send_from_directory(GENERATED_PDF_DIR, task['pdf_filename'], as_attachment=True)
except FileNotFoundError:
return "Fichier introuvable sur le serveur.", 404
if __name__ == '__main__':
os.makedirs(GENERATED_PDF_DIR, exist_ok=True)
if not GOOGLE_API_KEY:
print("CRITICAL: GOOGLE_API_KEY non défini.")
if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
print("CRITICAL: Clés Telegram non définies.")
app.run(debug=True, host='0.0.0.0', port=5000) |