Docfile commited on
Commit
5027e3b
·
verified ·
1 Parent(s): 16d9b01

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +193 -98
app.py CHANGED
@@ -1,17 +1,24 @@
1
- from flask import Flask, render_template, request, jsonify
2
  from google import genai
3
  from google.genai import types
4
  import os
5
  from PIL import Image
6
  import tempfile
 
 
7
 
8
  app = Flask(__name__)
9
 
 
 
 
10
  # Configuration de l'API Gemini
11
  token = os.environ.get("TOKEN")
 
 
 
 
12
  genai_client = genai.Client(api_key=token)
13
-
14
-
15
 
16
  SAFETY_SETTINGS = [
17
  types.SafetySetting(
@@ -32,91 +39,47 @@ SAFETY_SETTINGS = [
32
  )
33
  ]
34
 
 
35
  prompt_tableau = """
36
-
37
  Traite selon cette méthodologie :
38
 
39
- . Voici la méthodologie révisée pour insister sur l'utilisation de noms précis et uniques pour chaque outil d'analyse, sans regroupement dans une même case.
40
-
41
  La Méthodologie de la Lecture Méthodique
42
-
43
  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.
44
-
45
  Étape 1 : Observation Initiale et Identification du Texte
46
-
47
  (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.)
48
-
49
  Examen Visuel : Mise en page, longueur, paragraphes, typographie...
50
-
51
  Identification Contextuelle : Source (auteur, œuvre, date...), situation de l'extrait.
52
-
53
  Première Qualification : Genre littéraire, forme de discours (discours/récit), objectif principal (narratif, descriptif, argumentatif, explicatif/informatif).
54
-
55
  Étape 2 : Compréhension Globale du Texte
56
-
57
  (Cette partie reste identique : S'assurer de la compréhension littérale via des questions clés.)
58
-
59
  Thème(s), Situation (Qui, quoi, où, quand ?), Énonciation (Qui parle ? Point de vue/Focalisation ?), Progression, Temps verbaux dominants.
60
-
61
  Étape 3 : Analyse Approfondie et Structurée
62
-
63
  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.
64
-
65
  Définir les Axes de Lecture :
66
-
67
  Trouver deux axes de lecture principaux (idées directrices).
68
-
69
  Diviser chaque axe en deux sous-axes qui précisent l'idée principale.
70
-
71
  Choisir et Utiliser les Outils d'Analyse Littéraire :
72
-
73
  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) :
74
-
75
  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...
76
-
77
  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...
78
-
79
  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...
80
-
81
  É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...
82
-
83
  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...
84
-
85
  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...
86
-
87
  Organiser l'Analyse dans un Tableau :
88
-
89
  Présenter les résultats pour chaque sous-axe dans un tableau à trois colonnes :
90
-
91
  Je ne veux pas qu'un outil apparaisse deux fois. Et Aussi j'aimerais uniquement des noms officiel.
92
-
93
  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.
94
-
95
  Repérage / Citation : Citer le ou les passages exacts du texte où l'outil est repéré.
96
-
97
  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.
98
-
99
  Rappel : Analyser un Fait de Langue (Processus en 3 temps pour l'Interprétation)
100
-
101
  (Cette partie reste identique : Observation/Repérage [Quoi ?], Explication technique [Comment ?], Interprétation [Pourquoi ? Quel effet ? Quel sens ?])
102
-
103
  Structure Finale de la Présentation de l'Analyse (Synthèse des consignes)
104
-
105
  L'analyse méthodique doit être présentée de manière structurée :
106
-
107
  Deux axes de lecture distincts.
108
-
109
  Chaque axe subdivisé en deux sous-axes.
110
-
111
  Chaque sous-axe analysé via trois outils d'analyse spécifiques, nommés individuellement avec leur terme technique reconnu et officiel.
112
-
113
  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.
114
-
115
- (Note : Conformément à votre demande, cette méthodologie ne détaille pas la rédaction d'une introduction ou d'une conclusion pour le commentaire composé final, mais se concentre sur le processus d'analyse lui-même, en insistant sur la précision et l'unicité des outils nommés.)
116
-
117
-
118
-
119
- Réponds en français
120
  """
121
 
122
  prompt_redaction = """
@@ -172,78 +135,210 @@ Ainsi, ... (axe 2) est lié (e) à ... (sous-axe 1) et à ... (sous-axe 2).
172
 
173
  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).
174
  """
 
 
175
  generate_config = types.GenerateContentConfig(
176
- safety_settings=SAFETY_SETTINGS
177
  )
178
- model_id="gemini-2.0-flash"
179
 
 
 
 
 
180
 
181
-
182
- def generate_table(image, consignes=""):
183
  """Génère le tableau d'analyse à partir de l'image en intégrant éventuellement des consignes"""
184
  prompt = prompt_tableau
185
  if consignes:
186
- prompt += "\n" + consignes
187
- #sresponse = model.generate_content([prompt, image])
188
- response = client.models.generate_content(
189
- model=model_id,
190
- contents=[prompt,image],
191
- config=generate_config
192
- )
193
- return response.text
 
 
 
 
 
 
 
 
 
194
 
195
- def generate_dissertation(tableau):
196
  """Génère la dissertation basée sur le tableau"""
197
  prompt = f"""
198
-
199
- {prompt_redaction}.Écris maintenant un rédaction pour ça en suivant l'exercice a trous ; {tableau}"""
200
- #response = model.generate_content(prompt)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
- response = client.models.generate_content(
203
- model=model_id,
204
- contents=promtp,
205
- config=generate_config)
206
-
207
- return response.text
208
 
 
209
  @app.route('/')
210
  def index():
 
211
  return render_template('index.html')
212
 
213
- @app.route('/free')
214
- def free():
215
- return render_template('free.html')
216
-
 
217
  @app.route('/analyze', methods=['POST'])
218
  def analyze():
219
  if 'image' not in request.files:
 
220
  return jsonify({'error': 'No image uploaded'}), 400
221
-
222
- # Récupération du champ "consignes" (optionnel)
223
- consignes = request.form.get("consignes", "")
224
  image_file = request.files['image']
225
-
226
- # Sauvegarder temporairement l'image
227
- with tempfile.NamedTemporaryFile(delete=False) as temp_file:
228
- image_file.save(temp_file.name)
229
- image = Image.open(temp_file.name)
230
-
 
 
 
 
 
 
 
 
231
  try:
232
- # Génération du tableau enrichi avec les consignes éventuelles
233
- tableau = generate_table(image, consignes)
234
-
235
- # Génération de la dissertation basée sur le tableau
236
- dissertation = generate_dissertation(tableau)
237
-
238
- return jsonify({
239
- 'tableau': tableau,
240
- 'dissertation': dissertation
241
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  except Exception as e:
243
- return jsonify({'error': "Erreur."}), 500
244
- finally:
245
- # Nettoyer le fichier temporaire
246
- os.unlink(temp_file.name)
 
 
 
 
 
 
 
 
 
 
 
 
247
 
248
  if __name__ == '__main__':
249
- app.run(debug=True)
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, Response, stream_with_context
2
  from google import genai
3
  from google.genai import types
4
  import os
5
  from PIL import Image
6
  import tempfile
7
+ import json
8
+ import logging # Optional: for better logging
9
 
10
  app = Flask(__name__)
11
 
12
+ # Configure logging (optional but recommended)
13
+ logging.basicConfig(level=logging.INFO)
14
+
15
  # Configuration de l'API Gemini
16
  token = os.environ.get("TOKEN")
17
+ if not token:
18
+ logging.error("API Token (TOKEN environment variable) not found!")
19
+ # Handle the error appropriately - maybe exit or raise an exception
20
+ # For now, we'll let it potentially fail later if the client is used without a key
21
  genai_client = genai.Client(api_key=token)
 
 
22
 
23
  SAFETY_SETTINGS = [
24
  types.SafetySetting(
 
39
  )
40
  ]
41
 
42
+ # --- Prompts (Keep them as they are) ---
43
  prompt_tableau = """
 
44
  Traite selon cette méthodologie :
45
 
 
 
46
  La Méthodologie de la Lecture Méthodique
 
47
  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.
 
48
  Étape 1 : Observation Initiale et Identification du Texte
 
49
  (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.)
 
50
  Examen Visuel : Mise en page, longueur, paragraphes, typographie...
 
51
  Identification Contextuelle : Source (auteur, œuvre, date...), situation de l'extrait.
 
52
  Première Qualification : Genre littéraire, forme de discours (discours/récit), objectif principal (narratif, descriptif, argumentatif, explicatif/informatif).
 
53
  Étape 2 : Compréhension Globale du Texte
 
54
  (Cette partie reste identique : S'assurer de la compréhension littérale via des questions clés.)
 
55
  Thème(s), Situation (Qui, quoi, où, quand ?), Énonciation (Qui parle ? Point de vue/Focalisation ?), Progression, Temps verbaux dominants.
 
56
  Étape 3 : Analyse Approfondie et Structurée
 
57
  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.
 
58
  Définir les Axes de Lecture :
 
59
  Trouver deux axes de lecture principaux (idées directrices).
 
60
  Diviser chaque axe en deux sous-axes qui précisent l'idée principale.
 
61
  Choisir et Utiliser les Outils d'Analyse Littéraire :
 
62
  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) :
 
63
  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...
 
64
  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...
 
65
  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...
 
66
  É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...
 
67
  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...
 
68
  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...
 
69
  Organiser l'Analyse dans un Tableau :
 
70
  Présenter les résultats pour chaque sous-axe dans un tableau à trois colonnes :
 
71
  Je ne veux pas qu'un outil apparaisse deux fois. Et Aussi j'aimerais uniquement des noms officiel.
 
72
  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.
 
73
  Repérage / Citation : Citer le ou les passages exacts du texte où l'outil est repéré.
 
74
  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.
 
75
  Rappel : Analyser un Fait de Langue (Processus en 3 temps pour l'Interprétation)
 
76
  (Cette partie reste identique : Observation/Repérage [Quoi ?], Explication technique [Comment ?], Interprétation [Pourquoi ? Quel effet ? Quel sens ?])
 
77
  Structure Finale de la Présentation de l'Analyse (Synthèse des consignes)
 
78
  L'analyse méthodique doit être présentée de manière structurée :
 
79
  Deux axes de lecture distincts.
 
80
  Chaque axe subdivisé en deux sous-axes.
 
81
  Chaque sous-axe analysé via trois outils d'analyse spécifiques, nommés individuellement avec leur terme technique reconnu et officiel.
 
82
  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.
 
 
 
 
 
 
83
  """
84
 
85
  prompt_redaction = """
 
135
 
136
  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).
137
  """
138
+ # --- End Prompts ---
139
+
140
  generate_config = types.GenerateContentConfig(
141
+ safety_settings=SAFETY_SETTINGS
142
  )
 
143
 
144
+ # --- Model IDs ---
145
+ MODEL_ID_STANDARD = "gemini-2.0-flash" # Default model
146
+ MODEL_ID_DEEPTHINK = "gemini-2.5-pro-exp-03-25" # Advanced model for DeepThink
147
+ # --- End Model IDs ---
148
 
149
+ # --- Stream Generation Functions ---
150
+ def generate_table_stream(image, consignes="", model_id=MODEL_ID_STANDARD):
151
  """Génère le tableau d'analyse à partir de l'image en intégrant éventuellement des consignes"""
152
  prompt = prompt_tableau
153
  if consignes:
154
+ prompt += "\n\nConsignes supplémentaires de l'utilisateur :\n" + consignes # Make consignes clearer in prompt
155
+
156
+ logging.info(f"Generating table using model: {model_id}")
157
+ try:
158
+ response_stream = genai_client.models.generate_content_stream(
159
+ model=model_id, # Use the passed model_id
160
+ contents=[prompt, image],
161
+ config=generate_config
162
+ )
163
+
164
+ for chunk in response_stream:
165
+ if chunk.text:
166
+ yield json.dumps({"type": "tableau", "chunk": chunk.text}) + "\n"
167
+ except Exception as e:
168
+ logging.error(f"Error during table generation stream: {e}", exc_info=True)
169
+ yield json.dumps({"type": "error", "error": f"Erreur lors de la génération du tableau: {e}"}) + "\n"
170
+
171
 
172
+ def generate_dissertation_stream(tableau, model_id=MODEL_ID_STANDARD):
173
  """Génère la dissertation basée sur le tableau"""
174
  prompt = f"""
175
+ {prompt_redaction}
176
+
177
+ --- Tableau d'Analyse Fourni ---
178
+ {tableau}
179
+ --- Fin du Tableau ---
180
+
181
+ É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.
182
+ """
183
+
184
+ logging.info(f"Generating dissertation using model: {model_id}")
185
+ try:
186
+ response_stream = genai_client.models.generate_content_stream(
187
+ model=model_id, # Use the passed model_id
188
+ contents=prompt,
189
+ config=generate_config
190
+ )
191
+
192
+ for chunk in response_stream:
193
+ if chunk.text:
194
+ yield json.dumps({"type": "dissertation", "chunk": chunk.text}) + "\n"
195
+ except Exception as e:
196
+ logging.error(f"Error during dissertation generation stream: {e}", exc_info=True)
197
+ yield json.dumps({"type": "error", "error": f"Erreur lors de la génération de la dissertation: {e}"}) + "\n"
198
+
199
+ # --- End Stream Generation Functions ---
200
 
 
 
 
 
 
 
201
 
202
+ # --- Flask Routes ---
203
  @app.route('/')
204
  def index():
205
+ # Assuming your main HTML file is index.html
206
  return render_template('index.html')
207
 
208
+ # Removed /free route as it wasn't mentioned in the final HTML
209
+ # @app.route('/free')
210
+ # def free():
211
+ # return render_template('free.html')
212
+
213
  @app.route('/analyze', methods=['POST'])
214
  def analyze():
215
  if 'image' not in request.files:
216
+ logging.warning("Analyze request received without image file.")
217
  return jsonify({'error': 'No image uploaded'}), 400
218
+
 
 
219
  image_file = request.files['image']
220
+ consignes = request.form.get("consignes", "")
221
+ # Check for the DeepThink flag
222
+ use_deepthink = request.form.get('use_deepthink', 'false').lower() == 'true'
223
+
224
+ # Select model based on the flag
225
+ if use_deepthink:
226
+ model_id = MODEL_ID_DEEPTHINK
227
+ logging.info("DeepThink requested, using advanced model.")
228
+ else:
229
+ model_id = MODEL_ID_STANDARD
230
+ logging.info("Standard analysis requested, using standard model.")
231
+
232
+ # Use a temporary file to handle the image upload
233
+ temp_file = None # Initialize to None
234
  try:
235
+ # Create a temporary file with a specific suffix if needed, or let NamedTemporaryFile handle it
236
+ # Using 'delete=False' requires manual cleanup
237
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as temp_file:
238
+ image_file.save(temp_file.name)
239
+ # Ensure image is valid before proceeding
240
+ try:
241
+ image = Image.open(temp_file.name)
242
+ image.verify() # Verify image header
243
+ # Re-open after verify
244
+ image = Image.open(temp_file.name)
245
+ except (IOError, SyntaxError) as e:
246
+ logging.error(f"Invalid image file uploaded: {e}")
247
+ return jsonify({'error': f'Invalid or corrupted image file: {e}'}), 400
248
+
249
+ # Now 'image' holds the PIL Image object
250
+ # We need to pass the image object to the stream function
251
+ # Note: google.generativeai often works directly with PIL Image objects
252
+
253
+ @stream_with_context
254
+ def generate():
255
+ temp_file_path = temp_file.name # Store path for finally block
256
+ full_tableau_content = "" # Accumulate full table content for dissertation
257
+ try:
258
+ logging.info("Starting table generation stream...")
259
+ # Phase 1: Génération du tableau, passing the selected model_id
260
+ for chunk_json in generate_table_stream(image, consignes, model_id):
261
+ try:
262
+ chunk_data = json.loads(chunk_json)
263
+ if chunk_data.get("type") == "error":
264
+ logging.error(f"Error received from table stream: {chunk_data.get('error')}")
265
+ yield chunk_json # Forward the error to the client
266
+ return # Stop generation if table fails
267
+ elif chunk_data.get("type") == "tableau":
268
+ full_tableau_content += chunk_data.get("chunk", "")
269
+ yield chunk_json # Stream chunk to client
270
+ except json.JSONDecodeError:
271
+ logging.error(f"Received invalid JSON from table stream: {chunk_json}")
272
+ # Decide how to handle: yield error, ignore, etc.
273
+ yield json.dumps({"type": "error", "error": "Invalid data received during table generation."}) + "\n"
274
+ return # Stop if data is corrupt
275
+
276
+ logging.info("Table generation stream finished.")
277
+ logging.info("Starting dissertation generation stream...")
278
+
279
+ # Phase 2: Génération de la dissertation basée sur le tableau COMPLET, passing the selected model_id
280
+ if full_tableau_content: # Only generate if table content exists
281
+ for chunk_json in generate_dissertation_stream(full_tableau_content, model_id):
282
+ try:
283
+ chunk_data = json.loads(chunk_json)
284
+ if chunk_data.get("type") == "error":
285
+ logging.error(f"Error received from dissertation stream: {chunk_data.get('error')}")
286
+ yield chunk_json # Forward the error
287
+ # Decide if you want to return here or let it finish
288
+ elif chunk_data.get("type") == "dissertation":
289
+ yield chunk_json # Stream chunk to client
290
+ except json.JSONDecodeError:
291
+ logging.error(f"Received invalid JSON from dissertation stream: {chunk_json}")
292
+ yield json.dumps({"type": "error", "error": "Invalid data received during dissertation generation."}) + "\n"
293
+ # Potentially return here too
294
+ else:
295
+ logging.warning("Tableau content is empty, skipping dissertation generation.")
296
+ yield json.dumps({"type": "error", "error": "Tableau d'analyse non généré, impossible de créer la dissertation."}) + "\n"
297
+
298
+ logging.info("Dissertation generation stream finished.")
299
+
300
+ except Exception as e:
301
+ logging.error(f"Error during streaming generation: {e}", exc_info=True)
302
+ # Yield a JSON error message for the client
303
+ yield json.dumps({"type": "error", "error": f"Une erreur interne est survenue: {e}"}) + "\n"
304
+ finally:
305
+ # Nettoyer le fichier temporaire in the finally block of generate
306
+ # Ensure the image object is closed if necessary (PIL handles this reasonably well)
307
+ # image.close() # Usually not needed with 'with open' or tempfile context
308
+ pass # temp_file is closed by with statement, path needed for unlink
309
+
310
+ # Return the streaming response
311
+ # Make sure temp_file path is accessible for cleanup *after* streaming finishes
312
+ response = Response(generate(), content_type='text/event-stream')
313
+
314
+ # Use response.call_on_close for reliable cleanup after stream finishes/closes
315
+ if temp_file and os.path.exists(temp_file.name):
316
+ cleanup_path = temp_file.name
317
+ response.call_on_close(lambda: os.unlink(cleanup_path) if os.path.exists(cleanup_path) else None)
318
+ logging.info(f"Scheduled cleanup for temp file: {cleanup_path}")
319
+
320
+
321
+ return response
322
+
323
  except Exception as e:
324
+ # Catch errors during file handling or initial PIL processing
325
+ logging.error(f"Error processing upload before streaming: {e}", exc_info=True)
326
+ # Ensure cleanup if temp_file was created before the error
327
+ if temp_file and os.path.exists(temp_file.name):
328
+ try:
329
+ os.unlink(temp_file.name)
330
+ logging.info(f"Cleaned up temp file due to pre-stream error: {temp_file.name}")
331
+ except OSError as unlink_error:
332
+ logging.error(f"Error unlinking temp file during error handling: {unlink_error}")
333
+ return jsonify({'error': f'Error processing file: {e}'}), 500
334
+ # Note: The 'finally' block for the 'with tempfile' is implicitly handled
335
+ # but explicit cleanup using response.call_on_close is better for streaming
336
+
337
+
338
+ # --- End Flask Routes ---
339
+
340
 
341
  if __name__ == '__main__':
342
+ # Set debug=False for production
343
+ # Use a proper WSGI server like Gunicorn or Waitress in production
344
+ app.run(debug=True) # debug=True enables auto-reloading and detailed errors