File size: 26,587 Bytes
5027e3b
e968302
 
f2fff37
 
 
5027e3b
 
f2fff37
 
 
5027e3b
 
 
f2fff37
 
5027e3b
 
 
 
e968302
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f2fff37
 
5027e3b
f2fff37
c11970e
f2fff37
c11970e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f2fff37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5027e3b
 
e968302
5027e3b
f2fff37
e968302
5027e3b
 
 
 
e968302
5027e3b
 
f2fff37
 
 
5027e3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f2fff37
5027e3b
f2fff37
 
5027e3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e968302
f2fff37
5027e3b
f2fff37
 
5027e3b
f2fff37
 
5027e3b
 
 
 
 
f2fff37
 
 
5027e3b
f2fff37
5027e3b
f2fff37
5027e3b
 
 
 
 
 
 
 
 
 
 
 
 
 
f2fff37
5027e3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f2fff37
5027e3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f2fff37
 
5027e3b
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
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