Docfile commited on
Commit
3773549
·
verified ·
1 Parent(s): dfacd64

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +184 -61
app.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import os
2
  import json
3
  import mimetypes
@@ -7,28 +9,49 @@ import google.generativeai as genai
7
  import requests
8
  from werkzeug.utils import secure_filename
9
  import markdown # Pour convertir la réponse en HTML
 
10
 
11
  # --- Configuration Initiale ---
12
  load_dotenv()
13
 
14
  app = Flask(__name__)
15
 
16
- # Clé secrète FORTEMENT recommandée pour les sessions
17
- app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'dev-secret-key-replace-in-prod')
 
 
18
 
19
- # Configuration pour les uploads
20
  UPLOAD_FOLDER = 'temp'
21
- ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg'} # Extensions autorisées
22
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
23
  app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024 # Limite de taille (ex: 25MB)
24
 
25
- # Créer le dossier temp s'il n'existe pas
26
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
27
  print(f"Dossier d'upload configuré : {os.path.abspath(UPLOAD_FOLDER)}")
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  # --- Configuration de l'API Gemini ---
30
  MODEL_FLASH = 'gemini-2.0-flash' # Default model
31
  MODEL_PRO = 'gemini-2.5-pro-exp-03-25' # Advanced model
 
32
  SYSTEM_INSTRUCTION = "Tu es un assistant intelligent et amical nommé Mariam. Tu assistes les utilisateurs au mieux de tes capacités. Tu as été créé par Aenir."
