Spaces:
Sleeping
Sleeping
# app.py | |
import os | |
import logging | |
import json | |
from flask import Flask, jsonify, render_template, request | |
from pydantic import BaseModel, Field | |
from typing import List, Optional | |
import psycopg2 | |
from psycopg2.extras import RealDictCursor | |
from google import genai | |
from google.genai import types | |
from utils import load_prompt | |
# --- Configuration de l'application --- | |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
app = Flask(__name__) | |
app.secret_key = os.environ.get("FLASK_SECRET_KEY", "un-secret-par-defaut") | |
# --- Configuration de la base de données et de l'API --- | |
DATABASE_URL = os.environ.get("DATABASE") | |
GOOGLE_API_KEY = os.environ.get("TOKEN") | |
# --- Modèles de Données Pydantic (inchangés) --- | |
class Argument(BaseModel): | |
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.") | |
class Partie(BaseModel): | |
chapeau: str = Field(description="Le paragraphe d'introduction de la partie.") | |
arguments: list[Argument] = Field(description="La liste des paragraphes argumentatifs qui suivent le chapeau.") | |
transition: Optional[str] = Field(description="Phrase ou court paragraphe de transition.", default=None) | |
class Dissertation(BaseModel): | |
sujet: str = Field(description="Le sujet exact de la dissertation, tel que posé par l'utilisateur.") | |
prof: str = Field(description="Le nom du professeur, qui est toujours 'Mariam AI'.", default="Mariam AI") | |
introduction: str = Field(description="L'introduction complète de la dissertation.") | |
parties: List[Partie] | |
conclusion: str = Field(description="La conclusion complète de la dissertation.") | |
# --- Configuration Gemini --- | |
try: | |
if not GOOGLE_API_KEY: | |
logging.warning("La variable d'environnement TOKEN (GOOGLE_API_KEY) n'est pas définie.") | |
client = None | |
else: | |
client = genai.Client(api_key=GOOGLE_API_KEY) | |
except Exception as e: | |
logging.error(f"Erreur lors de l'initialisation du client GenAI: {e}") | |
client = None | |
SAFETY_SETTINGS = [ | |
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"}, | |
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"}, | |
{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"}, | |
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"}, | |
] | |
# --- Helpers de base de données (de l'exemple) --- | |
def create_connection(): | |
"""Crée et retourne une connexion à la base de données PostgreSQL.""" | |
if not DATABASE_URL: | |
logging.error("La variable d'environnement DATABASE n'est pas configurée.") | |
return None | |
try: | |
return psycopg2.connect(DATABASE_URL) | |
except psycopg2.OperationalError as e: | |
logging.error(f"Impossible de se connecter à la base de données : {e}") | |
return None | |
# --- Route Principale --- | |
def philosophie(): | |
return render_template("philosophie.html") | |
# --- NOUVELLE Route API pour lister les cours --- | |
def get_philosophy_courses(): | |
"""Récupère la liste de tous les cours de philosophie pour le menu déroulant.""" | |
conn = create_connection() | |
if not conn: | |
return jsonify({"error": "Connexion à la base de données échouée."}), 503 | |
try: | |
with conn.cursor(cursor_factory=RealDictCursor) as cur: | |
cur.execute("SELECT id, title FROM cours_philosophie ORDER BY title") | |
courses = cur.fetchall() | |
return jsonify(courses) | |
except Exception as e: | |
logging.error(f"Erreur lors de la récupération des cours : {e}") | |
return jsonify({"error": "Erreur interne du serveur lors de la récupération des cours."}), 500 | |
finally: | |
if conn: | |
conn.close() | |
# --- Route API pour la génération de dissertation (MODIFIÉE) --- | |
def generate_dissertation_api(): | |
if not client: | |
return jsonify({"error": "Le service IA n'est pas correctement configuré."}), 503 | |
data = request.json | |
sujet = data.get('question', '').strip() | |
dissertation_type = data.get('type', 'type1').strip() | |
course_id = data.get('courseId') # Nouvel ID de cours optionnel | |
if not sujet: | |
return jsonify({"error": "Le champ 'question' est obligatoire."}), 400 | |
if dissertation_type not in ['type1', 'type2']: | |
return jsonify({"error": "Type de méthodologie invalide."}), 400 | |
# Récupérer le contenu du cours si un ID est fourni | |
context_str = "" | |
if course_id: | |
conn = create_connection() | |
if not conn: | |
return jsonify({"error": "Connexion à la base de données échouée pour récupérer le contexte."}), 503 | |
try: | |
with conn.cursor(cursor_factory=RealDictCursor) as cur: | |
cur.execute("SELECT content FROM cours_philosophie WHERE id = %s", (course_id,)) | |
result = cur.fetchone() | |
if result and result.get('content'): | |
context_str = f"\n\n--- EXTRAIT DE COURS À UTILISER COMME CONTEXTE PRINCIPAL ---\n{result['content']}" | |
except Exception as e: | |
logging.error(f"Erreur lors de la récupération du contexte du cours {course_id}: {e}") | |
# On continue sans le contexte en cas d'erreur DB | |
finally: | |
if conn: | |
conn.close() | |
try: | |
prompt_filename = f"philo_dissertation_{dissertation_type}.txt" | |
prompt_template = load_prompt(prompt_filename) | |
if "Erreur:" in prompt_template: | |
logging.error(f"Fichier de prompt non trouvé : {prompt_filename}") | |
return jsonify({"error": "Configuration du prompt introuvable pour ce type."}), 500 | |
# Injecter le sujet ET le contexte dans le prompt | |
final_prompt = prompt_template.format(phi_prompt=sujet, context=context_str) | |
config = types.GenerateContentConfig( | |
safety_settings=SAFETY_SETTINGS, | |
response_mime_type="application/json", | |
response_schema=Dissertation, | |
) | |
response = client.models.generate_content( | |
model="gemini-2.5-flash", | |
contents=final_prompt, | |
config=config | |
) | |
if response.parsed: | |
return jsonify(response.parsed.dict()) | |
else: | |
logging.error(f"Erreur de parsing de la réponse structurée. Réponse brute : {response.text}") | |
return jsonify({"error": "Le modèle n'a pas pu générer une structure valide."}), 500 | |
except Exception as e: | |
logging.error(f"Erreur de génération Gemini : {e}") | |
return jsonify({"error": f"Une erreur est survenue avec le service IA : {e}"}), 500 | |
if __name__ == '__main__': | |
app.run(debug=True, port=5001) |