Docfile commited on
Commit
f77d475
·
verified ·
1 Parent(s): 9012a0c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +113 -53
app.py CHANGED
@@ -11,8 +11,9 @@ from psycopg2.extras import RealDictCursor
11
  from google import genai
12
  from google.genai import types
13
  from utils import load_prompt
14
- from weasyprint import HTML # <-- NOUVEL IMPORT
15
- import io # <-- NOUVEL IMPORT
 
16
 
17
  # --- Configuration de l'application ---
18
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -40,13 +41,13 @@ class Partie(BaseModel):
40
  transition: Optional[str] = Field(description="Phrase ou court paragraphe de transition.", default=None)
41
 
42
  class Dissertation(BaseModel):
43
- sujet: str = Field(description="Le sujet exact de la dissertation, tel que posé par l'utilisateur.")
44
  prof: str = Field(description="Le nom du professeur, qui est toujours 'Mariam AI'.", default="Mariam AI")
45
  introduction: str = Field(description="L'introduction complète de la dissertation.")
46
  parties: List[Partie]
47
  conclusion: str = Field(description="La conclusion complète de la dissertation.")
48
 
49
- # --- Configuration Gemini (inchangée) ---
50
  try:
51
  if not GOOGLE_API_KEY:
52
  logging.warning("La variable d'environnement TOKEN (GOOGLE_API_KEY) n'est pas définie.")
@@ -76,7 +77,7 @@ def create_connection():
76
  logging.error(f"Impossible de se connecter à la base de données : {e}")
77
  return None
78
 
79
- # --- Helpers pour la gestion des données (inchangés) ---
80
  def save_dissertation_data(input_data, output_data, success=True, error_message=None):
81
  """Sauvegarde les données d'entrée et de sortie dans un fichier JSON."""
82
  try:
