Mariam-cc / app.py
Docfile's picture
Update app.py
5027e3b verified
raw
history blame
26.6 kB
from flask import Flask, render_template, request, jsonify, Response, stream_with_context
from google import genai
from google.genai import types
import os
from PIL import Image
import tempfile
import json
import logging # Optional: for better logging
app = Flask(__name__)
# Configure logging (optional but recommended)
logging.basicConfig(level=logging.INFO)
# Configuration de l'API Gemini
token = os.environ.get("TOKEN")
if not token:
logging.error("API Token (TOKEN environment variable) not found!")
# Handle the error appropriately - maybe exit or raise an exception
# For now, we'll let it potentially fail later if the client is used without a key
genai_client = genai.Client(api_key=token)
SAFETY_SETTINGS = [
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold=types.HarmBlockThreshold.BLOCK_NONE,
),
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
threshold=types.HarmBlockThreshold.BLOCK_NONE,
),
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
threshold=types.HarmBlockThreshold.BLOCK_NONE,
),
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold=types.HarmBlockThreshold.BLOCK_NONE,
)
]
# --- Prompts (Keep them as they are) ---
prompt_tableau = """
Traite selon cette méthodologie :
La Méthodologie de la Lecture Méthodique
La lecture méthodique est une analyse approfondie et interprétative d'un extrait de texte. Son objectif est de comprendre comment le texte est construit (sa forme, sa structure, son langage) pour produire du sens et des effets sur le lecteur. Elle se déroule en plusieurs étapes structurées.
Étape 1 : Observation Initiale et Identification du Texte
(Cette partie reste identique à la précédente version : Examen Visuel, Identification Contextuelle, Première Qualification sur le genre, la forme de discours et l'objectif principal.)
Examen Visuel : Mise en page, longueur, paragraphes, typographie...
Identification Contextuelle : Source (auteur, œuvre, date...), situation de l'extrait.
Première Qualification : Genre littéraire, forme de discours (discours/récit), objectif principal (narratif, descriptif, argumentatif, explicatif/informatif).
Étape 2 : Compréhension Globale du Texte
(Cette partie reste identique : S'assurer de la compréhension littérale via des questions clés.)
Thème(s), Situation (Qui, quoi, où, quand ?), Énonciation (Qui parle ? Point de vue/Focalisation ?), Progression, Temps verbaux dominants.
Étape 3 : Analyse Approfondie et Structurée
C'est le cœur de la lecture méthodique. Il s'agit d'analyser comment le texte produit du sens et des effets, en s'appuyant sur des outils précis et nommés individuellement.
Définir les Axes de Lecture :
Trouver deux axes de lecture principaux (idées directrices).
Diviser chaque axe en deux sous-axes qui précisent l'idée principale.
Choisir et Utiliser les Outils d'Analyse Littéraire :
Pour chaque sous-axe, sélectionner deux outils d'analyse pertinents et spécifiques. Il est crucial d'utiliser la terminologie technique reconnue (les noms "officiels") pour chaque outil. Voici des exemples d'outils spécifiques (cette liste n'est pas exhaustive) :
Lexique : Champ lexical (préciser lequel : ex. Champ lexical de la nature), Registre de langue (préciser : ex. Registre familier), Terme mélioratif, Terme péjoratif, Néologisme, Archaïsme, Connotation. Etc...
Grammaire et Syntaxe : Mode verbal (préciser : ex. Subjonctif à valeur de souhait), Temps verbal (préciser avec sa valeur : ex. Imparfait descriptif, Passé simple de premier plan), Type de phrase (ex. Phrase interrogative), Forme de phrase (ex. Forme exclamative, Forme négative), Pronom personnel (préciser lequel et sa référence/valeur : ex. Pronom "on" indéfini), Connecteur logique (préciser : ex. Connecteur d'opposition "mais"), Adjectif qualificatif (noter sa fonction ou valeur : ex. Adjectif antéposé à valeur subjective), Adverbe (noter sa valeur : ex. Adverbe d'intensité). Etc...
Figures de Style : Comparaison, Métaphore, Personnification, Allégorie, Métonymie, Synecdoque, Périphrase, Antithèse, Oxymore, Chiasme, Parallélisme, Anaphore, Accumulation, Gradation, Hyperbole, Litote, Euphémisme, Ironie, Prétérition, Allitération, Assonance. Etc...
Énonciation : Indice de personne (ex. Marque de la première personne "je"), Modalisation (préciser : ex. Adverbe de certitude, Verbe d'opinion), Discours direct, Discours indirect, Discours indirect libre, Focalisation (préciser : Focalisation interne, Focalisation externe, Focalisation zéro). Etc...
Structure et Rythme : Paragraphe (analyser sa fonction), Strophe, Versification (si poésie : type de vers, rimes...), Ponctuation expressive (ex. Points de suspension), Longueur de phrase (ex. Phrase courte et nominale). Etc...
Tonalité / Registre Littéraire : Tonalité lyrique, Tonalité pathétique, Tonalité tragique, Tonalité comique, Tonalité satirique, Tonalité polémique, Tonalité épique, Tonalité fantastique. (Identifier la tonalité dominante ou spécifique à un passage). Etc...
Organiser l'Analyse dans un Tableau :
Présenter les résultats pour chaque sous-axe dans un tableau à trois colonnes :
Je ne veux pas qu'un outil apparaisse deux fois. Et Aussi j'aimerais uniquement des noms officiel.
Outils d'analyse : Nommer précisément et individuellement l'outil identifié. Utilisez le terme technique exact. Un seul outil par ligne/entrée. Par exemple, écrivez "Métaphore" sur une ligne, et si vous trouvez aussi une comparaison pertinente, écrivez "Comparaison" sur une autre ligne. N'écrivez jamais "Métaphore/Comparaison" ou "Figure de style" de manière générale.
Repérage / Citation : Citer le ou les passages exacts du texte où l'outil est repéré.
Interprétation : Expliquer l'effet produit par cet outil spécifique dans le contexte, en lien avec le sous-axe et l'axe de lecture.
Rappel : Analyser un Fait de Langue (Processus en 3 temps pour l'Interprétation)
(Cette partie reste identique : Observation/Repérage [Quoi ?], Explication technique [Comment ?], Interprétation [Pourquoi ? Quel effet ? Quel sens ?])
Structure Finale de la Présentation de l'Analyse (Synthèse des consignes)
L'analyse méthodique doit être présentée de manière structurée :
Deux axes de lecture distincts.
Chaque axe subdivisé en deux sous-axes.
Chaque sous-axe analysé via trois outils d'analyse spécifiques, nommés individuellement avec leur terme technique reconnu et officiel.
Résultats présentés en tableau(x) ("Outils d'analyse", "Repérage / Citation", "Interprétation"), en respectant la règle un seul outil par entrée dans la première colonne.
"""
prompt_redaction = """
III - LE COMMENTAIRE COMPOSÉ
PRÉSENTATION DE L'EXERCICE
Le commentaire composé, sujet de type 2, est un exercice écrit présenté au baccalauréat. C'est un exercice qui se fait à la base d'un texte. Procéder au commentaire composé d'un texte, c'est rendre compte de sa lecture. Lire un texte, c'est l'analyser, l'expliquer, l'interpréter, le décoder afin de le rendre compréhensible et accessible à tous. L'explication qui sera faite du texte découle des impressions premières dégagées après la lecture de ce dernier. Par ailleurs, ces impressions ou hypothèses de lecture seront justifiées, confirmées ou infirmées grâce à l'exploitation du texte et la convocation d'outils ou instruments d'analyse compris dans le texte. En outre, procéder au commentaire composé d'un texte, c'est aussi respecter un certain nombre de conventions. Le commentaire composé commence par une introduction dont l'objectif est de situer le texte, de le présenter et d'annoncer ses centres d'intérêt. Il propose par la suite un développement qui permet de répondre progressivement aux hypothèses de lecture. Il se termine par une conclusion qui permet d'établir un bilan, le cas échéant une opinion personnelle, et d'effectuer une ouverture à travers un rapprochement littéraire ou thématique avec d'autres œuvres.
LA RÉDACTION DE L'INTRODUCTION
La rédaction d'un commentaire composé doit comporter trois parties rédigées en un seul paragraphe. Les différentes étapes de l'introduction sont :
La situation du texte ou la mise en contexte. Dans cette partie, différentes approches sont possibles. La première consiste à s'inspirer de la vie littéraire de l'auteur si le texte a un quelconque lien avec cette dernière. La deuxième, quant à elle, tient compte de la tendance littéraire à laquelle appartient l'auteur du texte (mouvement littéraire). La troisième et dernière consiste à prendre en compte la thématique littéraire du texte étudié. En outre, situer le texte implique aussi que nous identifiions le genre littéraire auquel appartient le texte. De plus, tous les éléments ou renseignements offerts par le paratexte doivent être indiqués, à savoir la source (nom de l'auteur, le titre de l'œuvre d'où est extrait le texte, la ville de publication, la maison d'édition, l'année de publication, la pagination), le chapeau et, le cas échéant, le titre voire la position de l'extrait dans l'œuvre. La présentation du texte. Ici, il faut préciser le type du texte, le ton littéraire du texte, l'idée générale du texte et les mouvements du texte. L'annonce du plan. Dans cette dernière partie de l'introduction, il est recommandé de formuler clairement et précisément les différents axes de lecture ou centres d'intérêt qui constitueront les parties du développement.
LA RÉDACTION DU COMMENTAIRE COMPOSÉ
Chaque paragraphe du commentaire composé s'appesantit sur la logique d'un axe de lecture. Il comporte un sous-axe développé à l'aide des instruments d'analyse identifiables à travers les références textuelles et leurs interprétations. Cependant, développer ce sous-axe ne consiste pas à juxtaposer les instruments d'analyse les uns après les autres. Il s'agit plutôt d'énoncer un instrument d'analyse et la référence textuelle faisant foi et de l'accompagner de son interprétation. Aussi, les autres outils d'analyse qui seront évoqués obéiront à la même logique que le précédent. Par ailleurs, après avoir énuméré les outils d'analyse permettant de justifier la présence du sous-axe retenu, le paragraphe du commentaire composé se termine par une conclusion partielle.
Schématisation du développement d'un commentaire composé :
La phrase chapeau dans laquelle on énumère l'axe 1 :
Sous-axe 1 : outils d'analyse 1 + référence textuelle + interprétation + connecteur logique d'addition (C.L.A) + outil d'analyse 2 + référence textuelle + interprétation + conclusion partielle. La phrase de transition est le pont entre les deux parties (les deux axes de lecture).
La phrase chapeau dans laquelle on énumère l'axe 2 :
Sous-axe 2 : outils d'analyse 1 + référence textuelle + interprétation + connecteur logique d'addition (C.L.A) + outil d'analyse 2 + référence textuelle + interprétation + conclusion partielle.
IV - LA CONCLUSION DU COMMENTAIRE COMPOSÉ
Elle comporte deux ou trois parties regroupées en un seul paragraphe. Ces parties sont :
Le bilan des analyses faites à partir des grandes idées développées dans le corps du devoir. Il s'agit ici de faire une synthèse des éléments d'interprétation mis en évidence dans chaque partie du développement. Cependant, il ne s'agit pas de répéter et développer les grandes idées de chaque partie mais de les résumer.
L'opinion personnelle fait référence à un intérêt personnel dégagé à partir de la lecture du texte.
L'élargissement/ouverture peut se faire sous deux approches : la première approche consiste à comparer ou rapprocher le texte étudié à une autre œuvre abordant le même thème. Cela peut être du même auteur ou d'un auteur différent. La deuxième approche consiste à montrer la nouveauté, l'originalité, la distinction, la singularité du texte étudié en rapprochement avec le texte convoqué en tenant compte du thème, ceci pour mettre en relief la manière d'écrire ou le style d'écriture de l'auteur convoqué. Cette dernière approche permet de mettre en évidence les différences stylistiques utilisées par les deux auteurs dans la mesure où ils abordent le même thème.
Voici un exercice a trous présentant la rédaction.. référence textuelle= repérage. Et ça doit absolument être reporté.
EXERCICE À TROUS
Le thème de ... (thème du texte) a souvent fait l'objet de nombreuses préoccupations dans le monde littéraire (mais pas que). C’est dans ce cadre que s'inscrit l'extrait ... (titre du texte) qui fait l'objet de notre étude, de l'écrivain... (nom de l'auteur), tiré de son œuvre ... (préciser le genre littéraire de l'œuvre) ... (maison d'édition), en ... (date de publication) à la (aux) page (s) …. Dans ce texte (type du texte) à ton ... (tonalité du texte/facultative), structure, nous verrons en premier lieu,...(axe 1) et en second lieu, ...(axe 2).
Dans son extrait (poème), l’auteur met en relief ... (axe 1) à travers ... (sous-axe 1) et ... (sous-axe 2).
S'agissant de ... (sous-axe 1), l’écrivain utilise ... (outil d'analyse 1 + référence textuelle) pour montrer ... (interprétation). Aussi (de plus), par l'usage de... (outil d'analyse 2 + référence textuelle), l'écrivain ... (interprétation). Mieux encore, ... (outil d'analyse 3 + référence textuelle) nous donne également la possibilité d’appréhender ... (sous-axe 2).
De plus, l'homme de lettres emploie ... (outil d'analyse 1 + référence textuelle) pour ... (interprétation). Il se sert aussi de ...(outil d'analyse 2 + référence textuelle) afin de ... (interprétation). Pour continuer sa (description, représentation), le ...(nationalité de l'auteur) se manque pas de faire recours à ... (outil d'analyse 3 référence textuelle) Ici, il s'agit pour l'auteur autour de ... (sous-axe 1) et ... (sous-axe 2).
Après avoir démontré ... (axe 1), voyons à présent ... (axe 2).
En second lieu, le poète (l’écrivain ou l’homme de lettres) met en exergue ... (axe 2) en s’appuyant d’une part, sur... (sous-axe 1) et d’autre part, sur... (sous-axe 2). En ce qui concerne ... (sous-axe 1), l'homme de lettres met d'abord en évidence l'aspect (le caractère) ... (interprétation) comme en témoigne l'emploi (l'usage de) ... (outil d’analyse 1 + référence textuelle). Ensuite, ... (outil d'analyse 2 + référence textuelle) dévoile que... (interprétation) Enfin, ... (outil d'analyse 3 + référence textuelle) suggère que… (interprétation) . ... (axe 2) se révèle grâce à ….
En parlant de ... (sous-axe 2), l'auteur met l'accent en premier sur… (interprétation), comme nous pouvons le voir avec la récurrence de (du/des) ... (rappel du sous-axe 2), le poète (l'auteur) souligne ... (interprétation) toujours dans le même sens de ... (rappel du sous-axe 2) . Il use de ... (outil d’analyse 2 + référence textuelle). Dès lors, on peut déduire que ...(interprétation) utilise ... (outil d’analyse 3 + référence textuelle).
Ainsi, ... (axe 2) est lié (e) à ... (sous-axe 1) et à ... (sous-axe 2).
Somme toute, ... (titre du texte) organise son sens autour de … (axe 1) et de ... (axe 2). De ces deux centres d’intérêt découlent respectivement, d’une part, … (sous-axe 1 de l'axe 1) et ... (sous-axe 2 de l'axe 1) et, d’autre part, … (sous-axe 1 de l'axe 2) et … (sous-axe 2 de l'axe 2). À travers ce texte, ...(nom de l'auteur) nous ... (opinion personnelle). Une telle optique est perceptible dans la logique de... (nom de l'auteur nous permettant de faire un rapprochement thématique), dans son œuvre ...(titre de l’œuvre), dans lequel il aborde… (bref résumé de l'œuvre en question qui peut être facultatif).
"""
# --- End Prompts ---
generate_config = types.GenerateContentConfig(
safety_settings=SAFETY_SETTINGS
)
# --- Model IDs ---
MODEL_ID_STANDARD = "gemini-2.0-flash" # Default model
MODEL_ID_DEEPTHINK = "gemini-2.5-pro-exp-03-25" # Advanced model for DeepThink
# --- End Model IDs ---
# --- Stream Generation Functions ---
def generate_table_stream(image, consignes="", model_id=MODEL_ID_STANDARD):
"""Génère le tableau d'analyse à partir de l'image en intégrant éventuellement des consignes"""
prompt = prompt_tableau
if consignes:
prompt += "\n\nConsignes supplémentaires de l'utilisateur :\n" + consignes # Make consignes clearer in prompt
logging.info(f"Generating table using model: {model_id}")
try:
response_stream = genai_client.models.generate_content_stream(
model=model_id, # Use the passed model_id
contents=[prompt, image],
config=generate_config
)
for chunk in response_stream:
if chunk.text:
yield json.dumps({"type": "tableau", "chunk": chunk.text}) + "\n"
except Exception as e:
logging.error(f"Error during table generation stream: {e}", exc_info=True)
yield json.dumps({"type": "error", "error": f"Erreur lors de la génération du tableau: {e}"}) + "\n"
def generate_dissertation_stream(tableau, model_id=MODEL_ID_STANDARD):
"""Génère la dissertation basée sur le tableau"""
prompt = f"""
{prompt_redaction}
--- Tableau d'Analyse Fourni ---
{tableau}
--- Fin du Tableau ---
Écris maintenant une rédaction structurée pour le commentaire composé basé EXCLUSIVEMENT sur le tableau d'analyse fourni ci-dessus, en suivant strictement le modèle de l'exercice à trous présenté dans la première partie de ce prompt. Assure-toi d'utiliser les "Repérage / Citation" du tableau quand le modèle le demande.
"""
logging.info(f"Generating dissertation using model: {model_id}")
try:
response_stream = genai_client.models.generate_content_stream(
model=model_id, # Use the passed model_id
contents=prompt,
config=generate_config
)
for chunk in response_stream:
if chunk.text:
yield json.dumps({"type": "dissertation", "chunk": chunk.text}) + "\n"
except Exception as e:
logging.error(f"Error during dissertation generation stream: {e}", exc_info=True)
yield json.dumps({"type": "error", "error": f"Erreur lors de la génération de la dissertation: {e}"}) + "\n"
# --- End Stream Generation Functions ---
# --- Flask Routes ---
@app.route('/')
def index():
# Assuming your main HTML file is index.html
return render_template('index.html')
# Removed /free route as it wasn't mentioned in the final HTML
# @app.route('/free')
# def free():
# return render_template('free.html')
@app.route('/analyze', methods=['POST'])
def analyze():
if 'image' not in request.files:
logging.warning("Analyze request received without image file.")
return jsonify({'error': 'No image uploaded'}), 400
image_file = request.files['image']
consignes = request.form.get("consignes", "")
# Check for the DeepThink flag
use_deepthink = request.form.get('use_deepthink', 'false').lower() == 'true'
# Select model based on the flag
if use_deepthink:
model_id = MODEL_ID_DEEPTHINK
logging.info("DeepThink requested, using advanced model.")
else:
model_id = MODEL_ID_STANDARD
logging.info("Standard analysis requested, using standard model.")
# Use a temporary file to handle the image upload
temp_file = None # Initialize to None
try:
# Create a temporary file with a specific suffix if needed, or let NamedTemporaryFile handle it
# Using 'delete=False' requires manual cleanup
with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as temp_file:
image_file.save(temp_file.name)
# Ensure image is valid before proceeding
try:
image = Image.open(temp_file.name)
image.verify() # Verify image header
# Re-open after verify
image = Image.open(temp_file.name)
except (IOError, SyntaxError) as e:
logging.error(f"Invalid image file uploaded: {e}")
return jsonify({'error': f'Invalid or corrupted image file: {e}'}), 400
# Now 'image' holds the PIL Image object
# We need to pass the image object to the stream function
# Note: google.generativeai often works directly with PIL Image objects
@stream_with_context
def generate():
temp_file_path = temp_file.name # Store path for finally block
full_tableau_content = "" # Accumulate full table content for dissertation
try:
logging.info("Starting table generation stream...")
# Phase 1: Génération du tableau, passing the selected model_id
for chunk_json in generate_table_stream(image, consignes, model_id):
try:
chunk_data = json.loads(chunk_json)
if chunk_data.get("type") == "error":
logging.error(f"Error received from table stream: {chunk_data.get('error')}")
yield chunk_json # Forward the error to the client
return # Stop generation if table fails
elif chunk_data.get("type") == "tableau":
full_tableau_content += chunk_data.get("chunk", "")
yield chunk_json # Stream chunk to client
except json.JSONDecodeError:
logging.error(f"Received invalid JSON from table stream: {chunk_json}")
# Decide how to handle: yield error, ignore, etc.
yield json.dumps({"type": "error", "error": "Invalid data received during table generation."}) + "\n"
return # Stop if data is corrupt
logging.info("Table generation stream finished.")
logging.info("Starting dissertation generation stream...")
# Phase 2: Génération de la dissertation basée sur le tableau COMPLET, passing the selected model_id
if full_tableau_content: # Only generate if table content exists
for chunk_json in generate_dissertation_stream(full_tableau_content, model_id):
try:
chunk_data = json.loads(chunk_json)
if chunk_data.get("type") == "error":
logging.error(f"Error received from dissertation stream: {chunk_data.get('error')}")
yield chunk_json # Forward the error
# Decide if you want to return here or let it finish
elif chunk_data.get("type") == "dissertation":
yield chunk_json # Stream chunk to client
except json.JSONDecodeError:
logging.error(f"Received invalid JSON from dissertation stream: {chunk_json}")
yield json.dumps({"type": "error", "error": "Invalid data received during dissertation generation."}) + "\n"
# Potentially return here too
else:
logging.warning("Tableau content is empty, skipping dissertation generation.")
yield json.dumps({"type": "error", "error": "Tableau d'analyse non généré, impossible de créer la dissertation."}) + "\n"
logging.info("Dissertation generation stream finished.")
except Exception as e:
logging.error(f"Error during streaming generation: {e}", exc_info=True)
# Yield a JSON error message for the client
yield json.dumps({"type": "error", "error": f"Une erreur interne est survenue: {e}"}) + "\n"
finally:
# Nettoyer le fichier temporaire in the finally block of generate
# Ensure the image object is closed if necessary (PIL handles this reasonably well)
# image.close() # Usually not needed with 'with open' or tempfile context
pass # temp_file is closed by with statement, path needed for unlink
# Return the streaming response
# Make sure temp_file path is accessible for cleanup *after* streaming finishes
response = Response(generate(), content_type='text/event-stream')
# Use response.call_on_close for reliable cleanup after stream finishes/closes
if temp_file and os.path.exists(temp_file.name):
cleanup_path = temp_file.name
response.call_on_close(lambda: os.unlink(cleanup_path) if os.path.exists(cleanup_path) else None)
logging.info(f"Scheduled cleanup for temp file: {cleanup_path}")
return response
except Exception as e:
# Catch errors during file handling or initial PIL processing
logging.error(f"Error processing upload before streaming: {e}", exc_info=True)
# Ensure cleanup if temp_file was created before the error
if temp_file and os.path.exists(temp_file.name):
try:
os.unlink(temp_file.name)
logging.info(f"Cleaned up temp file due to pre-stream error: {temp_file.name}")
except OSError as unlink_error:
logging.error(f"Error unlinking temp file during error handling: {unlink_error}")
return jsonify({'error': f'Error processing file: {e}'}), 500
# Note: The 'finally' block for the 'with tempfile' is implicitly handled
# but explicit cleanup using response.call_on_close is better for streaming
# --- End Flask Routes ---
if __name__ == '__main__':
# Set debug=False for production
# Use a proper WSGI server like Gunicorn or Waitress in production
app.run(debug=True) # debug=True enables auto-reloading and detailed errors