Docfile commited on
Commit
77e12b5
·
verified ·
1 Parent(s): cec8423

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +209 -11
app.py CHANGED
@@ -10,6 +10,7 @@ from psycopg2.extras import RealDictCursor
10
  from google import genai
11
  from google.genai import types
12
  from utils import load_prompt
 
13
 
14
  # --- Configuration de l'application ---
15
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -54,7 +55,7 @@ SAFETY_SETTINGS = [
54
  {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
55
  ]
56
 
57
- # --- Helpers de base de données (de l'exemple) ---
58
  def create_connection():
59
  """Crée et retourne une connexion à la base de données PostgreSQL."""
60
  if not DATABASE_URL:
@@ -66,12 +67,68 @@ def create_connection():
66
  logging.error(f"Impossible de se connecter à la base de données : {e}")
67
  return None
68
 
69
- # --- Route Principale ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  @app.route('/')
71
  def philosophie():
72
  return render_template("philosophie.html")
73
 
74
- # --- NOUVELLE Route API pour lister les cours ---
 
 
 
 
 
75
  @app.route('/api/philosophy/courses', methods=['GET'])
76
  def get_philosophy_courses():
77
  """Récupère la liste de tous les cours de philosophie pour le menu déroulant."""
@@ -90,7 +147,130 @@ def get_philosophy_courses():
90
  if conn:
91
  conn.close()
92
 
93
- # --- Route API pour la génération de dissertation (MODIFIÉE) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  @app.route('/api/generate_dissertation', methods=['POST'])
95
  def generate_dissertation_api():
96
  if not client:
@@ -99,7 +279,7 @@ def generate_dissertation_api():
99
  data = request.json
100
  sujet = data.get('question', '').strip()
101
  dissertation_type = data.get('type', 'type1').strip()
102
- course_id = data.get('courseId') # Nouvel ID de cours optionnel
103
 
104
  if not sujet:
105
  return jsonify({"error": "Le champ 'question' est obligatoire."}), 400
@@ -109,19 +289,21 @@ def generate_dissertation_api():
109
 
110
  # Récupérer le contenu du cours si un ID est fourni
111
  context_str = ""
 
112
  if course_id:
113
  conn = create_connection()
114
  if not conn:
115
  return jsonify({"error": "Connexion à la base de données échouée pour récupérer le contexte."}), 503
116
  try:
117
  with conn.cursor(cursor_factory=RealDictCursor) as cur:
118
- cur.execute("SELECT content FROM cours_philosophie WHERE id = %s", (course_id,))
119
  result = cur.fetchone()
120
- if result and result.get('content'):
121
- context_str = f"\n\n--- EXTRAIT DE COURS À UTILISER COMME CONTEXTE PRINCIPAL ---\n{result['content']}"
 
 
122
  except Exception as e:
123
  logging.error(f"Erreur lors de la récupération du contexte du cours {course_id}: {e}")
124
- # On continue sans le contexte en cas d'erreur DB
125
  finally:
126
  if conn:
127
  conn.close()
@@ -134,7 +316,6 @@ def generate_dissertation_api():
134
  logging.error(f"Fichier de prompt non trouvé : {prompt_filename}")
135
  return jsonify({"error": "Configuration du prompt introuvable pour ce type."}), 500
136
 
137
- # Injecter le sujet ET le contexte dans le prompt
138
  final_prompt = prompt_template.format(phi_prompt=sujet, context=context_str)
139
 
140
  config = types.GenerateContentConfig(
@@ -150,7 +331,23 @@ def generate_dissertation_api():
150
  )
151
 
152
  if response.parsed:
153
- return jsonify(response.parsed.dict())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  else:
155
  logging.error(f"Erreur de parsing de la réponse structurée. Réponse brute : {response.text}")
156
  return jsonify({"error": "Le modèle n'a pas pu générer une structure valide."}), 500
@@ -160,4 +357,5 @@ def generate_dissertation_api():
160
  return jsonify({"error": f"Une erreur est survenue avec le service IA : {e}"}), 500
161
 
162
  if __name__ == '__main__':
 
163
  app.run(debug=True, port=5001)
 
10
  from google import genai
11
  from google.genai import types
12
  from utils import load_prompt
13
+ from datetime import datetime
14
 
15
  # --- Configuration de l'application ---
16
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
55
  {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
56
  ]
57
 
58
+ # --- Helpers de base de données ---
59
  def create_connection():
60
  """Crée et retourne une connexion à la base de données PostgreSQL."""
61
  if not DATABASE_URL:
 
67
  logging.error(f"Impossible de se connecter à la base de données : {e}")
68
  return None
69
 
70
+ def init_database():
71
+ """Initialise la table de sauvegarde des dissertations si elle n'existe pas."""
72
+ conn = create_connection()
73
+ if not conn:
74
+ return False
75
+ try:
76
+ with conn.cursor() as cur:
77
+ cur.execute("""
78
+ CREATE TABLE IF NOT EXISTS dissertations (
79
+ id SERIAL PRIMARY KEY,
80
+ user_ip VARCHAR(45),
81
+ user_agent TEXT,
82
+ question TEXT NOT NULL,
83
+ dissertation_type VARCHAR(10) NOT NULL,
84
+ course_id INTEGER,
85
+ course_title VARCHAR(255),
86
+ generated_content JSONB,
87
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
88
+ pdf_generated BOOLEAN DEFAULT FALSE
89
+ )
90
+ """)
91
+ conn.commit()
92
+ return True
93
+ except Exception as e:
94
+ logging.error(f"Erreur lors de l'initialisation de la base de données : {e}")
95
+ return False
96
+ finally:
97
+ if conn:
98
+ conn.close()
99
+
100
+ def save_dissertation(user_ip, user_agent, question, dissertation_type, course_id, course_title, content):
101
+ """Sauvegarde une dissertation générée en base de données."""
102
+ conn = create_connection()
103
+ if not conn:
104
+ return None
105
+ try:
106
+ with conn.cursor() as cur:
107
+ cur.execute("""
108
+ INSERT INTO dissertations (user_ip, user_agent, question, dissertation_type, course_id, course_title, generated_content)
109
+ VALUES (%s, %s, %s, %s, %s, %s, %s)
110
+ RETURNING id
111
+ """, (user_ip, user_agent, question, dissertation_type, course_id, course_title, json.dumps(content)))
112
+ conn.commit()
113
+ return cur.fetchone()[0]
114
+ except Exception as e:
115
+ logging.error(f"Erreur lors de la sauvegarde de la dissertation : {e}")
116
+ return None
117
+ finally:
118
+ if conn:
119
+ conn.close()
120
+
121
+ # --- Routes Principales ---
122
  @app.route('/')
123
  def philosophie():
124
  return render_template("philosophie.html")
125
 
126
+ @app.route('/gestion')
127
+ def gestion():
128
+ """Page de gestion pour afficher toutes les dissertations."""
129
+ return render_template("gestion.html")
130
+
131
+ # --- Routes API ---
132
  @app.route('/api/philosophy/courses', methods=['GET'])
133
  def get_philosophy_courses():
134
  """Récupère la liste de tous les cours de philosophie pour le menu déroulant."""
 
147
  if conn:
148
  conn.close()
149
 
150
+ @app.route('/api/dissertations', methods=['GET'])
151
+ def get_dissertations():
152
+ """Récupère toutes les dissertations avec pagination."""
153
+ page = int(request.args.get('page', 1))
154
+ per_page = int(request.args.get('per_page', 10))
155
+ search = request.args.get('search', '').strip()
156
+
157
+ conn = create_connection()
158
+ if not conn:
159
+ return jsonify({"error": "Connexion à la base de données échouée."}), 503
160
+
161
+ try:
162
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
163
+ # Construire la requête avec recherche optionnelle
164
+ base_query = """
165
+ SELECT d.*, COUNT(*) OVER() as total_count
166
+ FROM dissertations d
167
+ """
168
+ where_clause = ""
169
+ params = []
170
+
171
+ if search:
172
+ where_clause = " WHERE d.question ILIKE %s OR d.course_title ILIKE %s"
173
+ params = [f"%{search}%", f"%{search}%"]
174
+
175
+ query = base_query + where_clause + """
176
+ ORDER BY d.created_at DESC
177
+ LIMIT %s OFFSET %s
178
+ """
179
+ params.extend([per_page, (page - 1) * per_page])
180
+
181
+ cur.execute(query, params)
182
+ results = cur.fetchall()
183
+
184
+ total_count = results[0]['total_count'] if results else 0
185
+ dissertations = []
186
+
187
+ for row in results:
188
+ dissertations.append({
189
+ 'id': row['id'],
190
+ 'user_ip': row['user_ip'],
191
+ 'user_agent': row['user_agent'],
192
+ 'question': row['question'],
193
+ 'dissertation_type': row['dissertation_type'],
194
+ 'course_id': row['course_id'],
195
+ 'course_title': row['course_title'],
196
+ 'created_at': row['created_at'].isoformat() if row['created_at'] else None,
197
+ 'pdf_generated': row['pdf_generated'],
198
+ 'content_preview': row['generated_content'].get('introduction', '')[:200] + '...' if row['generated_content'] else ''
199
+ })
200
+
201
+ return jsonify({
202
+ 'dissertations': dissertations,
203
+ 'total': total_count,
204
+ 'page': page,
205
+ 'per_page': per_page,
206
+ 'total_pages': (total_count + per_page - 1) // per_page
207
+ })
208
+
209
+ except Exception as e:
210
+ logging.error(f"Erreur lors de la récupération des dissertations : {e}")
211
+ return jsonify({"error": "Erreur interne du serveur."}), 500
212
+ finally:
213
+ if conn:
214
+ conn.close()
215
+
216
+ @app.route('/api/dissertations/<int:dissertation_id>', methods=['GET'])
217
+ def get_dissertation_detail(dissertation_id):
218
+ """Récupère le détail complet d'une dissertation."""
219
+ conn = create_connection()
220
+ if not conn:
221
+ return jsonify({"error": "Connexion à la base de données échouée."}), 503
222
+
223
+ try:
224
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
225
+ cur.execute("""
226
+ SELECT * FROM dissertations WHERE id = %s
227
+ """, (dissertation_id,))
228
+ result = cur.fetchone()
229
+
230
+ if not result:
231
+ return jsonify({"error": "Dissertation non trouvée."}), 404
232
+
233
+ return jsonify({
234
+ 'id': result['id'],
235
+ 'user_ip': result['user_ip'],
236
+ 'user_agent': result['user_agent'],
237
+ 'question': result['question'],
238
+ 'dissertation_type': result['dissertation_type'],
239
+ 'course_id': result['course_id'],
240
+ 'course_title': result['course_title'],
241
+ 'created_at': result['created_at'].isoformat() if result['created_at'] else None,
242
+ 'pdf_generated': result['pdf_generated'],
243
+ 'generated_content': result['generated_content']
244
+ })
245
+
246
+ except Exception as e:
247
+ logging.error(f"Erreur lors de la récupération de la dissertation {dissertation_id} : {e}")
248
+ return jsonify({"error": "Erreur interne du serveur."}), 500
249
+ finally:
250
+ if conn:
251
+ conn.close()
252
+
253
+ @app.route('/api/dissertations/<int:dissertation_id>', methods=['DELETE'])
254
+ def delete_dissertation(dissertation_id):
255
+ """Supprime une dissertation."""
256
+ conn = create_connection()
257
+ if not conn:
258
+ return jsonify({"error": "Connexion à la base de données échouée."}), 503
259
+
260
+ try:
261
+ with conn.cursor() as cur:
262
+ cur.execute("DELETE FROM dissertations WHERE id = %s", (dissertation_id,))
263
+ if cur.rowcount == 0:
264
+ return jsonify({"error": "Dissertation non trouvée."}), 404
265
+ conn.commit()
266
+ return jsonify({"message": "Dissertation supprimée avec succès."})
267
+ except Exception as e:
268
+ logging.error(f"Erreur lors de la suppression de la dissertation {dissertation_id} : {e}")
269
+ return jsonify({"error": "Erreur interne du serveur."}), 500
270
+ finally:
271
+ if conn:
272
+ conn.close()
273
+
274
  @app.route('/api/generate_dissertation', methods=['POST'])
275
  def generate_dissertation_api():
276
  if not client:
 
279
  data = request.json
280
  sujet = data.get('question', '').strip()
281
  dissertation_type = data.get('type', 'type1').strip()
282
+ course_id = data.get('courseId')
283
 
284
  if not sujet:
285
  return jsonify({"error": "Le champ 'question' est obligatoire."}), 400
 
289
 
290
  # Récupérer le contenu du cours si un ID est fourni
291
  context_str = ""
292
+ course_title = None
293
  if course_id:
294
  conn = create_connection()
295
  if not conn:
296
  return jsonify({"error": "Connexion à la base de données échouée pour récupérer le contexte."}), 503
297
  try:
298
  with conn.cursor(cursor_factory=RealDictCursor) as cur:
299
+ cur.execute("SELECT title, content FROM cours_philosophie WHERE id = %s", (course_id,))
300
  result = cur.fetchone()
301
+ if result:
302
+ course_title = result['title']
303
+ if result.get('content'):
304
+ context_str = f"\n\n--- EXTRAIT DE COURS À UTILISER COMME CONTEXTE PRINCIPAL ---\n{result['content']}"
305
  except Exception as e:
306
  logging.error(f"Erreur lors de la récupération du contexte du cours {course_id}: {e}")
 
307
  finally:
308
  if conn:
309
  conn.close()
 
316
  logging.error(f"Fichier de prompt non trouvé : {prompt_filename}")
317
  return jsonify({"error": "Configuration du prompt introuvable pour ce type."}), 500
318
 
 
319
  final_prompt = prompt_template.format(phi_prompt=sujet, context=context_str)
320
 
321
  config = types.GenerateContentConfig(
 
331
  )
332
 
333
  if response.parsed:
334
+ # Sauvegarder en base de données
335
+ user_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', 'unknown'))
336
+ user_agent = request.headers.get('User-Agent', 'unknown')
337
+
338
+ dissertation_id = save_dissertation(
339
+ user_ip=user_ip,
340
+ user_agent=user_agent,
341
+ question=sujet,
342
+ dissertation_type=dissertation_type,
343
+ course_id=course_id,
344
+ course_title=course_title,
345
+ content=response.parsed.dict()
346
+ )
347
+
348
+ result = response.parsed.dict()
349
+ result['dissertation_id'] = dissertation_id
350
+ return jsonify(result)
351
  else:
352
  logging.error(f"Erreur de parsing de la réponse structurée. Réponse brute : {response.text}")
353
  return jsonify({"error": "Le modèle n'a pas pu générer une structure valide."}), 500
 
357
  return jsonify({"error": f"Une erreur est survenue avec le service IA : {e}"}), 500
358
 
359
  if __name__ == '__main__':
360
+ init_database() # Initialiser la base de données au démarrage
361
  app.run(debug=True, port=5001)