33
  SAFETY_SETTINGS = [
34
  {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
@@ -128,8 +151,10 @@ def format_search_results(data):
128
  if title and description:
129
  results.append(f"**{title} ({type})**\n{description}\n")
130
  if kg.get('attributes'):
 
131
  for attr, value in kg['attributes'].items():
132
  results.append(f"- {attr}: {value}")
 
133
 
134
 
135
  # Résultats organiques
@@ -156,16 +181,24 @@ def format_search_results(data):
156
  def prepare_gemini_history(chat_history):
157
  """Convertit l'historique stocké en session au format attendu par Gemini API."""
158
  gemini_history = []
159
- for message in chat_history:
160
- role = 'user' if message['role'] == 'user' else 'model'
 
161
  # Utiliser le 'raw_text' stocké pour Gemini
162
- text_part = message.get('raw_text', '') # Fallback au cas
163
- parts = [text_part]
164
- # NOTE: La gestion des fichiers des tours PRÉCÉDENTS n'est pas gérée ici.
165
- # L'API generate_content se concentre généralement sur le fichier du tour ACTUEL.
166
- # Si une référence de fichier passée était nécessaire, il faudrait la stocker
167
- # et la ré-attacher ici (potentiellement plus complexe).
168
- gemini_history.append({'role': role, 'parts': parts})
 
 
 
 
 
 
 
169
  return gemini_history
170
 
171
  # --- Routes Flask ---
@@ -173,20 +206,31 @@ def prepare_gemini_history(chat_history):
173
  @app.route('/')
174
  def root():
175
  """Sert la page HTML principale."""
 
176
  return render_template('index.html')
177
 
178
  @app.route('/api/history', methods=['GET'])
179
  def get_history():
180
  """Fournit l'historique de chat stocké en session au format JSON."""
 
181
  if 'chat_history' not in session:
182
  session['chat_history'] = []
183
 
184
  # Préparer l'historique pour l'affichage (contient déjà le HTML pour l'assistant)
185
- display_history = [
186
- {'role': msg.get('role', 'unknown'), 'text': msg.get('text', '')}
187
- for msg in session.get('chat_history', [])
188
- ]
189
- print(f"API: Récupération de l'historique ({len(display_history)} messages)")
 
 
 
 
 
 
 
 
 
190
  return jsonify({'success': True, 'history': display_history})
191
 
192
  @app.route('/api/chat', methods=['POST'])
@@ -214,7 +258,7 @@ def chat_api():
214
  print(f"Raisonnement avancé demandé: {use_advanced}")
215
  print(f"Fichier reçu: {file.filename if file else 'Aucun'}")
216
 
217
- # Initialiser l'historique de session si nécessaire
218
  if 'chat_history' not in session:
219
  session['chat_history'] = []
220
 
@@ -273,18 +317,32 @@ def chat_api():
273
  'raw_text': raw_user_text, # Pour l'envoi à Gemini via prepare_gemini_history
274
  # On ne stocke PAS l'objet 'uploaded_gemini_file' dans la session
275
  }
 
 
 
 
276
  session['chat_history'].append(user_history_entry)
277
- session.modified = True # Indiquer que la session a été modifiée
 
278
 
279
  # --- Préparation des 'parts' pour l'appel Gemini ACTUEL ---
280
  current_gemini_parts = []
281
  if uploaded_gemini_file:
282
- current_gemini_parts.append(uploaded_gemini_file) # L'objet fichier uploadé
 
 
 
 
 
 
 
 
 
283
 
284
- final_prompt_for_gemini = raw_user_text # Commencer avec le texte brut
285
 
286
  # --- Recherche Web (si activée et si un prompt textuel existe) ---
287
- if use_web_search and raw_user_text:
288
  print("Activation de la recherche web...")
289
  search_data = perform_web_search(raw_user_text)
290
  if search_data:
@@ -301,20 +359,38 @@ J'ai effectué une recherche web et voici les informations pertinentes trouvées
301
  En te basant sur ces informations ET sur ta connaissance générale, fournis une réponse complète et bien structurée à la question originale de l'utilisateur."""
302
  print("Prompt enrichi avec les résultats de recherche web.")
303
  else:
304
- print("Aucun résultat de recherche web pertinent trouvé ou erreur, utilisation du prompt original.")
305
- # final_prompt_for_gemini reste raw_user_text
 
 
 
 
 
 
 
 
 
 
306
 
307
- # Ajouter le texte (potentiellement enrichi) aux parts pour Gemini
308
- current_gemini_parts.append(final_prompt_for_gemini)
309
 
310
  # --- Appel à l'API Gemini ---
311
  try:
312
  # Préparer l'historique des messages PRÉCÉDENTS
313
- gemini_history = prepare_gemini_history(session['chat_history'][:-1]) # Exclut le message actuel
 
 
314
  print(f"Préparation de l'appel Gemini avec {len(gemini_history)} messages d'historique.")
 
315
  # Construire le contenu complet pour l'appel
316
  contents_for_gemini = gemini_history + [{'role': 'user', 'parts': current_gemini_parts}]
317
 
 
 
 
 
 
 
 
318
  # Choisir le nom du modèle à utiliser
319
  selected_model_name = MODEL_PRO if use_advanced else MODEL_FLASH
320
  print(f"Utilisation du modèle Gemini: {selected_model_name}")
@@ -335,24 +411,49 @@ En te basant sur ces informations ET sur ta connaissance générale, fournis une
335
 
336
  # Extraire le texte de la réponse (gestion d'erreur potentielle ici si la réponse est bloquée etc.)
337
  # Gérer le cas où la réponse est bloquée par les safety settings
 
 
338
  try:
339
- response_text_raw = response.text
340
- except ValueError:
341
- # Si response.text échoue, la réponse a probablement été bloquée.
342
- print("ERREUR: La réponse de Gemini a été bloquée (probablement par les safety settings).")
343
- print(f"Détails du blocage : {response.prompt_feedback}")
344
- # Vous pouvez décider quoi renvoyer au client ici.
345
- # Soit une erreur spécifique, soit un message générique.
346
- response_text_raw = "Désolé, ma réponse a été bloquée car elle pourrait enfreindre les règles de sécurité."
347
- # Convertir ce message d'erreur en HTML aussi pour la cohérence
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  response_html = markdown.markdown(response_text_raw)
349
 
350
- else:
351
- # Si response.text réussit, continuer normalement
352
- print(f"Réponse reçue de Gemini (brute, début): '{response_text_raw[:100]}...'")
353
- # Convertir la réponse Markdown en HTML pour l'affichage
354
- response_html = markdown.markdown(response_text_raw, extensions=['fenced_code', 'tables', 'nl2br'])
355
- print("Réponse convertie en HTML.")
356
 
357
 
358
  # Ajouter la réponse de l'assistant à l'historique de session
@@ -361,8 +462,12 @@ En te basant sur ces informations ET sur ta connaissance générale, fournis une
361
  'text': response_html, # HTML pour l'affichage via get_history
362
  'raw_text': response_text_raw # Texte brut pour les futurs appels Gemini
363
  }
 
 
 
 