@@ -86,10 +87,13 @@ def save_dissertation_data(input_data, output_data, success=True, error_message=
86
  else:
87
  data = []
88
 
 
 
 
89
  record = {
90
  "timestamp": datetime.now().isoformat(),
91
  "input": {
92
- "question": input_data.get('question', ''),
93
  "type": input_data.get('type', ''),
94
  "courseId": input_data.get('courseId')
95
  },
@@ -180,87 +184,143 @@ def get_philosophy_courses():
180
  if conn:
181
  conn.close()
182
 
183
- # --- API pour la génération de dissertation (inchangée) ---
184
  @app.route('/api/generate_dissertation', methods=['POST'])
185
  def generate_dissertation_api():
186
  if not client:
187
  error_msg = "Le service IA n'est pas correctement configuré."
188
- save_dissertation_data(request.json or {}, None, False, error_msg)
189
  return jsonify({"error": error_msg}), 503
190
 
191
- data = request.json
192
- sujet = data.get('question', '').strip()
193
- dissertation_type = data.get('type', 'type1').strip()
194
- course_id = data.get('courseId')
195
 
196
- if not sujet:
197
- error_msg = "Le champ 'question' est obligatoire."
198
- save_dissertation_data(data, None, False, error_msg)
199
- return jsonify({"error": error_msg}), 400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
- if dissertation_type not in ['type1', 'type2']:
202
- error_msg = "Type de méthodologie invalide."
203
- save_dissertation_data(data, None, False, error_msg)
204
- return jsonify({"error": error_msg}), 400
205
-
206
- context_str = ""
207
- if course_id:
208
- conn = create_connection()
209
- if not conn:
210
- error_msg = "Connexion à la base de données échouée pour récupérer le contexte."
211
- save_dissertation_data(data, None, False, error_msg)
212
- return jsonify({"error": error_msg}), 503
213
- try:
214
- with conn.cursor(cursor_factory=RealDictCursor) as cur:
215
- cur.execute("SELECT content FROM cours_philosophie WHERE id = %s", (course_id,))
216
- result = cur.fetchone()
217
- if result and result.get('content'):
218
- context_str = f"\n\n--- EXTRAIT DE COURS À UTILISER COMME CONTEXTE PRINCIPAL ---\n{result['content']}"
219
- except Exception as e:
220
- logging.error(f"Erreur lors de la récupération du contexte du cours {course_id}: {e}")
221
- finally:
222
- if conn:
223
- conn.close()
224
 
225
  try:
226
  prompt_filename = f"philo_dissertation_{dissertation_type}.txt"
227
  prompt_template = load_prompt(prompt_filename)
228
 
229
  if "Erreur:" in prompt_template:
230
- error_msg = "Configuration du prompt introuvable pour ce type."
231
  logging.error(f"Fichier de prompt non trouvé : {prompt_filename}")
232
- save_dissertation_data(data, None, False, error_msg)
233
  return jsonify({"error": error_msg}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
 
235
- final_prompt = prompt_template.format(phi_prompt=sujet, context=context_str)
 
236
  config = types.GenerateContentConfig(
237
  safety_settings=SAFETY_SETTINGS,
238
  response_mime_type="application/json",
239
  response_schema=Dissertation,
240
  )
241
  response = client.models.generate_content(
242
- model="gemini-flash-latest",
243
- contents=final_prompt,
244
  config=config
245
  )
246
 
247
- if response.parsed:
248
- result = response.parsed.dict()
249
- save_dissertation_data(data, result, True)
250
- return jsonify(result)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  else:
252
- error_msg = "Le modèle n'a pas pu générer une structure valide."
253
- logging.error(f"Erreur de parsing de la réponse structurée. Réponse brute : {response.text}")
254
- save_dissertation_data(data, None, False, error_msg)
255
  return jsonify({"error": error_msg}), 500
256
 
257
  except Exception as e:
258
  error_msg = f"Une erreur est survenue avec le service IA : {e}"
259
  logging.error(f"Erreur de génération Gemini : {e}")
260
- save_dissertation_data(data, None, False, error_msg)
261
  return jsonify({"error": error_msg}), 500
262
 
263
- # --- NOUVELLE ROUTE API POUR LA GÉNÉRATION DE PDF ---
264
  @app.route('/api/generate_pdf', methods=['POST'])
265
  def generate_pdf_api():
266
  """Génère un PDF à partir des données JSON de la dissertation."""
 
11
  from google import genai
12
  from google.genai import types
13
  from utils import load_prompt
14
+ from weasyprint import HTML
15
+ import io
16
+ import mimetypes
17
 
18
  # --- Configuration de l'application ---
19
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
41
  transition: Optional[str] = Field(description="Phrase ou court paragraphe de transition.", default=None)
42
 
43
  class Dissertation(BaseModel):
44
+ sujet: str = Field(description="Le sujet exact de la dissertation, tel que posé par l'utilisateur (ou le type de texte analysé).")
45
  prof: str = Field(description="Le nom du professeur, qui est toujours 'Mariam AI'.", default="Mariam AI")
46
  introduction: str = Field(description="L'introduction complète de la dissertation.")
47
  parties: List[Partie]
48
  conclusion: str = Field(description="La conclusion complète de la dissertation.")
49
 
50
+ # --- Configuration Gemini ---
51
  try:
52
  if not GOOGLE_API_KEY:
53
  logging.warning("La variable d'environnement TOKEN (GOOGLE_API_KEY) n'est pas définie.")
 
77
  logging.error(f"Impossible de se connecter à la base de données : {e}")
78
  return None
79
 
80
+ # --- Helpers pour la gestion des données (inchangés dans leur fonction, ajustés pour le logging) ---
81
  def save_dissertation_data(input_data, output_data, success=True, error_message=None):
82
  """Sauvegarde les données d'entrée et de sortie dans un fichier JSON."""
83
  try:
 
87
  else:
88
  data = []
89
 
90
+ # S'assurer que 'question' est bien dans input_data
91
+ question_logged = input_data.get('question', 'N/A (Image upload)')
92
+
93
  record = {
94
  "timestamp": datetime.now().isoformat(),
95
  "input": {
96
+ "question": question_logged,
97
  "type": input_data.get('type', ''),
98
  "courseId": input_data.get('courseId')
99
  },
 
184
  if conn:
185
  conn.close()
186
 
187
+ # --- API pour la génération de dissertation (MISE À JOUR pour le Type 3) ---
188
  @app.route('/api/generate_dissertation', methods=['POST'])
189
  def generate_dissertation_api():
190
  if not client:
191
  error_msg = "Le service IA n'est pas correctement configuré."
192
+ # Le logging d'erreur ici est plus complexe car on ne sait pas encore si on a du JSON ou des fichiers
193
  return jsonify({"error": error_msg}), 503
194
 
195
+ # Détecter le type de requête et extraire les données
196
+ is_file_upload = 'image' in request.files
197
+ data_for_log = {} # Structure pour le log
 
198
 
199
+ if is_file_upload:
200
+ dissertation_type = request.form.get('type', 'type3').strip()
201
+ data_for_log = {'type': dissertation_type, 'courseId': None} # Pas de courseId pour type3
202
+
203
+ if dissertation_type != 'type3':
204
+ error_msg = "Le type 3 nécessite un fichier image."
205
+ save_dissertation_data(data_for_log, None, False, error_msg)
206
+ return jsonify({"error": error_msg}), 400
207
+
208
+ else: # JSON (Type 1 ou Type 2)
209
+ data = request.json
210
+ if not data:
211
+ error_msg = "Requête JSON vide ou format incorrect."
212
+ save_dissertation_data({'type': 'unknown'}, None, False, error_msg)
213
+ return jsonify({"error": error_msg}), 400
214
+
215
+ dissertation_type = data.get('type', 'type1').strip()
216
+ sujet = data.get('question', '').strip()
217
+ course_id = data.get('courseId')
218
 
219
+ data_for_log = {'type': dissertation_type, 'question': sujet, 'courseId': course_id}
220
+
221
+ if dissertation_type not in ['type1', 'type2']:
222
+ error_msg = "Type de méthodologie invalide."
223
+ save_dissertation_data(data_for_log, None, False, error_msg)
224
+ return jsonify({"error": error_msg}), 400
225
+
226
+ if not sujet:
227
+ error_msg = "Le champ 'question' est obligatoire."
228
+ save_dissertation_data(data_for_log, None, False, error_msg)
229
+ return jsonify({"error": error_msg}), 400
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
  try:
232
  prompt_filename = f"philo_dissertation_{dissertation_type}.txt"
233
  prompt_template = load_prompt(prompt_filename)
234
 
235
  if "Erreur:" in prompt_template:
236
+ error_msg = f"Configuration du prompt introuvable pour le type '{dissertation_type}'."
237
  logging.error(f"Fichier de prompt non trouvé : {prompt_filename}")
238
+ save_dissertation_data(data_for_log, None, False, error_msg)
239
  return jsonify({"error": error_msg}), 500
240
+
241
+
242
+ # --- Préparation du contenu pour Gemini ---
243
+ contents = []
244
+ if dissertation_type == 'type3':
245
+ image_file = request.files['image']
246
+ image_bytes = image_file.read()
247
+ mime_type = image_file.mimetype or mimetypes.guess_type(image_file.filename)[0]
248
+
249
+ # Mise à jour du log pour le commentaire de texte
250
+ data_for_log['question'] = f"Image: {image_file.filename}"
251
+
252
+ # Contenu multimodal: le prompt + l'image
253
+ contents = [
254
+ prompt_template,
255
+ types.Part.from_bytes(data=image_bytes, mime_type=mime_type)
256
+ ]
257
+ # Utiliser le modèle multimodal 1.5
258
+ model_name = "models/gemini-1.5-flash"
259
+
260
+ else: # Type 1 et 2 (texte uniquement)
261
+ context_str = ""
262
+ # Récupération du contexte du cours (uniquement pour type 1 et 2)
263
+ if data_for_log.get('courseId'):
264
+ conn = create_connection()
265
+ if conn:
266
+ try:
267
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
268
+ cur.execute("SELECT content FROM cours_philosophie WHERE id = %s", (data_for_log['courseId'],))
269
+ result = cur.fetchone()
270
+ if result and result.get('content'):
271
+ context_str = f"\n\n--- EXTRAIT DE COURS À UTILISER COMME CONTEXTE PRINCIPAL ---\n{result['content']}"
272
+ except Exception as e:
273
+ logging.error(f"Erreur lors de la récupération du contexte du cours {data_for_log['courseId']}: {e}")
274
+ finally:
275
+ conn.close()
276
+
277
+ final_prompt = prompt_template.format(phi_prompt=data_for_log['question'], context=context_str)
278
+ contents = [final_prompt]
279
+ model_name = "models/gemini-2.5-flash" # Modèle texte optimisé
280
 
281
+
282
+ # --- Appel à l'IA ---
283
  config = types.GenerateContentConfig(
284
  safety_settings=SAFETY_SETTINGS,
285
  response_mime_type="application/json",
286
  response_schema=Dissertation,
287
  )
288
  response = client.models.generate_content(
289
+ model=model_name,
290
+ contents=contents,
291
  config=config
292
  )
293
 
294
+ # --- Traitement de la réponse ---
295
+ if response.text:
296
+ try:
297
+ # response.text contient la chaîne JSON
298
+ result = json.loads(response.text)
299
+ # Validation Pydantic optionnelle ici, mais on se fie au modèle pour être rigide
300
+
301
+ # S'assurer que le sujet est bien rempli pour le log
302
+ if dissertation_type == 'type3' and 'sujet' in result:
303
+ data_for_log['question'] = result['sujet']
304
+
305
+ save_dissertation_data(data_for_log, result, True)
306
+ return jsonify(result)
307
+ except json.JSONDecodeError:
308
+ error_msg = "Le modèle n'a pas pu générer une structure JSON valide."
309
+ logging.error(f"Erreur JSON Decode: Réponse brute : {response.text}")
310
+ save_dissertation_data(data_for_log, None, False, error_msg)
311
+ return jsonify({"error": error_msg}), 500
312
  else:
313
+ error_msg = "Le modèle n'a retourné aucune réponse textuelle."
314
+ save_dissertation_data(data_for_log, None, False, error_msg)
 
315
  return jsonify({"error": error_msg}), 500
316
 
317
  except Exception as e:
318
  error_msg = f"Une erreur est survenue avec le service IA : {e}"
319
  logging.error(f"Erreur de génération Gemini : {e}")
320
+ save_dissertation_data(data_for_log, None, False, error_msg)
321
  return jsonify({"error": error_msg}), 500
322
 
323
+ # --- ROUTE API POUR LA GÉNÉRATION DE PDF (inchangée) ---
324
  @app.route('/api/generate_pdf', methods=['POST'])
325
  def generate_pdf_api():
326
  """Génère un PDF à partir des données JSON de la dissertation."""