Docfile commited on
Commit
b9ae53b
·
verified ·
1 Parent(s): 54001a1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +216 -283
app.py CHANGED
@@ -10,6 +10,7 @@ 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()
@@ -36,22 +37,17 @@ app.config['SESSION_TYPE'] = 'filesystem' # Indique d'utiliser le stockage par
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"},
@@ -66,8 +62,6 @@ try:
66
  print("ERREUR: Clé API GOOGLE_API_KEY manquante dans le fichier .env")
67
  else:
68
  genai.configure(api_key=gemini_api_key)
69
- # Just configure, don't create model instance yet
70
- # Check if we can list models as a basic configuration test
71
  models_list = [m.name for m in genai.list_models()]
72
  if f'models/{MODEL_FLASH}' in models_list and f'models/{MODEL_PRO}' in models_list:
73
  print(f"Configuration Gemini effectuée. Modèles requis ({MODEL_FLASH}, {MODEL_PRO}) disponibles.")
@@ -94,111 +88,74 @@ def perform_web_search(query):
94
  if not serper_api_key:
95
  print("AVERTISSEMENT: Clé API SERPER_API_KEY manquante. Recherche web désactivée.")
96
  return None
97
-
98
  search_url = "https://google.serper.dev/search"
99
- headers = {
100
- 'X-API-KEY': serper_api_key,
101
- 'Content-Type': 'application/json'
102
- }
103
- payload = json.dumps({"q": query, "gl": "fr", "hl": "fr"}) # Ajout localisation FR
104
-
105
  try:
106
- print(f"Recherche Serper pour: '{query}'")
107
  response = requests.post(search_url, headers=headers, data=payload, timeout=10)
108
- response.raise_for_status() # Lève une exception pour les erreurs HTTP (4xx, 5xx)
109
  data = response.json()
110
- print("Résultats de recherche Serper obtenus.")
111
- # print(json.dumps(data, indent=2)) # Décommenter pour voir les résultats bruts
112
  return data
113
- except requests.exceptions.Timeout:
114
- print("Erreur lors de la recherche web : Timeout")
115
- return None
116
  except requests.exceptions.RequestException as e:
117
- print(f"Erreur lors de la recherche web : {e}")
118
- # Essayer de lire le corps de la réponse d'erreur si possible
119
- try:
120
- error_details = e.response.json()
121
- print(f"Détails de l'erreur Serper: {error_details}")
122
- except:
123
- pass # Ignorer si le corps n'est pas JSON ou n'existe pas
124
  return None
125
  except json.JSONDecodeError as e:
126
- print(f"Erreur lors du décodage de la réponse JSON de Serper : {e}")
127
- print(f"Réponse reçue (texte brut) : {response.text}")
128
  return None
129
 
130
  def format_search_results(data):
131
  """Met en forme les résultats de recherche (format Markdown)."""
132
- if not data:
133
- return "Aucun résultat de recherche web trouvé pertinent."
134
-
135
  results = []
136
-
137
- # Réponse directe (Answer Box)
138
  if data.get('answerBox'):
139
  ab = data['answerBox']
140
  title = ab.get('title', '')
141
  snippet = ab.get('snippet') or ab.get('answer', '')
142
- if snippet:
143
- results.append(f"**Réponse rapide : {title}**\n{snippet}\n")
144
-
145
- # Knowledge Graph
146
  if data.get('knowledgeGraph'):
147
  kg = data['knowledgeGraph']
148
  title = kg.get('title', '')
149
  type = kg.get('type', '')
150
  description = kg.get('description', '')
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
161
  if data.get('organic'):
162
  results.append("**Pages web pertinentes :**")
163
- for i, item in enumerate(data['organic'][:3], 1): # Top 3
164
  title = item.get('title', 'Sans titre')
165
  link = item.get('link', '#')
166
  snippet = item.get('snippet', 'Pas de description.')
167
  results.append(f"{i}. **[{title}]({link})**\n {snippet}\n")
168
-
169
- # People Also Ask
170
  if data.get('peopleAlsoAsk'):
171
  results.append("**Questions liées :**")
172
- for i, item in enumerate(data['peopleAlsoAsk'][:2], 1): # Top 2
173
- results.append(f"- {item.get('question', '')}")
174
-
175
-
176
- if not results:
177
- return "Aucun résultat structuré trouvé dans la recherche web."
178
-
179
  return "\n".join(results)
180
 
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,342 +163,318 @@ def prepare_gemini_history(chat_history):
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'])
237
  def chat_api():
238
  """Gère les nouvelles requêtes de chat via AJAX."""
 
 
 
239
  if not GEMINI_CONFIGURED:
