Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
from flask import Flask, render_template, request, send_file, jsonify
|
2 |
from google import genai
|
|
|
3 |
from PIL import Image
|
4 |
import subprocess
|
5 |
import tempfile
|
@@ -10,7 +11,7 @@ import base64
|
|
10 |
|
11 |
app = Flask(__name__)
|
12 |
|
13 |
-
# ---
|
14 |
MODEL_SINGLE_GENERATION = "gemini-2.5-pro-exp-03-25" # Modèle avec "thinking" pour la génération complète
|
15 |
LATEX_MENTION = r"\vspace{1cm}\noindent\textit{Ce devoir a été généré par Mariam AI. \url{https://mariam-241.vercel.app}}"
|
16 |
|
@@ -78,11 +79,44 @@ def clean_latex(raw_latex_text):
|
|
78 |
|
79 |
return cleaned.strip() # Retourne le résultat nettoyé
|
80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
# --- Fonction Principale (API GenAI) ---
|
82 |
|
83 |
def generate_complete_latex(client, image_bytes):
|
84 |
"""
|
85 |
-
Génère une solution complète en LaTeX à partir de l'image en
|
|
|
86 |
"""
|
87 |
try:
|
88 |
# Convertir les bytes en image PIL
|
@@ -90,17 +124,44 @@ def generate_complete_latex(client, image_bytes):
|
|
90 |
|
91 |
prompt = f"""
|
92 |
# ROLE & OBJECTIF
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
"""
|
105 |
|
106 |
response = client.models.generate_content(
|
@@ -108,57 +169,34 @@ def generate_complete_latex(client, image_bytes):
|
|
108 |
contents=[image, prompt],
|
109 |
config=genai.types.GenerateContentConfig(
|
110 |
temperature=0.3,
|
111 |
-
tools=[
|
112 |
-
code_execution=genai.types.ToolCodeExecution()
|
113 |
-
)]
|
114 |
)
|
115 |
)
|
116 |
|
117 |
-
|
118 |
-
latex_content_raw = None
|
119 |
-
thinking_content = None
|
120 |
-
code_execution_results = []
|
121 |
-
|
122 |
-
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
|
123 |
-
for part in response.candidates[0].content.parts:
|
124 |
-
if getattr(part, 'thought', None):
|
125 |
-
thinking_content = part.text
|
126 |
-
elif getattr(part, 'executable_code', None):
|
127 |
-
# Capture le code exécutable et son résultat
|
128 |
-
code = part.executable_code.code
|
129 |
-
result = getattr(part, 'code_execution_result', None)
|
130 |
-
result_output = result.output if result else "Pas de résultat d'exécution"
|
131 |
-
code_execution_results.append({
|
132 |
-
'code': code,
|
133 |
-
'result': result_output
|
134 |
-
})
|
135 |
-
elif part.text:
|
136 |
-
# Le contenu texte contient probablement le LaTeX
|
137 |
-
if '\\documentclass' in part.text:
|
138 |
-
latex_content_raw = part.text
|
139 |
-
|
140 |
-
# Si aucun contenu LaTeX n'a été trouvé dans les parties, chercher dans le texte global
|
141 |
-
if not latex_content_raw and response.text:
|
142 |
-
if '\\documentclass' in response.text:
|
143 |
-
latex_content_raw = response.text
|
144 |
|
145 |
if latex_content_raw:
|
146 |
latex_content_cleaned = clean_latex(latex_content_raw)
|
147 |
|
148 |
-
#
|
149 |
if LATEX_MENTION not in latex_content_cleaned:
|
|
|
150 |
end_doc_tag = "\\end{document}"
|
151 |
if end_doc_tag in latex_content_cleaned:
|
152 |
latex_content_cleaned = latex_content_cleaned.replace(end_doc_tag, f"{LATEX_MENTION}\n{end_doc_tag}")
|
153 |
else:
|
|
|
154 |
latex_content_cleaned += f"\n{LATEX_MENTION}\n{end_doc_tag}"
|
155 |
|
156 |
-
return latex_content_cleaned, thinking_content, None
|
157 |
else:
|
158 |
-
return None, thinking_content, "Aucun contenu LaTeX n'a été généré par le modèle."
|
159 |
|
|
|
|
|
160 |
except Exception as e:
|
161 |
-
return None, None, f"❌ Erreur lors de la génération du LaTeX: {str(e)}"
|
|
|
162 |
# --- Fonction de Compilation LaTeX ---
|
163 |
|
164 |
def compile_latex_to_pdf(latex_content):
|
@@ -285,8 +323,8 @@ def process():
|
|
285 |
if client is None:
|
286 |
return jsonify({'success': False, 'message': message})
|
287 |
|
288 |
-
# Générer le LaTeX
|
289 |
-
latex_content, thinking_content, error
|
290 |
if error:
|
291 |
return jsonify({'success': False, 'message': error})
|
292 |
|
@@ -297,8 +335,7 @@ def process():
|
|
297 |
'success': False,
|
298 |
'message': message,
|
299 |
'latex': latex_content,
|
300 |
-
'thinking': thinking_content
|
301 |
-
'code_execution': code_execution_results
|
302 |
})
|
303 |
|
304 |
# Encoder le PDF en base64 pour le retourner au front-end
|
@@ -310,11 +347,9 @@ def process():
|
|
310 |
'message': 'PDF généré avec succès',
|
311 |
'latex': latex_content,
|
312 |
'thinking': thinking_content,
|
313 |
-
'code_execution': code_execution_results,
|
314 |
'pdf_base64': pdf_base64
|
315 |
})
|
316 |
|
317 |
-
|
318 |
@app.route('/download-pdf', methods=['POST'])
|
319 |
def download_pdf():
|
320 |
if 'pdf_data' not in request.json:
|
|
|
1 |
from flask import Flask, render_template, request, send_file, jsonify
|
2 |
from google import genai
|
3 |
+
from google.genai import types
|
4 |
from PIL import Image
|
5 |
import subprocess
|
6 |
import tempfile
|
|
|
11 |
|
12 |
app = Flask(__name__)
|
13 |
|
14 |
+
# --- Constantes ---
|
15 |
MODEL_SINGLE_GENERATION = "gemini-2.5-pro-exp-03-25" # Modèle avec "thinking" pour la génération complète
|
16 |
LATEX_MENTION = r"\vspace{1cm}\noindent\textit{Ce devoir a été généré par Mariam AI. \url{https://mariam-241.vercel.app}}"
|
17 |
|
|
|
79 |
|
80 |
return cleaned.strip() # Retourne le résultat nettoyé
|
81 |
|
82 |
+
def extract_content_from_response(response):
|
83 |
+
"""
|
84 |
+
Extrait le contenu LaTeX et le raisonnement (thought) de la réponse Gemini.
|
85 |
+
Prend également en compte les résultats d'exécution de code.
|
86 |
+
"""
|
87 |
+
latex_content_raw = None
|
88 |
+
thinking_content = []
|
89 |
+
code_execution_results = []
|
90 |
+
|
91 |
+
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
|
92 |
+
for part in response.candidates[0].content.parts:
|
93 |
+
# Extraire le thinking
|
94 |
+
if getattr(part, 'thought', None):
|
95 |
+
thinking_content.append(part.text)
|
96 |
+
|
97 |
+
# Extraire le code et les résultats d'exécution
|
98 |
+
if getattr(part, 'executable_code', None):
|
99 |
+
thinking_content.append(f"Code généré:\n{part.executable_code.code}")
|
100 |
+
|
101 |
+
if getattr(part, 'code_execution_result', None):
|
102 |
+
code_execution_results.append(part.code_execution_result.output)
|
103 |
+
thinking_content.append(f"Résultat d'exécution:\n{part.code_execution_result.output}")
|
104 |
+
|
105 |
+
# Extraire le texte (contenu LaTeX)
|
106 |
+
if getattr(part, 'text', None) and not latex_content_raw:
|
107 |
+
latex_content_raw = part.text
|
108 |
+
|
109 |
+
# Combiner les éléments de thinking_content
|
110 |
+
thinking_content = "\n\n".join(thinking_content)
|
111 |
+
|
112 |
+
return latex_content_raw, thinking_content, code_execution_results
|
113 |
+
|
114 |
# --- Fonction Principale (API GenAI) ---
|
115 |
|
116 |
def generate_complete_latex(client, image_bytes):
|
117 |
"""
|
118 |
+
Génère une solution complète en LaTeX à partir de l'image en une seule étape.
|
119 |
+
Utilise l'exécution de code pour améliorer les réponses mathématiques.
|
120 |
"""
|
121 |
try:
|
122 |
# Convertir les bytes en image PIL
|
|
|
124 |
|
125 |
prompt = f"""
|
126 |
# ROLE & OBJECTIF
|
127 |
+
Agis en tant qu'expert en mathématiques et tuteur pédagogue. Ton objectif est de créer une correction détaillée et irréprochable pour l'exercice mathématique présenté dans l'image fournie. La correction doit être sous forme de document LaTeX complet et directement compilable.
|
128 |
+
|
129 |
+
# CONTEXTE
|
130 |
+
- **Input:** Une image contenant un exercice de mathématiques.
|
131 |
+
- **Niveau Cible:** Élève de Terminale S (Lycée, filière scientifique française).
|
132 |
+
- **Output Attendu:** Un fichier source LaTeX (.tex) autonome.
|
133 |
+
|
134 |
+
# TÂCHE PRINCIPALE
|
135 |
+
1. Analyse l'image pour comprendre parfaitement l'énoncé de l'exercice.
|
136 |
+
2. Résous l'exercice de manière rigoureuse, étape par étape.
|
137 |
+
3. Utilise l'exécution de code Python quand nécessaire pour vérifier tes calculs, résoudre des équations ou générer des visualisations.
|
138 |
+
4. Rédige la solution complète directement en code LaTeX, en respectant **toutes** les spécifications ci-dessous.
|
139 |
+
|
140 |
+
# SPÉCIFICATIONS TECHNIQUES DU CODE LATEX
|
141 |
+
1. **Structure du Document:** Commence **strictement** par `\\documentclass{{article}}` et se termine **strictement** par `\\end{{document}}`.
|
142 |
+
2. **Packages Requis:** Inclus impérativement les packages suivants via `\\usepackage{{...}}`: `amsmath`, `amssymb`, `geometry`, `hyperref`, `url`. Assure-toi qu'ils sont déclarés dans le préambule.
|
143 |
+
3. **Compilabilité:** Le code généré doit être valide et compilable sans erreur avec `pdflatex`.
|
144 |
+
4. **Formatage du Code:** Produis un code LaTeX propre, bien indenté et lisible.
|
145 |
+
5. **Environnements Mathématiques:** Utilise les environnements LaTeX appropriés (`align`, `equation`, `gather`, etc.) pour présenter les calculs et les équations de manière claire et standard.
|
146 |
+
6. **AUCUN Marqueur de Code:** Le résultat doit être **uniquement** le code LaTeX brut. N'inclus **JAMAIS** de marqueurs de code comme ```latex ... ``` ou ``` ... ``` au début ou à la fin.
|
147 |
+
|
148 |
+
# STYLE & CONTENU DE LA SOLUTION
|
149 |
+
1. **Pédagogie:** La correction doit être claire, aérée et facile à comprendre pour un élève de Terminale S.
|
150 |
+
2. **Justifications:** Justifie **chaque** étape clé du raisonnement mathématique. Explique *pourquoi* une certaine méthode est utilisée ou *comment* on passe d'une étape à l'autre.
|
151 |
+
3. **Rigueur:** Assure l'exactitude mathématique complète de la solution.
|
152 |
+
4. **Structure Logique:** Organise la solution de manière logique. Utilise des sections (`\\section*{{...}}`, `\\subsection*{{...}}`) si cela améliore la clarté pour des problèmes longs ou multi-parties.
|
153 |
+
5. **Mention Obligatoire:** Insère la ligne suivante **exactement** telle quelle, juste avant la ligne `\\end{{document}}`:
|
154 |
+
{LATEX_MENTION}
|
155 |
+
|
156 |
+
# UTILISATION DU CODE PYTHON
|
157 |
+
1. Utilise Python pour vérifier tes calculs, résoudre des équations complexes ou générer des visualisations.
|
158 |
+
2. Lorsque tu utilises l'exécution de code, assure-toi que les résultats sont correctement intégrés dans ta solution LaTeX.
|
159 |
+
3. N'affiche pas le code Python lui-même dans le document LaTeX final, utilise-le uniquement comme outil pour améliorer la qualité de ta correction.
|
160 |
+
|
161 |
+
# PROCESSUS INTERNE RECOMMANDÉ (Pour l'IA)
|
162 |
+
1. **Analyse Approfondie:** Décompose le problème en sous-étapes logiques.
|
163 |
+
2. **Résolution Étape par Étape:** Effectue la résolution mathématique complète en interne.
|
164 |
+
3. **Traduction en LaTeX:** Convertis ta résolution raisonnée en code LaTeX, en appliquant méticuleusement toutes les spécifications de formatage et de style demandées
|
165 |
"""
|
166 |
|
167 |
response = client.models.generate_content(
|
|
|
169 |
contents=[image, prompt],
|
170 |
config=genai.types.GenerateContentConfig(
|
171 |
temperature=0.3,
|
172 |
+
tools=[types.Tool(code_execution=types.ToolCodeExecution)]
|
|
|
|
|
173 |
)
|
174 |
)
|
175 |
|
176 |
+
latex_content_raw, thinking_content, code_execution_results = extract_content_from_response(response)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
177 |
|
178 |
if latex_content_raw:
|
179 |
latex_content_cleaned = clean_latex(latex_content_raw)
|
180 |
|
181 |
+
# Vérification supplémentaire : est-ce que la mention obligatoire est présente ?
|
182 |
if LATEX_MENTION not in latex_content_cleaned:
|
183 |
+
# Tente de l'insérer avant \end{document}
|
184 |
end_doc_tag = "\\end{document}"
|
185 |
if end_doc_tag in latex_content_cleaned:
|
186 |
latex_content_cleaned = latex_content_cleaned.replace(end_doc_tag, f"{LATEX_MENTION}\n{end_doc_tag}")
|
187 |
else:
|
188 |
+
# Si \end{document} n'est pas là, ajoute les deux à la fin
|
189 |
latex_content_cleaned += f"\n{LATEX_MENTION}\n{end_doc_tag}"
|
190 |
|
191 |
+
return latex_content_cleaned, thinking_content, None
|
192 |
else:
|
193 |
+
return None, thinking_content, "Aucun contenu LaTeX n'a été généré par le modèle."
|
194 |
|
195 |
+
except genai.types.StopCandidateException as e:
|
196 |
+
return None, None, "❌ Génération stoppée (possible contenu inapproprié)"
|
197 |
except Exception as e:
|
198 |
+
return None, None, f"❌ Erreur lors de la génération du LaTeX: {str(e)}"
|
199 |
+
|
200 |
# --- Fonction de Compilation LaTeX ---
|
201 |
|
202 |
def compile_latex_to_pdf(latex_content):
|
|
|
323 |
if client is None:
|
324 |
return jsonify({'success': False, 'message': message})
|
325 |
|
326 |
+
# Générer le LaTeX
|
327 |
+
latex_content, thinking_content, error = generate_complete_latex(client, image_bytes)
|
328 |
if error:
|
329 |
return jsonify({'success': False, 'message': error})
|
330 |
|
|
|
335 |
'success': False,
|
336 |
'message': message,
|
337 |
'latex': latex_content,
|
338 |
+
'thinking': thinking_content
|
|
|
339 |
})
|
340 |
|
341 |
# Encoder le PDF en base64 pour le retourner au front-end
|
|
|
347 |
'message': 'PDF généré avec succès',
|
348 |
'latex': latex_content,
|
349 |
'thinking': thinking_content,
|
|
|
350 |
'pdf_base64': pdf_base64
|
351 |
})
|
352 |
|
|
|
353 |
@app.route('/download-pdf', methods=['POST'])
|
354 |
def download_pdf():
|
355 |
if 'pdf_data' not in request.json:
|