364
  session['chat_history'].append(assistant_history_entry)
365
- session.modified = True
366
 
367
  # Renvoyer la réponse HTML au frontend
368
  print("Envoi de la réponse HTML au client.")
@@ -373,37 +478,54 @@ En te basant sur ces informations ET sur ta connaissance générale, fournis une
373
  # En cas d'erreur, retirer le dernier message utilisateur de l'historique
374
  # pour éviter les boucles d'erreur si le message lui-même pose problème.
375
  # Vérifier si l'historique n'est pas vide avant de pop
376
- if session.get('chat_history'):
377
- session['chat_history'].pop()
378
- session.modified = True
379
- print("Le dernier message utilisateur a été retiré de l'historique suite à l'erreur.")
 
 
 
 
 
 
 
 
380
  else:
381
- print("L'historique était déjà vide lors de l'erreur.")
382
 
383
  # Renvoyer une erreur générique mais informative
384
  return jsonify({'success': False, 'error': f"Une erreur interne est survenue lors de la génération de la réponse. Détails: {e}"}), 500
385
 
386
  finally:
387
- # --- Nettoyage du fichier temporaire ---
 
 
 
 
 
 
 
 
 
 
 
388
  if filepath_to_delete and os.path.exists(filepath_to_delete):
389
  try:
390
  os.remove(filepath_to_delete)
391
- print(f"Fichier temporaire '{filepath_to_delete}' supprimé avec succès.")
392
- except OSError as e:
393
- print(f"ERREUR lors de la suppression du fichier temporaire '{filepath_to_delete}': {e}")
394
 
395
 
396
  @app.route('/clear', methods=['POST'])
397
  def clear_chat():
398
  """Efface l'historique de chat dans la session."""
399
- session.pop('chat_history', None)
400
- # session.pop('web_search', None) # On ne stocke pas ça en session
401
- print("API: Historique de chat effacé via /clear.")
402
 
403
  # Adapter la réponse selon si c'est une requête AJAX (fetch) ou une soumission classique
404
- # Vérification si la requête vient probablement de fetch (simple)
405
  is_ajax = 'XMLHttpRequest' == request.headers.get('X-Requested-With') or \
406
- 'application/json' in request.headers.get('Accept', '') # Plus robuste
407
 
408
  if is_ajax:
409
  return jsonify({'success': True, 'message': 'Historique effacé.'})
@@ -418,7 +540,8 @@ if __name__ == '__main__':
418
  print("Démarrage du serveur Flask...")
419
  # Utiliser host='0.0.0.0' pour rendre accessible sur le réseau local
420
  # debug=True est pratique pour le développement, mais à désactiver en production !
421
- # Changer le port si nécessaire (ex: 5000, 5001, 8080)
422
  # Utiliser un port différent si le port 5000 est déjà pris
423
  port = int(os.environ.get('PORT', 5001))
424
- app.run(debug=True, host='0.0.0.0', port=port)
 
 
 
1
+ # --- START OF FILE app.py ---
2
+
3
  import os
4
  import json
5
  import mimetypes
 
9
  import requests
10
  from werkzeug.utils import secure_filename
11
  import markdown # Pour convertir la réponse en HTML
12
+ from flask_session import Session # <-- Importer Session
13
 
14
  # --- Configuration Initiale ---
15
  load_dotenv()
16
 
17
  app = Flask(__name__)
18
 
19
+ # --- Configuration Flask Standard ---
20
+ # Clé secrète FORTEMENT recommandée (vous l'avez déjà)
21
+ # Gardez-la secrète en production !
22
+ app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'une-super-cle-secrete-a-changer')
23
 
24
+ # Configuration pour les uploads (vous l'avez déjà)
25
  UPLOAD_FOLDER = 'temp'
26
+ ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg'}
27
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
28
  app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024 # Limite de taille (ex: 25MB)
29
 
30
+ # Créer le dossier temp s'il n'existe pas (vous l'avez déjà)
31
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
32
  print(f"Dossier d'upload configuré : {os.path.abspath(UPLOAD_FOLDER)}")
33
 