240
- print("API ERREUR: Tentative d'appel à /api/chat sans configuration Gemini valide.")
241
- return jsonify({'success': False, 'error': "Le service IA n'est pas configuré correctement."}), 503 # Service Unavailable
242
 
243
  # Récupération des données du formulaire
244
  prompt = request.form.get('prompt', '').strip()
245
- use_web_search_str = request.form.get('web_search', 'false') # 'true' ou 'false'
246
- use_web_search = use_web_search_str.lower() == 'true'
247
  file = request.files.get('file')
248
- use_advanced_str = request.form.get('advanced_reasoning', 'false') # Get the new flag
249
- use_advanced = use_advanced_str.lower() == 'true'
 
 
 
250
 
251
- # Validation simple
252
  if not prompt and not file:
 
253
  return jsonify({'success': False, 'error': 'Veuillez fournir un message ou un fichier.'}), 400
254
 
255
- print(f"\n--- Nouvelle requête /api/chat ---")
256
- print(f"Prompt reçu: '{prompt[:50]}...'")
257
- print(f"Recherche Web activée: {use_web_search}")
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
-
265
- uploaded_gemini_file = None # L'objet fichier retourné par genai.upload_file
266
- uploaded_filename = None # Juste le nom du fichier pour référence/affichage
267
- filepath_to_delete = None # Chemin du fichier local à supprimer après traitement
268
-
269
- # --- Gestion de l'upload de fichier ---
 
 
 
 
 
 
270
  if file and file.filename != '':
 
271
  if allowed_file(file.filename):
272
  try:
273
  filename = secure_filename(file.filename)
274
  filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
275
  file.save(filepath)
276
- filepath_to_delete = filepath # Marquer pour suppression
277
  uploaded_filename = filename
278
- print(f"Fichier '{filename}' sauvegardé temporairement dans '{filepath}'")
279
-
280
- # Détecter le MimeType pour Gemini
281
- mime_type = mimetypes.guess_type(filepath)[0]
282
- if not mime_type:
283
- mime_type = 'application/octet-stream' # Fallback
284
- print(f"AVERTISSEMENT: Impossible de deviner le MimeType pour '{filename}', utilisation de '{mime_type}'.")
285
-
286
- # Uploader vers Google AI (peut prendre du temps)
287
- print(f"Upload du fichier vers Google AI (MimeType: {mime_type})...")
288
- # Note: L'API upload_file est générique et ne dépend pas du modèle Flash/Pro
289
  uploaded_gemini_file = genai.upload_file(path=filepath, mime_type=mime_type)
290
- print(f"Fichier '{uploaded_gemini_file.name}' uploadé avec succès vers Google AI.")
291
-
292
  except Exception as e:
293
- print(f"ERREUR Critique lors du traitement/upload du fichier '{filename}': {e}")
294
- # Supprimer le fichier local même en cas d'erreur d'upload Gemini
295
  if filepath_to_delete and os.path.exists(filepath_to_delete):
296
- try:
297
- os.remove(filepath_to_delete)
298
- print(f"Fichier temporaire '{filepath_to_delete}' supprimé après erreur.")
299
- except OSError as del_e:
300
- print(f"Erreur lors de la suppression du fichier temporaire après erreur: {del_e}")
301
- # Renvoyer une erreur claire au client
302
- return jsonify({'success': False, 'error': f"Erreur lors du traitement du fichier: {e}"}), 500
303
  else:
304
- print(f"ERREUR: Type de fichier non autorisé: {file.filename}")
305
- return jsonify({'success': False, 'error': f"Type de fichier non autorisé. Extensions permises: {', '.join(ALLOWED_EXTENSIONS)}"}), 400
306
 
307
  # --- Préparation du message utilisateur pour l'historique et Gemini ---
308
- # Texte brut pour Gemini (et pour l'historique interne)
309
  raw_user_text = prompt
310
- # Texte pour l'affichage dans l'interface (peut inclure le nom de fichier)
311
  display_user_text = f"[{uploaded_filename}] {prompt}" if uploaded_filename and prompt else (prompt or f"[{uploaded_filename}]")
