Docfile commited on
Commit
3c0f013
·
verified ·
1 Parent(s): 366507d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +49 -34
app.py CHANGED
@@ -1,7 +1,7 @@
1
- # app.py
2
  import os
3
  import logging
4
  import json
 
5
  from datetime import datetime
6
  from flask import Flask, jsonify, render_template, request, send_file
7
  from pydantic import BaseModel, Field
@@ -31,7 +31,7 @@ DISSERTATIONS_FILE = os.path.join(DATA_DIR, "dissertations_log.json")
31
  # Créer le dossier data s'il n'existe pas
32
  os.makedirs(DATA_DIR, exist_ok=True)
33
 
34
- # --- Modèles de Données Pydantic (inchangés) ---
35
  class Argument(BaseModel):
36
  paragraphe_argumentatif: str = Field(description="Un unique paragraphe formant un argument complet. Il doit commencer par un connecteur logique (ex: 'Premièrement,'), suivi de son développement.")
37
 
@@ -65,7 +65,7 @@ SAFETY_SETTINGS = [
65
  {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
66
  ]
67
 
68
- # --- Helpers de base de données (inchangés) ---
69
  def create_connection():
70
  """Crée et retourne une connexion à la base de données PostgreSQL."""
71
  if not DATABASE_URL:
@@ -77,7 +77,7 @@ def create_connection():
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,7 +87,6 @@ def save_dissertation_data(input_data, output_data, success=True, error_message=
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 = {
@@ -122,7 +121,7 @@ def load_dissertations_data():
122
  logging.error(f"Erreur lors du chargement des données: {e}")
123
  return []
124
 
125
- # --- Routes (inchangées) ---
126
  @app.route('/')
127
  def philosophie():
128
  return render_template("philosophie.html")
@@ -131,7 +130,7 @@ def philosophie():
131
  def gestion():
132
  return render_template("gestion.html")
133
 
134
- # --- API de gestion (inchangées) ---
135
  @app.route('/api/gestion/dissertations', methods=['GET'])
136
  def get_dissertations_data():
137
  try:
@@ -166,7 +165,7 @@ def clear_all_dissertations():
166
  logging.error(f"Erreur lors de la suppression générale: {e}")
167
  return jsonify({"success": False, "error": "Erreur lors de la suppression"}), 500
168
 
169
- # --- API pour lister les cours (inchangée) ---
170
  @app.route('/api/philosophy/courses', methods=['GET'])
171
  def get_philosophy_courses():
172
  conn = create_connection()
@@ -184,28 +183,28 @@ def get_philosophy_courses():
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."
@@ -237,29 +236,44 @@ def generate_dissertation_api():
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-2.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:
@@ -276,8 +290,7 @@ def generate_dissertation_api():
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(
@@ -294,11 +307,8 @@ def generate_dissertation_api():
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
 
@@ -319,8 +329,17 @@ def generate_dissertation_api():
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."""
@@ -329,14 +348,10 @@ def generate_pdf_api():
329
  return jsonify({"error": "Aucune donnée de dissertation fournie."}), 400
330
 
331
  try:
332
- # 1. Rendre le template HTML avec les données de la dissertation
333
  html_string = render_template('dissertation_pdf.html', dissertation=dissertation_data)
334
-
335
- # 2. Utiliser WeasyPrint pour convertir le HTML en PDF
336
  html = HTML(string=html_string)
337
  pdf_bytes = html.write_pdf()
338
 
339
- # 3. Envoyer le PDF en tant que fichier à télécharger
340
  return send_file(
341
  io.BytesIO(pdf_bytes),
342
  mimetype='application/pdf',
 
 
1
  import os
2
  import logging
3
  import json
4
+ import tempfile
5
  from datetime import datetime
6
  from flask import Flask, jsonify, render_template, request, send_file
7
  from pydantic import BaseModel, Field
 
31
  # Créer le dossier data s'il n'existe pas
32
  os.makedirs(DATA_DIR, exist_ok=True)
33
 
34
+ # --- Modèles de Données Pydantic ---
35
  class Argument(BaseModel):
36
  paragraphe_argumentatif: str = Field(description="Un unique paragraphe formant un argument complet. Il doit commencer par un connecteur logique (ex: 'Premièrement,'), suivi de son développement.")
37
 
 
65
  {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
66
  ]
67
 
68
+ # --- Helpers de base de données ---
69
  def create_connection():
70
  """Crée et retourne une connexion à la base de données PostgreSQL."""
71
  if not DATABASE_URL:
 
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 ---
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
  question_logged = input_data.get('question', 'N/A (Image upload)')
91
 
92
  record = {
 
121
  logging.error(f"Erreur lors du chargement des données: {e}")
122
  return []
123
 
124
+ # --- Routes ---
125
  @app.route('/')
126
  def philosophie():
127
  return render_template("philosophie.html")
 
130
  def gestion():
131
  return render_template("gestion.html")
132
 
133
+ # --- API de gestion ---
134
  @app.route('/api/gestion/dissertations', methods=['GET'])
135
  def get_dissertations_data():
136
  try:
 
165
  logging.error(f"Erreur lors de la suppression générale: {e}")
166
  return jsonify({"success": False, "error": "Erreur lors de la suppression"}), 500
167
 
168
+ # --- API pour lister les cours ---
169
  @app.route('/api/philosophy/courses', methods=['GET'])
170
  def get_philosophy_courses():
171
  conn = create_connection()
 
183
  if conn:
184
  conn.close()
185
 
186
+ # --- API pour la génération de dissertation (MISE À JOUR avec File Upload API) ---
187
  @app.route('/api/generate_dissertation', methods=['POST'])
188
  def generate_dissertation_api():
189
  if not client:
190
  error_msg = "Le service IA n'est pas correctement configuré."
 
191
  return jsonify({"error": error_msg}), 503
192
 
193
  # Détecter le type de requête et extraire les données
194
  is_file_upload = 'image' in request.files
195
+ data_for_log = {}
196
+ uploaded_file_ref = None # Pour stocker la référence du fichier uploadé
197
 
198
  if is_file_upload:
199
  dissertation_type = request.form.get('type', 'type3').strip()
200
+ data_for_log = {'type': dissertation_type, 'courseId': None}
201
 
202
  if dissertation_type != 'type3':
203
  error_msg = "Le type 3 nécessite un fichier image."
204
  save_dissertation_data(data_for_log, None, False, error_msg)
205
  return jsonify({"error": error_msg}), 400
206
 
207
+ else:
208
  data = request.json
209
  if not data:
210
  error_msg = "Requête JSON vide ou format incorrect."
 
236
  logging.error(f"Fichier de prompt non trouvé : {prompt_filename}")
237
  save_dissertation_data(data_for_log, None, False, error_msg)
238
  return jsonify({"error": error_msg}), 500
 
239
 
240
  # --- Préparation du contenu pour Gemini ---
241
  contents = []
242
+
243
  if dissertation_type == 'type3':
244
  image_file = request.files['image']
 
 
245
 
246
  # Mise à jour du log pour le commentaire de texte
247
  data_for_log['question'] = f"Image: {image_file.filename}"
248
 
249
+ # UPLOAD DU FICHIER VIA L'API FILE UPLOAD DE GEMINI
250
+ # Sauvegarder temporairement le fichier
251
+ with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(image_file.filename)[1]) as temp_file:
252
+ image_file.save(temp_file.name)
253
+ temp_file_path = temp_file.name
254
+
255
+ try:
256
+ # Upload du fichier vers Gemini File API
257
+ logging.info(f"Upload du fichier {image_file.filename} vers Gemini File API...")
258
+ uploaded_file_ref = client.files.upload(file=temp_file_path)
259
+ logging.info(f"Fichier uploadé avec succès. URI: {uploaded_file_ref.uri}")
260
+
261
+ # Contenu: le prompt + la référence du fichier uploadé
262
+ contents = [prompt_template, uploaded_file_ref]
263
+
264
+ finally:
265
+ # Supprimer le fichier temporaire
266
+ try:
267
+ os.unlink(temp_file_path)
268
+ except Exception as e:
269
+ logging.warning(f"Impossible de supprimer le fichier temporaire {temp_file_path}: {e}")
270
+
271
+ # Utiliser le modèle multimodal
272
  model_name = "models/gemini-2.5-flash"
273
 
274
+ else: # Type 1 et 2 (texte uniquement)
275
  context_str = ""
276
+ # Récupération du contexte du cours
277
  if data_for_log.get('courseId'):
278
  conn = create_connection()
279
  if conn:
 
290
 
291
  final_prompt = prompt_template.format(phi_prompt=data_for_log['question'], context=context_str)
292
  contents = [final_prompt]
293
+ model_name = "models/gemini-2.5-flash"
 
294
 
295
  # --- Appel à l'IA ---
296
  config = types.GenerateContentConfig(
 
307
  # --- Traitement de la réponse ---
308
  if response.text:
309
  try:
 
310
  result = json.loads(response.text)
 
311
 
 
312
  if dissertation_type == 'type3' and 'sujet' in result:
313
  data_for_log['question'] = result['sujet']
314
 
 
329
  logging.error(f"Erreur de génération Gemini : {e}")
330
  save_dissertation_data(data_for_log, None, False, error_msg)
331
  return jsonify({"error": error_msg}), 500
332
+
333
+ finally:
334
+ # Nettoyer le fichier uploadé sur Gemini (optionnel mais recommandé)
335
+ if uploaded_file_ref:
336
+ try:
337
+ client.files.delete(name=uploaded_file_ref.name)
338
+ logging.info(f"Fichier {uploaded_file_ref.name} supprimé de Gemini File API")
339
+ except Exception as e:
340
+ logging.warning(f"Impossible de supprimer le fichier {uploaded_file_ref.name}: {e}")
341
 
342
+ # --- ROUTE API POUR LA GÉNÉRATION DE PDF ---
343
  @app.route('/api/generate_pdf', methods=['POST'])
344
  def generate_pdf_api():
345
  """Génère un PDF à partir des données JSON de la dissertation."""
 
348
  return jsonify({"error": "Aucune donnée de dissertation fournie."}), 400
349
 
350
  try:
 
351
  html_string = render_template('dissertation_pdf.html', dissertation=dissertation_data)
 
 
352
  html = HTML(string=html_string)
353
  pdf_bytes = html.write_pdf()
354
 
 
355
  return send_file(
356
  io.BytesIO(pdf_bytes),
357
  mimetype='application/pdf',