34
+ # --- Configuration pour Flask-Session (Backend Filesystem) ---
35
+ app.config['SESSION_TYPE'] = 'filesystem' # Indique d'utiliser le stockage par fichiers
36
+ app.config['SESSION_PERMANENT'] = False # La session expire quand le navigateur est fermé
37
+ app.config['SESSION_USE_SIGNER'] = True # Signe l'ID de session dans le cookie pour sécurité
38
+ app.config['SESSION_FILE_DIR'] = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'flask_session') # Chemin où stocker les fichiers de session
39
+ # os.path.join(...) crée un chemin vers un dossier nommé 'flask_session'
40
+ # à côté de votre fichier app.py
41
+
42
+ # Crée le dossier pour les sessions filesystem s'il n'existe pas
43
+ os.makedirs(app.config['SESSION_FILE_DIR'], exist_ok=True)
44
+ print(f"Dossier pour les sessions serveur configuré : {app.config['SESSION_FILE_DIR']}")
45
+
46
+
47
+ # --- Initialisation de Flask-Session ---
48
+ # Doit être fait APRÈS avoir défini la configuration de session
49
+ server_session = Session(app) # <-- Lie l'extension à votre application
50
+
51
  # --- Configuration de l'API Gemini ---
52
  MODEL_FLASH = 'gemini-2.0-flash' # Default model
53
  MODEL_PRO = 'gemini-2.5-pro-exp-03-25' # Advanced model
54
+ # ASSUREZ-VOUS QUE SYSTEM_INSTRUCTION EST BIEN DÉFINI ICI OU CHARGÉ
55
  SYSTEM_INSTRUCTION = "Tu es un assistant intelligent et amical nommé Mariam. Tu assistes les utilisateurs au mieux de tes capacités. Tu as été créé par Aenir."
