Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
import os
|
| 3 |
import logging
|
| 4 |
import json
|
|
|
|
| 5 |
from flask import Flask, jsonify, render_template, request
|
| 6 |
from pydantic import BaseModel, Field
|
| 7 |
from typing import List, Optional
|
|
@@ -20,6 +21,13 @@ app.secret_key = os.environ.get("FLASK_SECRET_KEY", "un-secret-par-defaut")
|
|
| 20 |
DATABASE_URL = os.environ.get("DATABASE")
|
| 21 |
GOOGLE_API_KEY = os.environ.get("TOKEN")
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
# --- Modèles de Données Pydantic (inchangés) ---
|
| 24 |
class Argument(BaseModel):
|
| 25 |
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.")
|
|
@@ -66,11 +74,140 @@ 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():
|
|
@@ -90,11 +227,13 @@ 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:
|
| 97 |
-
|
|
|
|
|
|
|
| 98 |
|
| 99 |
data = request.json
|
| 100 |
sujet = data.get('question', '').strip()
|
|
@@ -102,17 +241,23 @@ def generate_dissertation_api():
|
|
| 102 |
course_id = data.get('courseId') # Nouvel ID de cours optionnel
|
| 103 |
|
| 104 |
if not sujet:
|
| 105 |
-
|
|
|
|
|
|
|
| 106 |
|
| 107 |
if dissertation_type not in ['type1', 'type2']:
|
| 108 |
-
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
| 116 |
try:
|
| 117 |
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
| 118 |
cur.execute("SELECT content FROM cours_philosophie WHERE id = %s", (course_id,))
|
|
@@ -131,8 +276,10 @@ def generate_dissertation_api():
|
|
| 131 |
prompt_template = load_prompt(prompt_filename)
|
| 132 |
|
| 133 |
if "Erreur:" in prompt_template:
|
| 134 |
-
|
| 135 |
-
|
|
|
|
|
|
|
| 136 |
|
| 137 |
# Injecter le sujet ET le contexte dans le prompt
|
| 138 |
final_prompt = prompt_template.format(phi_prompt=sujet, context=context_str)
|
|
@@ -150,14 +297,21 @@ def generate_dissertation_api():
|
|
| 150 |
)
|
| 151 |
|
| 152 |
if response.parsed:
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
| 154 |
else:
|
|
|
|
| 155 |
logging.error(f"Erreur de parsing de la réponse structurée. Réponse brute : {response.text}")
|
| 156 |
-
|
|
|
|
| 157 |
|
| 158 |
except Exception as e:
|
|
|
|
| 159 |
logging.error(f"Erreur de génération Gemini : {e}")
|
| 160 |
-
|
|
|
|
| 161 |
|
| 162 |
if __name__ == '__main__':
|
| 163 |
app.run(debug=True, port=5001)
|
|
|
|
| 2 |
import os
|
| 3 |
import logging
|
| 4 |
import json
|
| 5 |
+
from datetime import datetime
|
| 6 |
from flask import Flask, jsonify, render_template, request
|
| 7 |
from pydantic import BaseModel, Field
|
| 8 |
from typing import List, Optional
|
|
|
|
| 21 |
DATABASE_URL = os.environ.get("DATABASE")
|
| 22 |
GOOGLE_API_KEY = os.environ.get("TOKEN")
|
| 23 |
|
| 24 |
+
# Dossier pour stocker les données de gestion
|
| 25 |
+
DATA_DIR = "data"
|
| 26 |
+
DISSERTATIONS_FILE = os.path.join(DATA_DIR, "dissertations_log.json")
|
| 27 |
+
|
| 28 |
+
# Créer le dossier data s'il n'existe pas
|
| 29 |
+
os.makedirs(DATA_DIR, exist_ok=True)
|
| 30 |
+
|
| 31 |
# --- Modèles de Données Pydantic (inchangés) ---
|
| 32 |
class Argument(BaseModel):
|
| 33 |
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.")
|
|
|
|
| 74 |
logging.error(f"Impossible de se connecter à la base de données : {e}")
|
| 75 |
return None
|
| 76 |
|
| 77 |
+
# --- Helpers pour la gestion des données ---
|
| 78 |
+
def save_dissertation_data(input_data, output_data, success=True, error_message=None):
|
| 79 |
+
"""Sauvegarde les données d'entrée et de sortie dans un fichier JSON."""
|
| 80 |
+
try:
|
| 81 |
+
# Lire les données existantes
|
| 82 |
+
if os.path.exists(DISSERTATIONS_FILE):
|
| 83 |
+
with open(DISSERTATIONS_FILE, 'r', encoding='utf-8') as f:
|
| 84 |
+
data = json.load(f)
|
| 85 |
+
else:
|
| 86 |
+
data = []
|
| 87 |
+
|
| 88 |
+
# Préparer le nouvel enregistrement
|
| 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 |
+
},
|
| 96 |
+
"output": output_data if success else None,
|
| 97 |
+
"success": success,
|
| 98 |
+
"error": error_message,
|
| 99 |
+
"id": len(data) + 1
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
# Ajouter le nouvel enregistrement
|
| 103 |
+
data.append(record)
|
| 104 |
+
|
| 105 |
+
# Sauvegarder
|
| 106 |
+
with open(DISSERTATIONS_FILE, 'w', encoding='utf-8') as f:
|
| 107 |
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 108 |
+
|
| 109 |
+
except Exception as e:
|
| 110 |
+
logging.error(f"Erreur lors de la sauvegarde des données: {e}")
|
| 111 |
+
|
| 112 |
+
def load_dissertations_data():
|
| 113 |
+
"""Charge toutes les données des dissertations depuis le fichier JSON."""
|
| 114 |
+
try:
|
| 115 |
+
if os.path.exists(DISSERTATIONS_FILE):
|
| 116 |
+
with open(DISSERTATIONS_FILE, 'r', encoding='utf-8') as f:
|
| 117 |
+
return json.load(f)
|
| 118 |
+
return []
|
| 119 |
+
except Exception as e:
|
| 120 |
+
logging.error(f"Erreur lors du chargement des données: {e}")
|
| 121 |
+
return []
|
| 122 |
+
|
| 123 |
# --- Route Principale ---
|
| 124 |
@app.route('/')
|
| 125 |
def philosophie():
|
| 126 |
return render_template("philosophie.html")
|
| 127 |
|
| 128 |
+
# --- Route de Gestion ---
|
| 129 |
+
@app.route('/gestion')
|
| 130 |
+
def gestion():
|
| 131 |
+
return render_template("gestion.html")
|
| 132 |
+
|
| 133 |
+
# --- API pour récupérer les données de gestion ---
|
| 134 |
+
@app.route('/api/gestion/dissertations', methods=['GET'])
|
| 135 |
+
def get_dissertations_data():
|
| 136 |
+
"""Récupère toutes les données des dissertations générées."""
|
| 137 |
+
try:
|
| 138 |
+
data = load_dissertations_data()
|
| 139 |
+
return jsonify({
|
| 140 |
+
"success": True,
|
| 141 |
+
"data": data,
|
| 142 |
+
"total": len(data)
|
| 143 |
+
})
|
| 144 |
+
except Exception as e:
|
| 145 |
+
logging.error(f"Erreur lors de la récupération des données de gestion: {e}")
|
| 146 |
+
return jsonify({
|
| 147 |
+
"success": False,
|
| 148 |
+
"error": "Erreur lors de la récupération des données"
|
| 149 |
+
}), 500
|
| 150 |
+
|
| 151 |
+
# --- API pour supprimer une entrée ---
|
| 152 |
+
@app.route('/api/gestion/dissertations/<int:record_id>', methods=['DELETE'])
|
| 153 |
+
def delete_dissertation_record(record_id):
|
| 154 |
+
"""Supprime un enregistrement spécifique."""
|
| 155 |
+
try:
|
| 156 |
+
data = load_dissertations_data()
|
| 157 |
+
|
| 158 |
+
# Trouver l'index de l'enregistrement à supprimer
|
| 159 |
+
record_index = None
|
| 160 |
+
for i, record in enumerate(data):
|
| 161 |
+
if record.get('id') == record_id:
|
| 162 |
+
record_index = i
|
| 163 |
+
break
|
| 164 |
+
|
| 165 |
+
if record_index is None:
|
| 166 |
+
return jsonify({
|
| 167 |
+
"success": False,
|
| 168 |
+
"error": "Enregistrement non trouvé"
|
| 169 |
+
}), 404
|
| 170 |
+
|
| 171 |
+
# Supprimer l'enregistrement
|
| 172 |
+
deleted_record = data.pop(record_index)
|
| 173 |
+
|
| 174 |
+
# Sauvegarder
|
| 175 |
+
with open(DISSERTATIONS_FILE, 'w', encoding='utf-8') as f:
|
| 176 |
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 177 |
+
|
| 178 |
+
return jsonify({
|
| 179 |
+
"success": True,
|
| 180 |
+
"message": "Enregistrement supprimé avec succès"
|
| 181 |
+
})
|
| 182 |
+
|
| 183 |
+
except Exception as e:
|
| 184 |
+
logging.error(f"Erreur lors de la suppression: {e}")
|
| 185 |
+
return jsonify({
|
| 186 |
+
"success": False,
|
| 187 |
+
"error": "Erreur lors de la suppression"
|
| 188 |
+
}), 500
|
| 189 |
+
|
| 190 |
+
# --- API pour vider toutes les données ---
|
| 191 |
+
@app.route('/api/gestion/dissertations/clear', methods=['DELETE'])
|
| 192 |
+
def clear_all_dissertations():
|
| 193 |
+
"""Vide toutes les données des dissertations."""
|
| 194 |
+
try:
|
| 195 |
+
# Créer un fichier vide
|
| 196 |
+
with open(DISSERTATIONS_FILE, 'w', encoding='utf-8') as f:
|
| 197 |
+
json.dump([], f)
|
| 198 |
+
|
| 199 |
+
return jsonify({
|
| 200 |
+
"success": True,
|
| 201 |
+
"message": "Toutes les données ont été supprimées"
|
| 202 |
+
})
|
| 203 |
+
|
| 204 |
+
except Exception as e:
|
| 205 |
+
logging.error(f"Erreur lors de la suppression générale: {e}")
|
| 206 |
+
return jsonify({
|
| 207 |
+
"success": False,
|
| 208 |
+
"error": "Erreur lors de la suppression"
|
| 209 |
+
}), 500
|
| 210 |
+
|
| 211 |
# --- NOUVELLE Route API pour lister les cours ---
|
| 212 |
@app.route('/api/philosophy/courses', methods=['GET'])
|
| 213 |
def get_philosophy_courses():
|
|
|
|
| 227 |
if conn:
|
| 228 |
conn.close()
|
| 229 |
|
| 230 |
+
# --- Route API pour la génération de dissertation (MODIFIÉE avec logging) ---
|
| 231 |
@app.route('/api/generate_dissertation', methods=['POST'])
|
| 232 |
def generate_dissertation_api():
|
| 233 |
if not client:
|
| 234 |
+
error_msg = "Le service IA n'est pas correctement configuré."
|
| 235 |
+
save_dissertation_data(request.json or {}, None, False, error_msg)
|
| 236 |
+
return jsonify({"error": error_msg}), 503
|
| 237 |
|
| 238 |
data = request.json
|
| 239 |
sujet = data.get('question', '').strip()
|
|
|
|
| 241 |
course_id = data.get('courseId') # Nouvel ID de cours optionnel
|
| 242 |
|
| 243 |
if not sujet:
|
| 244 |
+
error_msg = "Le champ 'question' est obligatoire."
|
| 245 |
+
save_dissertation_data(data, None, False, error_msg)
|
| 246 |
+
return jsonify({"error": error_msg}), 400
|
| 247 |
|
| 248 |
if dissertation_type not in ['type1', 'type2']:
|
| 249 |
+
error_msg = "Type de méthodologie invalide."
|
| 250 |
+
save_dissertation_data(data, None, False, error_msg)
|
| 251 |
+
return jsonify({"error": error_msg}), 400
|
| 252 |
|
| 253 |
# Récupérer le contenu du cours si un ID est fourni
|
| 254 |
context_str = ""
|
| 255 |
if course_id:
|
| 256 |
conn = create_connection()
|
| 257 |
if not conn:
|
| 258 |
+
error_msg = "Connexion à la base de données échouée pour récupérer le contexte."
|
| 259 |
+
save_dissertation_data(data, None, False, error_msg)
|
| 260 |
+
return jsonify({"error": error_msg}), 503
|
| 261 |
try:
|
| 262 |
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
| 263 |
cur.execute("SELECT content FROM cours_philosophie WHERE id = %s", (course_id,))
|
|
|
|
| 276 |
prompt_template = load_prompt(prompt_filename)
|
| 277 |
|
| 278 |
if "Erreur:" in prompt_template:
|
| 279 |
+
error_msg = "Configuration du prompt introuvable pour ce type."
|
| 280 |
+
logging.error(f"Fichier de prompt non trouvé : {prompt_filename}")
|
| 281 |
+
save_dissertation_data(data, None, False, error_msg)
|
| 282 |
+
return jsonify({"error": error_msg}), 500
|
| 283 |
|
| 284 |
# Injecter le sujet ET le contexte dans le prompt
|
| 285 |
final_prompt = prompt_template.format(phi_prompt=sujet, context=context_str)
|
|
|
|
| 297 |
)
|
| 298 |
|
| 299 |
if response.parsed:
|
| 300 |
+
result = response.parsed.dict()
|
| 301 |
+
# Sauvegarder les données avec succès
|
| 302 |
+
save_dissertation_data(data, result, True)
|
| 303 |
+
return jsonify(result)
|
| 304 |
else:
|
| 305 |
+
error_msg = "Le modèle n'a pas pu générer une structure valide."
|
| 306 |
logging.error(f"Erreur de parsing de la réponse structurée. Réponse brute : {response.text}")
|
| 307 |
+
save_dissertation_data(data, None, False, error_msg)
|
| 308 |
+
return jsonify({"error": error_msg}), 500
|
| 309 |
|
| 310 |
except Exception as e:
|
| 311 |
+
error_msg = f"Une erreur est survenue avec le service IA : {e}"
|
| 312 |
logging.error(f"Erreur de génération Gemini : {e}")
|
| 313 |
+
save_dissertation_data(data, None, False, error_msg)
|
| 314 |
+
return jsonify({"error": error_msg}), 500
|
| 315 |
|
| 316 |
if __name__ == '__main__':
|
| 317 |
app.run(debug=True, port=5001)
|