312
-
313
- # Ajout à l'historique de session
314
  user_history_entry = {
315
  'role': 'user',
316
- 'text': display_user_text, # Pour get_history et potentiellement debug
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:
349
  formatted_results = format_search_results(search_data)
350
- # Construire un prompt enrichi pour Gemini
351
- final_prompt_for_gemini = f"""Voici la question originale de l'utilisateur:
352
- "{raw_user_text}"
353
-
354
- J'ai effectué une recherche web et voici les informations pertinentes trouvées:
355
- --- DEBUT RESULTATS WEB ---
356
- {formatted_results}
357
- --- FIN RESULTATS WEB ---
358
-
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}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
 
398
- # Créer l'instance du modèle spécifique pour cette requête
399
- # Réutiliser les paramètres globaux (safety, system instruction)
400
  active_model = genai.GenerativeModel(
401
  model_name=selected_model_name,
402
- safety_settings=SAFETY_SETTINGS, # defined globally
403
- system_instruction=SYSTEM_INSTRUCTION # defined globally
404
  )
405
-
406
- # Appel API
407
- print(f"Envoi de la requête à {selected_model_name}...")
408
- # Utilisation de generate_content en mode non-streamé
409
  response = active_model.generate_content(contents_for_gemini)
410
- # print(response) # Décommenter pour voir la réponse brute de l'API
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 .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
460
  assistant_history_entry = {
461
  'role': 'assistant',
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.")
 
 
 
 
 
 
 
474
  return jsonify({'success': True, 'message': response_html})
475
 
476
  except Exception as e:
477
- print(f"ERREUR Critique lors de l'appel à Gemini ou du traitement de la réponse : {e}")
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é.'})
532
  else:
533
- # Comportement pour une soumission de formulaire classique (si jamais utilisé)
534
  flash("Conversation effacée.", "info")
535
- return redirect(url_for('root')) # Redirige vers la racine
536
 
537
 
538
  # --- Démarrage de l'application ---
539
  if __name__ == '__main__':
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 ---
 
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
+ import pprint # Pour un affichage plus lisible des structures complexes (optionnel)
14
 
15
  # --- Configuration Initiale ---
16
  load_dotenv()
 
37
  app.config['SESSION_PERMANENT'] = False # La session expire quand le navigateur est fermé
38
  app.config['SESSION_USE_SIGNER'] = True # Signe l'ID de session dans le cookie pour sécurité
39
  app.config['SESSION_FILE_DIR'] = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'flask_session') # Chemin où stocker les fichiers de session
 
 
40
 
41
  # Crée le dossier pour les sessions filesystem s'il n'existe pas
42
  os.makedirs(app.config['SESSION_FILE_DIR'], exist_ok=True)
43
  print(f"Dossier pour les sessions serveur configuré : {app.config['SESSION_FILE_DIR']}")
44
 
 
45
  # --- Initialisation de Flask-Session ---
46
+ server_session = Session(app)
 
47
 
48
  # --- Configuration de l'API Gemini ---
49
+ MODEL_FLASH = 'gemini-2.0-flash'
50
+ MODEL_PRO = 'gemini-2.5-pro-exp-03-25'
 
51
  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."
52
  SAFETY_SETTINGS = [
53
  {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
 
62
  print("ERREUR: Clé API GOOGLE_API_KEY manquante dans le fichier .env")
63
  else:
64
  genai.configure(api_key=gemini_api_key)
 
 
65
  models_list = [m.name for m in genai.list_models()]
66
  if f'models/{MODEL_FLASH}' in models_list and f'models/{MODEL_PRO}' in models_list:
67
  print(f"Configuration Gemini effectuée. Modèles requis ({MODEL_FLASH}, {MODEL_PRO}) disponibles.")
 
88
  if not serper_api_key:
89
  print("AVERTISSEMENT: Clé API SERPER_API_KEY manquante. Recherche web désactivée.")
90
  return None
 
91
  search_url = "https://google.serper.dev/search"
92
+ headers = {'X-API-KEY': serper_api_key, 'Content-Type': 'application/json'}
93
+ payload = json.dumps({"q": query, "gl": "fr", "hl": "fr"})
 
 
 
 
94
  try:
95
+ print(f"--- LOG WEBSEARCH: Recherche Serper pour: '{query}'")
96
  response = requests.post(search_url, headers=headers, data=payload, timeout=10)
97
+ response.raise_for_status()
98
  data = response.json()
99
+ print("--- LOG WEBSEARCH: Résultats de recherche Serper obtenus.")
 
100
  return data
 
 
 
101
  except requests.exceptions.RequestException as e:
102
+ print(f"--- LOG WEBSEARCH: Erreur lors de la recherche web : {e}")
 
 
 
 
 
 
103
  return None
104
  except json.JSONDecodeError as e:
105
+ print(f"--- LOG WEBSEARCH: Erreur JSON Serper : {e}")
 
106
  return None
107
 
108
  def format_search_results(data):
109
  """Met en forme les résultats de recherche (format Markdown)."""
110
+ if not data: return "Aucun résultat de recherche web trouvé pertinent."
 
 
111
  results = []
 
 
112
  if data.get('answerBox'):
113
  ab = data['answerBox']
114
  title = ab.get('title', '')
115
  snippet = ab.get('snippet') or ab.get('answer', '')
116
+ if snippet: results.append(f"**Réponse rapide : {title}**\n{snippet}\n")
 
 
 
117
  if data.get('knowledgeGraph'):
118
  kg = data['knowledgeGraph']
119
  title = kg.get('title', '')
120
  type = kg.get('type', '')
121
  description = kg.get('description', '')
122
+ if title and description: results.append(f"**{title} ({type})**\n{description}\n")
 
123
  if kg.get('attributes'):
124
+ results.append("\n**Attributs :**")
125
+ for attr, value in kg['attributes'].items(): results.append(f"- {attr}: {value}")
126
+ results.append("")
 
 
 
 
127
  if data.get('organic'):
128
  results.append("**Pages web pertinentes :**")
129
+ for i, item in enumerate(data['organic'][:3], 1):
130
  title = item.get('title', 'Sans titre')
131
  link = item.get('link', '#')
132
  snippet = item.get('snippet', 'Pas de description.')
133
  results.append(f"{i}. **[{title}]({link})**\n {snippet}\n")
 
 
134
  if data.get('peopleAlsoAsk'):
135
  results.append("**Questions liées :**")
136
+ for i, item in enumerate(data['peopleAlsoAsk'][:2], 1): results.append(f"- {item.get('question', '')}")
137
+ if not results: return "Aucun résultat structuré trouvé dans la recherche web."
138
+ print(f"--- LOG WEBSEARCH: Résultats formatés (début): '{(' '.join(results))[:100]}...'")
 
 
 
 
139
  return "\n".join(results)
140
 
141
  def prepare_gemini_history(chat_history):
142
  """Convertit l'historique stocké en session au format attendu par Gemini API."""
143
+ print(f"--- DEBUG [prepare_gemini_history]: Entrée avec {len(chat_history)} messages") # LOG 1
144
  gemini_history = []
145
+ for i, message in enumerate(list(chat_history)): # Utiliser list() pour itérer sur une copie
 
146
  role = 'user' if message.get('role') == 'user' else 'model'
147
+ text_part = message.get('raw_text')
148
+ # Log détaillé pour chaque message traité
149
+ print(f" [prepare_gemini_history] Message {i} (rôle session: {message.get('role')}, rôle gemini: {role}): raw_text présent? {'Oui' if text_part is not None else 'NON'}, contenu début: '{str(text_part)[:60]}...'") # LOG 2
150
+
151
+ if text_part: # Important: Ne pas ajouter de messages vides à l'historique Gemini
 
 
 
152
  parts = [text_part]
 
 
153
  gemini_history.append({'role': role, 'parts': parts})
154
  else:
155
+ # Log si un message est ignoré car vide
156
+ print(f" AVERTISSEMENT [prepare_gemini_history]: raw_text vide ou absent pour le message {i}, ignoré pour l'historique Gemini.") # LOG 3
157
 
158
+ print(f"--- DEBUG [prepare_gemini_history]: Sortie avec {len(gemini_history)} messages formatés pour Gemini") # LOG 4
159
  return gemini_history
160
 
161
  # --- Routes Flask ---
 
163
  @app.route('/')
164
  def root():
165
  """Sert la page HTML principale."""
166
+ print("--- LOG: Appel route '/' ---")
167
  return render_template('index.html')
168
 
169
  @app.route('/api/history', methods=['GET'])
170
  def get_history():
171
  """Fournit l'historique de chat stocké en session au format JSON."""
172
+ print("\n--- DEBUG [/api/history]: Début requête GET ---") # LOG 5
173
  if 'chat_history' not in session:
174
  session['chat_history'] = []
175
+ print(" [/api/history]: Session 'chat_history' initialisée (vide).")
176
 
 
 
177
  display_history = []
178
+ current_history = session.get('chat_history', [])
179
+ print(f" [/api/history]: Historique récupéré de la session serveur: {len(current_history)} messages.") # LOG 6
180
+
181
+ # Optionnel: Afficher la structure brute pour un debug profond
182
+ # print(" [/api/history]: Contenu brut de l'historique session:")
183
+ # pprint.pprint(current_history)
184
+
185
+ for i, msg in enumerate(current_history):
186
+ # Vérifier la structure de chaque message récupéré
187
  if isinstance(msg, dict) and 'role' in msg and 'text' in msg:
188
  display_history.append({
189
  'role': msg.get('role'),
190
+ 'text': msg.get('text') # On envoie bien le HTML ('text') au frontend
191
  })
192
  else:
193
+ # Log si un message dans la session est mal formé
194
+ print(f" AVERTISSEMENT [/api/history]: Format invalide dans l'historique session au message {i}: {msg}") # LOG 7
195
 
196
+ print(f" [/api/history]: Historique préparé pour le frontend: {len(display_history)} messages.") # LOG 8
197
  return jsonify({'success': True, 'history': display_history})
198
 
199
  @app.route('/api/chat', methods=['POST'])
200
  def chat_api():
201
  """Gère les nouvelles requêtes de chat via AJAX."""
202
+ print(f"\n---===================================---")
203
+ print(f"--- DEBUG [/api/chat]: Nouvelle requête POST ---")
204
+
205
  if not GEMINI_CONFIGURED:
206
+ print("--- ERREUR [/api/chat]: Tentative d'appel sans configuration Gemini valide.")
207
+ return jsonify({'success': False, 'error': "Le service IA n'est pas configuré correctement."}), 503
208
 
209
  # Récupération des données du formulaire
210
  prompt = request.form.get('prompt', '').strip()
211
+ use_web_search = request.form.get('web_search', 'false').lower() == 'true'
 
212
  file = request.files.get('file')
213
+ use_advanced = request.form.get('advanced_reasoning', 'false').lower() == 'true'
214
+
215
+ print(f" [/api/chat]: Prompt reçu: '{prompt[:50]}...'")
216
+ print(f" [/api/chat]: Recherche Web: {use_web_search}, Raisonnement Avancé: {use_advanced}")
217
+ print(f" [/api/chat]: Fichier: {file.filename if file else 'Aucun'}")
218
 
219
+ # Validation
220
  if not prompt and not file:
221
+ print("--- ERREUR [/api/chat]: Prompt et fichier vides.")
222
  return jsonify({'success': False, 'error': 'Veuillez fournir un message ou un fichier.'}), 400
223
 
224
+ # --- Log de l'état de l'historique AVANT toute modification ---
 
 
 
 
 
 
225
  if 'chat_history' not in session:
226
  session['chat_history'] = []
227
+ history_before_user_add = list(session.get('chat_history', [])) # Copie pour le log
228
+ print(f"--- DEBUG [/api/chat]: Historique en session AVANT ajout user message: {len(history_before_user_add)} messages") # LOG 9
229
+ # Optionnel: Afficher les derniers messages pour voir le contexte précédent
230
+ # if history_before_user_add:
231
+ # print(" [/api/chat]: Dernier(s) message(s) avant ajout:")
232
+ # pprint.pprint(history_before_user_add[-2:]) # Afficher les 2 derniers
233
+
234
+ uploaded_gemini_file = None
235
+ uploaded_filename = None
236
+ filepath_to_delete = None
237
+
238
+ # --- Gestion de l'upload de fichier (avec logs) ---
239
  if file and file.filename != '':
240
+ print(f"--- LOG [/api/chat]: Traitement du fichier '{file.filename}'")
241
  if allowed_file(file.filename):
242
  try:
243
  filename = secure_filename(file.filename)
244
  filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
245
  file.save(filepath)
246
+ filepath_to_delete = filepath
247
  uploaded_filename = filename
248
+ print(f" [/api/chat]: Fichier '{filename}' sauvegardé dans '{filepath}'")
249
+ mime_type = mimetypes.guess_type(filepath)[0] or 'application/octet-stream'
250
+ print(f" [/api/chat]: Upload Google AI (Mime: {mime_type})...")
 
 
 
 
 
 
 
 
251
  uploaded_gemini_file = genai.upload_file(path=filepath, mime_type=mime_type)
252
+ print(f" [/api/chat]: Fichier Google AI '{uploaded_gemini_file.name}' uploadé.")
 
253
  except Exception as e:
254
+ print(f"--- ERREUR [/api/chat]: Échec traitement/upload fichier '{filename}': {e}")
 
255
  if filepath_to_delete and os.path.exists(filepath_to_delete):
256
+ try: os.remove(filepath_to_delete)
257
+ except OSError as del_e: print(f" Erreur suppression fichier temp après erreur: {del_e}")
258
+ return jsonify({'success': False, 'error': f"Erreur traitement fichier: {e}"}), 500
 
 
 
 
259
  else:
260
+ print(f"--- ERREUR [/api/chat]: Type de fichier non autorisé: {file.filename}")
261
+ return jsonify({'success': False, 'error': f"Type de fichier non autorisé."}), 400
262
 
263
  # --- Préparation du message utilisateur pour l'historique et Gemini ---
 
264
  raw_user_text = prompt
 
265
  display_user_text = f"[{uploaded_filename}] {prompt}" if uploaded_filename and prompt else (prompt or f"[{uploaded_filename}]")
 
 
266
  user_history_entry = {
267
  'role': 'user',
268
+ 'text': display_user_text,
269
+ 'raw_text': raw_user_text,
 
270
  }
271
+
272
+ # Ajout à l'historique de session (vérifier que c'est une liste)
273
  if not isinstance(session.get('chat_history'), list):
274
+ print("--- ERREUR [/api/chat]: 'chat_history' n'est pas une liste! Réinitialisation.")
275
  session['chat_history'] = []
276
  session['chat_history'].append(user_history_entry)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
+ # --- Log de l'état de l'historique APRES ajout du message utilisateur ---
279
+ history_after_user_add = list(session.get('chat_history', [])) # Nouvelle copie
280
+ print(f"--- DEBUG [/api/chat]: Historique en session APRES ajout user message: {len(history_after_user_add)} messages") # LOG 10
281
+ # print(" [/api/chat]: Dernier message ajouté (user):")
282
+ # pprint.pprint(history_after_user_add[-1])
283
 
 
284
 
285
+ # --- Préparation des 'parts' pour l'appel Gemini ACTUEL ---
286
+ current_gemini_parts = []
287
+ # Gérer le cas où seul un fichier est envoyé
288
+ if uploaded_gemini_file and not raw_user_text:
289
+ raw_user_text = f"Décris le contenu de ce fichier : {uploaded_filename}"
290
+ final_prompt_for_gemini = raw_user_text
291
+ current_gemini_parts.append(uploaded_gemini_file)
292
+ current_gemini_parts.append(final_prompt_for_gemini)
293
+ print(f" [/api/chat]: Fichier seul détecté, prompt généré: '{final_prompt_for_gemini}'")
294
+ elif uploaded_gemini_file and raw_user_text:
295
+ final_prompt_for_gemini = raw_user_text
296
+ current_gemini_parts.append(uploaded_gemini_file)
297
+ current_gemini_parts.append(final_prompt_for_gemini)
298
+ else: # Seulement du texte (ou ni texte ni fichier valide, géré plus bas)
299
+ final_prompt_for_gemini = raw_user_text
300
+ if final_prompt_for_gemini:
301
+ current_gemini_parts.append(final_prompt_for_gemini)
302
+
303
+ # --- Recherche Web ---
304
+ if use_web_search and final_prompt_for_gemini: # Assurer qu'il y a du texte à chercher
305
+ print(f"--- LOG [/api/chat]: Activation recherche web pour: '{final_prompt_for_gemini[:60]}...'")
306
+ search_data = perform_web_search(final_prompt_for_gemini)
307
  if search_data:
308
  formatted_results = format_search_results(search_data)
309
+ enriched_prompt = f"""Voici la question originale de l'utilisateur:\n"{final_prompt_for_gemini}"\n\nInformations pertinentes de recherche web:\n--- DEBUT RESULTATS WEB ---\n{formatted_results}\n--- FIN RESULTATS WEB ---\n\nRéponds à la question originale en te basant sur ces informations ET ta connaissance générale."""
310
+ # Remplacer le dernier élément texte dans current_gemini_parts
311
+ if current_gemini_parts and isinstance(current_gemini_parts[-1], str):
312
+ current_gemini_parts[-1] = enriched_prompt
313
+ final_prompt_for_gemini = enriched_prompt # Mettre à jour pour les logs suivants
314
+ print(f" [/api/chat]: Prompt enrichi avec recherche web.")
 
 
 
 
 
315
  else:
316
+ print(f" [/api/chat]: Recherche web sans résultat pertinent ou erreur.")
 
 
 
 
 
 
 
 
 
 
 
317
 
318
+ # Vérifier si on a quelque chose à envoyer
319
+ if not current_gemini_parts:
320
+ print("--- ERREUR [/api/chat]: Aucune donnée (texte ou fichier valide) à envoyer après traitement.")
321
+ if session.get('chat_history'): session['chat_history'].pop() # Retirer le user message vide/inutile
322
+ return jsonify({'success': False, 'error': "Impossible d'envoyer une requête vide."}), 400
323
 
324
  # --- Appel à l'API Gemini ---
325
  try:
326
+ # Préparer l'historique des messages PRÉCÉDENTS pour Gemini
327
+ # Important: Utiliser une copie de l'historique SANS le dernier message utilisateur ajouté
328
+ history_for_gemini_prep = list(session.get('chat_history', []))[:-1]
329
+ gemini_history_to_send = prepare_gemini_history(history_for_gemini_prep)
 
330
 
331
  # Construire le contenu complet pour l'appel
332
+ contents_for_gemini = gemini_history_to_send + [{'role': 'user', 'parts': current_gemini_parts}]
333
 
334
+ # --- LOG DÉTAILLÉ : Ce qui est envoyé à l'API ---
 
 
 
 
 
 
 
335
  selected_model_name = MODEL_PRO if use_advanced else MODEL_FLASH
336
+ print(f"--- DEBUG [/api/chat]: Préparation de l'envoi à l'API Gemini (Modèle: {selected_model_name}) ---") # LOG 11
337
+ print(f" Nombre total de tours (historique + actuel): {len(contents_for_gemini)}") # LOG 12a
338
+ print(f" Nombre de messages d'historique formatés envoyés: {len(gemini_history_to_send)}") # LOG 12b
339
+ print(f" Contenu détaillé des 'parts' envoyées:") # LOG 13
340
+ for i, turn in enumerate(contents_for_gemini):
341
+ role = turn.get('role')
342
+ parts_details = []
343
+ for part in turn.get('parts', []):
344
+ if isinstance(part, str):
345
+ parts_details.append(f"Text({len(part)} chars): '{part[:60].replace(chr(10), ' ')}...'") # Remplacer newline pour log sur 1 ligne
346
+ elif hasattr(part, 'name') and hasattr(part, 'mime_type'):
347
+ parts_details.append(f"File(name={part.name}, mime={part.mime_type})")
348
+ else:
349
+ parts_details.append(f"UnknownPart({type(part)})")
350
+ print(f" Turn {i} (role: {role}): {', '.join(parts_details)}")
351
+ print("--------------------------------------------------------------------")
352
 
353
+ # Créer l'instance du modèle et appeler l'API
 
354
  active_model = genai.GenerativeModel(
355
  model_name=selected_model_name,
356
+ safety_settings=SAFETY_SETTINGS,
357
+ system_instruction=SYSTEM_INSTRUCTION
358
  )
359
+ print(f"--- LOG [/api/chat]: Envoi de la requête à {selected_model_name}...")
 
 
 
360
  response = active_model.generate_content(contents_for_gemini)
 
361
 
362
+ # --- Traitement de la réponse (avec logs) ---
 
363
  response_text_raw = ""
364
  response_html = ""
365
  try:
366
+ if response.parts:
 
367
  response_text_raw = response.text # .text concatène les parts textuelles
368
+ print(f"--- LOG [/api/chat]: Réponse reçue de Gemini (brute, début): '{response_text_raw[:100]}...'")
369
+ else:
370
+ feedback_info = f"Feedback: {response.prompt_feedback}" if response.prompt_feedback else "Pas de feedback détaillé."
371
+ print(f"--- AVERTISSEMENT [/api/chat]: Réponse Gemini sans 'parts'. {feedback_info}")
372
  if response.prompt_feedback and response.prompt_feedback.block_reason:
373
+ reason = response.prompt_feedback.block_reason.name
374
  response_text_raw = f"Désolé, ma réponse a été bloquée ({reason})."
 
375
  if reason == 'SAFETY' and response.prompt_feedback.safety_ratings:
376
+ blocked_cats = [r.category.name for r in response.prompt_feedback.safety_ratings if r.probability.name not in ['NEGLIGIBLE', 'LOW']]
377
+ if blocked_cats: response_text_raw += f" Catégories: {', '.join(blocked_cats)}."
 
 
378
  else:
379
+ response_text_raw = "Désolé, je n'ai pas pu générer de réponse."
380
+ print(f" [/api/chat]: Message d'erreur généré: '{response_text_raw}'")
381
 
382
+ # Conversion Markdown
383
+ response_html = markdown.markdown(response_text_raw, extensions=['fenced_code', 'tables', 'nl2br'])
384
+ if response_html != response_text_raw:
385
+ print(f" [/api/chat]: Réponse convertie en HTML.")
386
 
387
  except ValueError as e:
388
+ print(f"--- ERREUR [/api/chat]: Impossible d'extraire le texte de la réponse Gemini. Erreur: {e}")
389
+ print(f" Réponse brute: {response}")
390
+ response_text_raw = "Désolé, erreur interne lors de la lecture de la réponse."
 
 
391
  response_html = markdown.markdown(response_text_raw)
392
+ except Exception as e_resp:
393
+ print(f"--- ERREUR [/api/chat]: INATTENDUE lors traitement réponse Gemini : {e_resp}")
394
+ print(f" Réponse brute: {response}")
395
+ response_text_raw = f"Désolé, erreur inattendue ({type(e_resp).__name__})."
396
  response_html = markdown.markdown(response_text_raw)
397
 
398
+ # --- Ajout de la réponse Assistant à l'historique de session ---
 
 
 
 
 
 
399
  assistant_history_entry = {
400
  'role': 'assistant',
401
+ 'text': response_html,
402
+ 'raw_text': response_text_raw
403
  }
 
404
  if not isinstance(session.get('chat_history'), list):
405
+ print("--- ERREUR [/api/chat]: 'chat_history' n'est pas liste avant ajout assistant! Réinitialisation.")
406
+ session['chat_history'] = [user_history_entry] # Garder au moins user msg
407
  session['chat_history'].append(assistant_history_entry)
 
408
 
409
+ # --- Log de l'état final de l'historique pour ce tour ---
410
+ history_final_turn = list(session.get('chat_history', []))
411
+ print(f"--- DEBUG [/api/chat]: Historique en session FINAL après ajout assistant: {len(history_final_turn)} messages") # LOG 14
412
+ # print(" [/api/chat]: Dernier message ajouté (assistant):")
413
+ # pprint.pprint(history_final_turn[-1])
414
+
415
+ # --- Renvoyer la réponse au frontend ---
416
+ print(f"--- LOG [/api/chat]: Envoi de la réponse HTML au client.")
417
+ print(f"---==================================---\n")
418
  return jsonify({'success': True, 'message': response_html})
419
 
420
  except Exception as e:
421
+ print(f"--- ERREUR CRITIQUE [/api/chat]: Échec appel Gemini ou traitement réponse : {e}")
422
+ # Tentative de retrait du dernier message utilisateur en cas d'erreur
 
 
423
  current_history = session.get('chat_history')
424
  if isinstance(current_history, list) and current_history:
425
  try:
 
426
  if current_history[-1].get('role') == 'user':
427
  current_history.pop()
428
+ print(" [/api/chat]: Dernier message user retiré de l'historique suite à l'erreur.")
 
429
  else:
430
+ print(" [/api/chat]: Dernier message n'était pas 'user', historique non modifié après erreur.")
431
  except Exception as pop_e:
432
+ print(f" Erreur lors tentative retrait message user: {pop_e}")
433
+ print(f"---==================================---\n")
434
+ return jsonify({'success': False, 'error': f"Erreur interne: {e}"}), 500
 
 
 
435
 
436
  finally:
437
+ # --- Nettoyage des fichiers (Google AI et local) ---
438
+ # Nettoyage Google AI File
439
  if uploaded_gemini_file:
440
  try:
441
+ print(f"--- LOG [/api/chat FINALLY]: Tentative suppression fichier Google AI : {uploaded_gemini_file.name}")
442
  genai.delete_file(uploaded_gemini_file.name)
443
+ print(f" [/api/chat FINALLY]: Fichier Google AI '{uploaded_gemini_file.name}' supprimé.")
444
  except Exception as e_del_gcp:
445
+ print(f" AVERTISSEMENT [/api/chat FINALLY]: Échec suppression fichier Google AI '{uploaded_gemini_file.name}': {e_del_gcp}")
446
+ # Nettoyage Fichier Local
 
 
 
447
  if filepath_to_delete and os.path.exists(filepath_to_delete):
448
  try:
449
  os.remove(filepath_to_delete)
450
+ print(f"--- LOG [/api/chat FINALLY]: Fichier temporaire local '{filepath_to_delete}' supprimé.")
451
  except OSError as e_del_local:
452
+ print(f"--- ERREUR [/api/chat FINALLY]: Échec suppression fichier local '{filepath_to_delete}': {e_del_local}")
453
 
454
 
455
  @app.route('/clear', methods=['POST'])
456
  def clear_chat():
457
  """Efface l'historique de chat dans la session."""
458
+ print("\n--- DEBUG [/clear]: Requête POST reçue ---")
459
  session.clear() # Efface toutes les données de la session serveur actuelle
460
+ print(" [/clear]: Session serveur effacée.")
 
 
461
  is_ajax = 'XMLHttpRequest' == request.headers.get('X-Requested-With') or \
462
  'application/json' in request.headers.get('Accept', '')
 
463
  if is_ajax:
464
+ print(" [/clear]: Réponse JSON (AJAX).")
465
  return jsonify({'success': True, 'message': 'Historique effacé.'})
466
  else:
467
+ print(" [/clear]: Réponse Flash + Redirect (non-AJAX).")
468
  flash("Conversation effacée.", "info")
469
+ return redirect(url_for('root'))
470
 
471
 
472
  # --- Démarrage de l'application ---
473
  if __name__ == '__main__':
474
+ print("--- Démarrage du serveur Flask ---")
 
 
 
475
  port = int(os.environ.get('PORT', 5001))
476
+ # Mettre debug=True pour le développement et voir les logs facilement.
477
+ # ATTENTION: NE PAS UTILISER debug=True en production !
478
+ app.run(debug=True, host='0.0.0.0', port=port)
479
+
480
  # --- END OF FILE app.py ---