56
  SAFETY_SETTINGS = [
57
  {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
 
151
  if title and description:
152
  results.append(f"**{title} ({type})**\n{description}\n")
153
  if kg.get('attributes'):
154
+ results.append("\n**Attributs :**") # Ajout titre pour clarté
155
  for attr, value in kg['attributes'].items():
156
  results.append(f"- {attr}: {value}")
157
+ results.append("") # Ligne vide après les attributs
158
 
159
 
160
  # Résultats organiques
 
181
  def prepare_gemini_history(chat_history):
182
  """Convertit l'historique stocké en session au format attendu par Gemini API."""
183
  gemini_history = []
184
+ # Itérer sur une copie pour éviter les problèmes si on modifie pendant l'itération (peu probable ici mais bonne pratique)
185
+ for message in list(chat_history):
186
+ role = 'user' if message.get('role') == 'user' else 'model'
187
  # Utiliser le 'raw_text' stocké pour Gemini
188
+ text_part = message.get('raw_text') # Utiliser get pour éviter KeyError si 'raw_text' manque
189
+ if text_part is None:
190
+ print(f"AVERTISSEMENT: 'raw_text' manquant pour un message {role}, utilisation de 'text'. Message: {message.get('text', '')[:50]}...")
191
+ text_part = message.get('text', '') # Fallback vers 'text' si raw_text est absent
192
+
193
+ # Vérifier si text_part n'est pas vide avant d'ajouter
194
+ if text_part:
195
+ parts = [text_part]
196
+ # NOTE: La gestion des fichiers des tours PRÉCÉDENTS n'est pas gérée ici.
197
+ # L'API generate_content se concentre généralement sur le fichier du tour ACTUEL.
198
+ gemini_history.append({'role': role, 'parts': parts})
199
+ else:
200
+ print(f"AVERTISSEMENT: Message vide détecté et ignoré pour l'historique Gemini (role: {role}).")
201
+
202
  return gemini_history
203
 
204
  # --- Routes Flask ---
 
206
  @app.route('/')
207
  def root():
208
  """Sert la page HTML principale."""
209
+ # Pas besoin de toucher à l'historique ici, le frontend le demandera via /api/history
210
  return render_template('index.html')
211
 
212
  @app.route('/api/history', methods=['GET'])
213
  def get_history():
214
  """Fournit l'historique de chat stocké en session au format JSON."""
215
+ # Flask-Session gère la session (filesystem dans ce cas)
216
  if 'chat_history' not in session:
217
  session['chat_history'] = []
218
 
219
  # Préparer l'historique pour l'affichage (contient déjà le HTML pour l'assistant)
220
+ # Il est crucial que les messages stockés aient 'role' et 'text'
221
+ display_history = []
222
+ for msg in session.get('chat_history', []):
223
+ # Vérification minimale pour éviter les erreurs si la structure est corrompue
224
+ if isinstance(msg, dict) and 'role' in msg and 'text' in msg:
225
+ display_history.append({
226
+ 'role': msg.get('role'),
227
+ 'text': msg.get('text')
228
+ })
229
+ else:
230
+ print(f"AVERTISSEMENT: Format de message invalide dans l'historique de session ignoré: {msg}")
231
+
232
+
233
+ print(f"API: Récupération de l'historique ({len(display_history)} messages depuis la session serveur)")
234
  return jsonify({'success': True, 'history': display_history})
235
 
236
  @app.route('/api/chat', methods=['POST'])
 
258
  print(f"Raisonnement avancé demandé: {use_advanced}")
259
  print(f"Fichier reçu: {file.filename if file else 'Aucun'}")
260
 
261
+ # Initialiser l'historique de session si nécessaire (géré par Flask-Session)
262
  if 'chat_history' not in session:
263
  session['chat_history'] = []
264
 
 
317
  'raw_text': raw_user_text, # Pour l'envoi à Gemini via prepare_gemini_history
318
  # On ne stocke PAS l'objet 'uploaded_gemini_file' dans la session
319
  }
320
+ # Vérifier que l'historique est bien une liste avant d'ajouter
321
+ if not isinstance(session.get('chat_history'), list):
322
+ print("ERREUR: chat_history dans la session n'est pas une liste! Réinitialisation.")
323
+ session['chat_history'] = []
324
  session['chat_history'].append(user_history_entry)
325
+ # session.modified = True # Normalement plus nécessaire avec Flask-Session Filesystem/Redis etc.
326
+ # mais ne fait pas de mal de le laisser pour l'instant.
327
 
328
  # --- Préparation des 'parts' pour l'appel Gemini ACTUEL ---
329
  current_gemini_parts = []
330
  if uploaded_gemini_file:
331
+ # Important: Ne pas envoyer l'objet fichier lui-même si le prompt est vide.
332
+ # Gemini pourrait avoir du mal à l'interpréter sans contexte textuel.
333
+ if raw_user_text:
334
+ current_gemini_parts.append(uploaded_gemini_file)
335
+ else:
336
+ # Si seulement un fichier est envoyé, créer un prompt minimal
337
+ raw_user_text = f"Décris le contenu de ce fichier : {uploaded_filename}"
338
+ print(f"INFO: Fichier seul détecté, utilisation du prompt généré: '{raw_user_text}'")
339
+ current_gemini_parts.append(uploaded_gemini_file)
340
+
341
 
342
+ final_prompt_for_gemini = raw_user_text # Commencer avec le texte brut (potentiellement généré ci-dessus)
343
 
344
  # --- Recherche Web (si activée et si un prompt textuel existe) ---
345
+ if use_web_search and raw_user_text: # Vérifier qu'on a bien du texte pour chercher
346
  print("Activation de la recherche web...")
347
  search_data = perform_web_search(raw_user_text)
348
  if search_data:
 
359
  En te basant sur ces informations ET sur ta connaissance générale, fournis une réponse complète et bien structurée à la question originale de l'utilisateur."""
360
  print("Prompt enrichi avec les résultats de recherche web.")
361
  else:
362
+ print("Aucun résultat de recherche web pertinent trouvé ou erreur, utilisation du prompt original/généré.")
363
+ # final_prompt_for_gemini reste raw_user_text ou le prompt généré
364
+
365
+ # Ajouter le texte (potentiellement enrichi) aux parts pour Gemini UNIQUEMENT s'il y a du texte
366
+ if final_prompt_for_gemini:
367
+ current_gemini_parts.append(final_prompt_for_gemini)
368
+ elif not current_gemini_parts: # Si on n'a ni fichier utile ni texte
369
+ print("ERREUR: Aucune donnée (texte ou fichier valide) à envoyer à Gemini.")
370
+ # Retirer le message utilisateur vide ajouté précédemment
371
+ if session.get('chat_history'):
372
+ session['chat_history'].pop()
373
+ return jsonify({'success': False, 'error': "Impossible d'envoyer une requête vide à l'IA."}), 400
374
 
 
 
375
 
376
  # --- Appel à l'API Gemini ---
377
  try:
378
  # Préparer l'historique des messages PRÉCÉDENTS
379
+ # Utiliser une copie de la session pour éviter les modifications pendant la préparation
380
+ history_copy = list(session.get('chat_history', []))
381
+ gemini_history = prepare_gemini_history(history_copy[:-1]) # Exclut le message actuel
382
  print(f"Préparation de l'appel Gemini avec {len(gemini_history)} messages d'historique.")
383
+
384
  # Construire le contenu complet pour l'appel
385
  contents_for_gemini = gemini_history + [{'role': 'user', 'parts': current_gemini_parts}]
386
 
387
+ # DEBUG: Afficher ce qui va être envoyé (peut être très verbeux)
388
+ # print("--- Contenu envoyé à Gemini ---")
389
+ # import pprint
390
+ # pprint.pprint(contents_for_gemini)
391
+ # print("-----------------------------")
392
+
393
+
394
  # Choisir le nom du modèle à utiliser
395
  selected_model_name = MODEL_PRO if use_advanced else MODEL_FLASH
396
  print(f"Utilisation du modèle Gemini: {selected_model_name}")
 
411
 
412
  # Extraire le texte de la réponse (gestion d'erreur potentielle ici si la réponse est bloquée etc.)
413
  # Gérer le cas où la réponse est bloquée par les safety settings
414
+ response_text_raw = ""
415
+ response_html = ""
416
  try:
417
+ # Vérifier si la réponse a des 'parts' avant d'essayer d'accéder à .text
418
+ if response.parts:
419
+ response_text_raw = response.text # .text concatène les parts textuelles
420
+ else:
421
+ # Si pas de parts, vérifier le prompt_feedback pour la raison (blocage probable)
422
+ print(f"AVERTISSEMENT: Réponse de Gemini sans contenu textuel ('parts'). Feedback: {response.prompt_feedback}")
423
+ # Donner un message d'erreur plus informatif basé sur le feedback si possible
424
+ if response.prompt_feedback and response.prompt_feedback.block_reason:
425
+ reason = response.prompt_feedback.block_reason.name # Ex: 'SAFETY', 'OTHER'
426
+ response_text_raw = f"Désolé, ma réponse a été bloquée ({reason})."
427
+ # Si SAFETY, vérifier les ratings spécifiques si disponibles
428
+ if reason == 'SAFETY' and response.prompt_feedback.safety_ratings:
429
+ blocked_categories = [r.category.name for r in response.prompt_feedback.safety_ratings if r.probability.name not in ['NEGLIGIBLE', 'LOW']]
430
+ if blocked_categories:
431
+ response_text_raw += f" Catégories potentiellement concernées: {', '.join(blocked_categories)}."
432
+
433
+ else:
434
+ response_text_raw = "Désolé, je n'ai pas pu générer de réponse pour cette demande."
435
+
436
+
437
+ # Convertir en HTML même si c'est un message d'erreur
438
+ response_html = markdown.markdown(response_text_raw, extensions=['fenced_code', 'tables', 'nl2br'])
439
+
440
+ except ValueError as e:
441
+ # Gérer le cas où .text échoue même avec des parts (rare, mais possible si structure inattendue)
442
+ print(f"ERREUR: Impossible d'extraire le texte de la réponse Gemini. Erreur: {e}")
443
+ print(f"Réponse brute: {response}")
444
+ print(f"Feedback: {response.prompt_feedback}")
445
+ response_text_raw = "Désolé, une erreur interne est survenue lors de la lecture de la réponse."
446
+ response_html = markdown.markdown(response_text_raw)
447
+ except Exception as e_resp: # Capturer toute autre exception inattendue
448
+ print(f"ERREUR INATTENDUE lors du traitement de la réponse Gemini : {e_resp}")
449
+ print(f"Réponse brute: {response}")
450
+ response_text_raw = f"Désolé, une erreur inattendue est survenue ({type(e_resp).__name__})."
451
  response_html = markdown.markdown(response_text_raw)
452
 
453
+
454
+ print(f"Réponse reçue de Gemini (brute, début): '{response_text_raw[:100]}...'")
455
+ if response_html != response_text_raw: # Log seulement si Markdown a fait quelque chose
456
+ print("Réponse convertie en HTML.")
 
 
457
 
458
 
459
  # Ajouter la réponse de l'assistant à l'historique de session
 
462
  'text': response_html, # HTML pour l'affichage via get_history
463
  'raw_text': response_text_raw # Texte brut pour les futurs appels Gemini
464
  }
465
+ # Re-vérifier que l'historique est une liste avant d'ajouter
466
+ if not isinstance(session.get('chat_history'), list):
467
+ print("ERREUR: chat_history dans la session n'est pas une liste avant ajout assistant! Réinitialisation.")
468
+ session['chat_history'] = [user_history_entry] # Garder au moins le user msg
469
  session['chat_history'].append(assistant_history_entry)
470
+ # session.modified = True # Probablement pas nécessaire mais sans danger
471
 
472
  # Renvoyer la réponse HTML au frontend
473
  print("Envoi de la réponse HTML au client.")
 
478
  # En cas d'erreur, retirer le dernier message utilisateur de l'historique
479
  # pour éviter les boucles d'erreur si le message lui-même pose problème.
480
  # Vérifier si l'historique n'est pas vide avant de pop
481
+ current_history = session.get('chat_history')
482
+ if isinstance(current_history, list) and current_history:
483
+ try:
484
+ # S'assurer que le dernier élément était bien le message utilisateur
485
+ if current_history[-1].get('role') == 'user':
486
+ current_history.pop()
487
+ # session.modified = True # Si on modifie directement la liste obtenue par .get()
488
+ print("Le dernier message utilisateur a été retiré de l'historique suite à l'erreur.")
489
+ else:
490
+ print("Le dernier message n'était pas 'user', historique non modifié après erreur.")
491
+ except Exception as pop_e:
492
+ print(f"Erreur lors de la tentative de retrait du message utilisateur de l'historique: {pop_e}")
493
  else:
494
+ print("L'historique était vide ou invalide lors de l'erreur.")
495
 
496
  # Renvoyer une erreur générique mais informative
497
  return jsonify({'success': False, 'error': f"Une erreur interne est survenue lors de la génération de la réponse. Détails: {e}"}), 500
498
 
499
  finally:
500
+ # --- Nettoyage du fichier uploadé vers Google AI ---
501
+ if uploaded_gemini_file:
502
+ try:
503
+ print(f"Tentative de suppression du fichier Google AI : {uploaded_gemini_file.name}")
504
+ genai.delete_file(uploaded_gemini_file.name)
505
+ print(f"Fichier Google AI '{uploaded_gemini_file.name}' supprimé.")
506
+ except Exception as e_del_gcp:
507
+ # Ne pas bloquer l'utilisateur pour ça, juste logguer
508
+ print(f"AVERTISSEMENT: Échec de la suppression du fichier Google AI '{uploaded_gemini_file.name}': {e_del_gcp}")
509
+
510
+
511
+ # --- Nettoyage du fichier temporaire local ---
512
  if filepath_to_delete and os.path.exists(filepath_to_delete):
513
  try:
514
  os.remove(filepath_to_delete)
515
+ print(f"Fichier temporaire local '{filepath_to_delete}' supprimé avec succès.")
516
+ except OSError as e_del_local:
517
+ print(f"ERREUR lors de la suppression du fichier temporaire local '{filepath_to_delete}': {e_del_local}")
518
 
519
 
520
  @app.route('/clear', methods=['POST'])
521
  def clear_chat():
522
  """Efface l'historique de chat dans la session."""
523
+ session.clear() # Efface toutes les données de la session serveur actuelle
524
+ print("API: Historique de chat effacé via /clear (session serveur).")
 
525
 
526
  # Adapter la réponse selon si c'est une requête AJAX (fetch) ou une soumission classique
 
527
  is_ajax = 'XMLHttpRequest' == request.headers.get('X-Requested-With') or \
528
+ 'application/json' in request.headers.get('Accept', '')
529
 
530
  if is_ajax:
531
  return jsonify({'success': True, 'message': 'Historique effacé.'})
 
540
  print("Démarrage du serveur Flask...")
541
  # Utiliser host='0.0.0.0' pour rendre accessible sur le réseau local
542
  # debug=True est pratique pour le développement, mais à désactiver en production !
 
543
  # Utiliser un port différent si le port 5000 est déjà pris
544
  port = int(os.environ.get('PORT', 5001))
545
+ # Mettre debug=False pour la production
546
+ app.run(debug=False, host='0.0.0.0', port=port)
547
+ # --- END OF FILE app.py ---