Docfile commited on
Commit
4533086
·
verified ·
1 Parent(s): 7961e80

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +237 -207
app.py CHANGED
@@ -5,7 +5,8 @@ import json
5
  import mimetypes
6
  from flask import Flask, request, session, jsonify, redirect, url_for, flash, render_template
7
  from dotenv import load_dotenv
8
- import google.generativeai as genai
 
9
  import requests
10
  from werkzeug.utils import secure_filename
11
  import markdown # Pour convertir la réponse en HTML
@@ -53,27 +54,56 @@ server_session = Session(app)
53
  MODEL_FLASH = 'gemini-2.0-flash'
54
  MODEL_PRO = 'gemini-2.5-pro-exp-03-25'
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"},
58
- {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
59
- {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"},
60
- {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
 
 
 
 
 
 
 
 
 
 
 
 
61
  ]
 
62
  GEMINI_CONFIGURED = False
 
63
  try:
64
  gemini_api_key = os.getenv("GOOGLE_API_KEY")
65
  if not gemini_api_key:
66
  print("ERREUR: Clé API GOOGLE_API_KEY manquante dans le fichier .env")
67
  else:
68
- genai.configure(api_key=gemini_api_key)
69
- models_list = [m.name for m in genai.list_models()]
70
- if f'models/{MODEL_FLASH}' in models_list and f'models/{MODEL_PRO}' in models_list:
71
- print(f"Configuration Gemini effectuée. Modèles requis ({MODEL_FLASH}, {MODEL_PRO}) disponibles.")
72
- print(f"System instruction: {SYSTEM_INSTRUCTION}")
73
- GEMINI_CONFIGURED = True
74
- else:
75
- print(f"ERREUR: Les modèles requis ({MODEL_FLASH}, {MODEL_PRO}) ne sont pas tous disponibles via l'API.")
76
- print(f"Modèles trouvés: {models_list}")
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
  except Exception as e:
79
  print(f"ERREUR Critique lors de la configuration initiale de Gemini : {e}")
@@ -86,80 +116,80 @@ def allowed_file(filename):
86
  return '.' in filename and \
87
  filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
88
 
89
- def perform_web_search(query):
90
- """Effectue une recherche web via l'API Serper."""
91
- serper_api_key = os.getenv("SERPER_API_KEY")
92
- if not serper_api_key:
93
- print("AVERTISSEMENT: Clé API SERPER_API_KEY manquante. Recherche web désactivée.")
94
- return None
95
- search_url = "https://google.serper.dev/search"
96
- headers = {'X-API-KEY': serper_api_key, 'Content-Type': 'application/json'}
97
- payload = json.dumps({"q": query, "gl": "fr", "hl": "fr"})
98
  try:
99
- print(f"--- LOG WEBSEARCH: Recherche Serper pour: '{query}'")
100
- response = requests.post(search_url, headers=headers, data=payload, timeout=10)
101
- response.raise_for_status()
102
- data = response.json()
103
- print("--- LOG WEBSEARCH: Résultats de recherche Serper obtenus.")
104
- return data
105
- except requests.exceptions.RequestException as e:
106
- print(f"--- LOG WEBSEARCH: Erreur lors de la recherche web : {e}")
107
- return None
108
- except json.JSONDecodeError as e:
109
- print(f"--- LOG WEBSEARCH: Erreur JSON Serper : {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  return None
111
 
112
- def format_search_results(data):
113
- """Met en forme les résultats de recherche (format Markdown)."""
114
- if not data: return "Aucun résultat de recherche web trouvé pertinent."
115
- results = []
116
- if data.get('answerBox'):
117
- ab = data['answerBox']
118
- title = ab.get('title', '')
119
- snippet = ab.get('snippet') or ab.get('answer', '')
120
- if snippet: results.append(f"**Réponse rapide : {title}**\n{snippet}\n")
121
- if data.get('knowledgeGraph'):
122
- kg = data['knowledgeGraph']
123
- title = kg.get('title', '')
124
- type = kg.get('type', '')
125
- description = kg.get('description', '')
126
- if title and description: results.append(f"**{title} ({type})**\n{description}\n")
127
- if kg.get('attributes'):
128
- results.append("\n**Attributs :**")
129
- for attr, value in kg['attributes'].items(): results.append(f"- {attr}: {value}")
130
- results.append("")
131
- if data.get('organic'):
132
- results.append("**Pages web pertinentes :**")
133
- for i, item in enumerate(data['organic'][:3], 1):
134
- title = item.get('title', 'Sans titre')
135
- link = item.get('link', '#')
136
- snippet = item.get('snippet', 'Pas de description.')
137
- results.append(f"{i}. **[{title}]({link})**\n {snippet}\n")
138
- if data.get('peopleAlsoAsk'):
139
- results.append("**Questions liées :**")
140
- for i, item in enumerate(data['peopleAlsoAsk'][:2], 1): results.append(f"- {item.get('question', '')}")
141
- if not results: return "Aucun résultat structuré trouvé dans la recherche web."
142
- print(f"--- LOG WEBSEARCH: Résultats formatés (début): '{(' '.join(results))[:100]}...'")
143
- return "\n".join(results)
144
 
145
  def prepare_gemini_history(chat_history):
146
  """Convertit l'historique stocké en session au format attendu par Gemini API."""
147
- print(f"--- DEBUG [prepare_gemini_history]: Entrée avec {len(chat_history)} messages") # LOG 1
 
 
148
  gemini_history = []
149
- for i, message in enumerate(list(chat_history)): # Utiliser list() pour itérer sur une copie
150
- role = 'user' if message.get('role') == 'user' else 'model'
151
  text_part = message.get('raw_text')
152
- # Log détaillé pour chaque message traité
153
- 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
154
-
155
- if text_part: # Important: Ne pas ajouter de messages vides à l'historique Gemini
156
- parts = [text_part]
157
- gemini_history.append({'role': role, 'parts': parts})
 
 
 
 
 
 
 
 
 
158
  else:
159
- # Log si un message est ignoré car vide
160
- print(f" AVERTISSEMENT [prepare_gemini_history]: raw_text vide ou absent pour le message {i}, ignoré pour l'historique Gemini.") # LOG 3
161
-
162
- print(f"--- DEBUG [prepare_gemini_history]: Sortie avec {len(gemini_history)} messages formatés pour Gemini") # LOG 4
163
  return gemini_history
164
 
165
  # --- Routes Flask ---
@@ -173,18 +203,14 @@ def root():
173
  @app.route('/api/history', methods=['GET'])
174
  def get_history():
175
  """Fournit l'historique de chat stocké en session au format JSON."""
176
- print("\n--- DEBUG [/api/history]: Début requête GET ---") # LOG 5
177
  if 'chat_history' not in session:
178
  session['chat_history'] = []
179
  print(" [/api/history]: Session 'chat_history' initialisée (vide).")
180
 
181
  display_history = []
182
  current_history = session.get('chat_history', [])
183
- print(f" [/api/history]: Historique récupéré de la session serveur: {len(current_history)} messages.") # LOG 6
184
-
185
- # Optionnel: Afficher la structure brute pour un debug profond
186
- # print(" [/api/history]: Contenu brut de l'historique session:")
187
- # pprint.pprint(current_history)
188
 
189
  for i, msg in enumerate(current_history):
190
  # Vérifier la structure de chaque message récupéré
@@ -195,9 +221,9 @@ def get_history():
195
  })
196
  else:
197
  # Log si un message dans la session est mal formé
198
- print(f" AVERTISSEMENT [/api/history]: Format invalide dans l'historique session au message {i}: {msg}") # LOG 7
199
 
200
- print(f" [/api/history]: Historique préparé pour le frontend: {len(display_history)} messages.") # LOG 8
201
  return jsonify({'success': True, 'history': display_history})
202
 
203
  @app.route('/api/chat', methods=['POST'])
@@ -206,7 +232,7 @@ def chat_api():
206
  print(f"\n---===================================---")
207
  print(f"--- DEBUG [/api/chat]: Nouvelle requête POST ---")
208
 
209
- if not GEMINI_CONFIGURED:
210
  print("--- ERREUR [/api/chat]: Tentative d'appel sans configuration Gemini valide.")
211
  return jsonify({'success': False, 'error': "Le service IA n'est pas configuré correctement."}), 503
212
 
@@ -228,14 +254,10 @@ def chat_api():
228
  # --- Log de l'état de l'historique AVANT toute modification ---
229
  if 'chat_history' not in session:
230
  session['chat_history'] = []
231
- history_before_user_add = list(session.get('chat_history', [])) # Copie pour le log
232
- print(f"--- DEBUG [/api/chat]: Historique en session AVANT ajout user message: {len(history_before_user_add)} messages") # LOG 9
233
- # Optionnel: Afficher les derniers messages pour voir le contexte précédent
234
- # if history_before_user_add:
235
- # print(" [/api/chat]: Dernier(s) message(s) avant ajout:")
236
- # pprint.pprint(history_before_user_add[-2:]) # Afficher les 2 derniers
237
-
238
- uploaded_gemini_file = None
239
  uploaded_filename = None
240
  filepath_to_delete = None
241
 
@@ -251,9 +273,20 @@ def chat_api():
251
  uploaded_filename = filename
252
  print(f" [/api/chat]: Fichier '{filename}' sauvegardé dans '{filepath}'")
253
  mime_type = mimetypes.guess_type(filepath)[0] or 'application/octet-stream'
254
- print(f" [/api/chat]: Upload Google AI (Mime: {mime_type})...")
255
- uploaded_gemini_file = genai.upload_file(path=filepath, mime_type=mime_type)
256
- print(f" [/api/chat]: Fichier Google AI '{uploaded_gemini_file.name}' uploadé.")
 
 
 
 
 
 
 
 
 
 
 
257
  except Exception as e:
258
  print(f"--- ERREUR [/api/chat]: Échec traitement/upload fichier '{filename}': {e}")
259
  if filepath_to_delete and os.path.exists(filepath_to_delete):
@@ -280,147 +313,153 @@ def chat_api():
280
  session['chat_history'].append(user_history_entry)
281
 
282
  # --- Log de l'état de l'historique APRES ajout du message utilisateur ---
283
- history_after_user_add = list(session.get('chat_history', [])) # Nouvelle copie
284
- print(f"--- DEBUG [/api/chat]: Historique en session APRES ajout user message: {len(history_after_user_add)} messages") # LOG 10
285
- # print(" [/api/chat]: Dernier message ajouté (user):")
286
- # pprint.pprint(history_after_user_add[-1])
287
 
 
 
288
 
289
- # --- Préparation des 'parts' pour l'appel Gemini ACTUEL ---
290
- current_gemini_parts = []
 
291
  # Gérer le cas où seul un fichier est envoyé
292
- if uploaded_gemini_file and not raw_user_text:
293
- raw_user_text = f"Décris le contenu de ce fichier : {uploaded_filename}"
294
- final_prompt_for_gemini = raw_user_text
295
- current_gemini_parts.append(uploaded_gemini_file)
296
- current_gemini_parts.append(final_prompt_for_gemini)
297
- print(f" [/api/chat]: Fichier seul détecté, prompt généré: '{final_prompt_for_gemini}'")
298
- elif uploaded_gemini_file and raw_user_text:
299
- final_prompt_for_gemini = raw_user_text
300
- current_gemini_parts.append(uploaded_gemini_file)
301
- current_gemini_parts.append(final_prompt_for_gemini)
302
- else: # Seulement du texte (ou ni texte ni fichier valide, géré plus bas)
303
  final_prompt_for_gemini = raw_user_text
304
- if final_prompt_for_gemini:
305
- current_gemini_parts.append(final_prompt_for_gemini)
306
 
307
- # --- Recherche Web ---
308
- if use_web_search and final_prompt_for_gemini: # Assurer qu'il y a du texte à chercher
 
309
  print(f"--- LOG [/api/chat]: Activation recherche web pour: '{final_prompt_for_gemini[:60]}...'")
310
- search_data = perform_web_search(final_prompt_for_gemini)
311
- if search_data:
312
- formatted_results = format_search_results(search_data)
313
- 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."""
314
- # Remplacer le dernier élément texte dans current_gemini_parts
315
- if current_gemini_parts and isinstance(current_gemini_parts[-1], str):
316
- current_gemini_parts[-1] = enriched_prompt
317
- final_prompt_for_gemini = enriched_prompt # Mettre à jour pour les logs suivants
318
- print(f" [/api/chat]: Prompt enrichi avec recherche web.")
319
- else:
320
- print(f" [/api/chat]: Recherche web sans résultat pertinent ou erreur.")
321
-
322
- # Vérifier si on a quelque chose à envoyer
323
- if not current_gemini_parts:
324
- print("--- ERREUR [/api/chat]: Aucune donnée (texte ou fichier valide) à envoyer après traitement.")
325
- if session.get('chat_history'): session['chat_history'].pop() # Retirer le user message vide/inutile
326
- return jsonify({'success': False, 'error': "Impossible d'envoyer une requête vide."}), 400
327
 
328
  # --- Appel à l'API Gemini ---
329
  try:
 
 
330
  # Préparer l'historique des messages PRÉCÉDENTS pour Gemini
331
- # Important: Utiliser une copie de l'historique SANS le dernier message utilisateur ajouté
332
- history_for_gemini_prep = list(session.get('chat_history', []))[:-1]
333
  gemini_history_to_send = prepare_gemini_history(history_for_gemini_prep)
334
-
335
- # Construire le contenu complet pour l'appel
336
- contents_for_gemini = gemini_history_to_send + [{'role': 'user', 'parts': current_gemini_parts}]
337
-
338
- # --- LOG DÉTAILLÉ : Ce qui est envoyé à l'API ---
339
- selected_model_name = MODEL_PRO if use_advanced else MODEL_FLASH
340
- print(f"--- DEBUG [/api/chat]: Préparation de l'envoi à l'API Gemini (Modèle: {selected_model_name}) ---") # LOG 11
341
- print(f" Nombre total de tours (historique + actuel): {len(contents_for_gemini)}") # LOG 12a
342
- print(f" Nombre de messages d'historique formatés envoyés: {len(gemini_history_to_send)}") # LOG 12b
343
- print(f" Contenu détaillé des 'parts' envoyées:") # LOG 13
344
- for i, turn in enumerate(contents_for_gemini):
345
- role = turn.get('role')
346
- parts_details = []
347
- for part in turn.get('parts', []):
348
- if isinstance(part, str):
349
- parts_details.append(f"Text({len(part)} chars): '{part[:60].replace(chr(10), ' ')}...'") # Remplacer newline pour log sur 1 ligne
350
- elif hasattr(part, 'name') and hasattr(part, 'mime_type'):
351
- parts_details.append(f"File(name={part.name}, mime={part.mime_type})")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  else:
353
- parts_details.append(f"UnknownPart({type(part)})")
354
- print(f" Turn {i} (role: {role}): {', '.join(parts_details)}")
355
- print("--------------------------------------------------------------------")
356
-
357
- # Créer l'instance du modèle et appeler l'API
358
- active_model = genai.GenerativeModel(
359
- model_name=selected_model_name,
360
- safety_settings=SAFETY_SETTINGS,
361
- system_instruction=SYSTEM_INSTRUCTION
362
  )
 
 
 
 
 
 
363
  print(f"--- LOG [/api/chat]: Envoi de la requête à {selected_model_name}...")
364
- response = active_model.generate_content(contents_for_gemini)
365
-
 
 
 
 
366
  # --- Traitement de la réponse (avec logs) ---
367
  response_text_raw = ""
368
  response_html = ""
 
369
  try:
370
- if response.parts:
371
- response_text_raw = response.text # .text concatène les parts textuelles
 
372
  print(f"--- LOG [/api/chat]: Réponse reçue de Gemini (brute, début): '{response_text_raw[:100]}...'")
 
 
 
373
  else:
374
- feedback_info = f"Feedback: {response.prompt_feedback}" if response.prompt_feedback else "Pas de feedback détaillé."
375
- print(f"--- AVERTISSEMENT [/api/chat]: Réponse Gemini sans 'parts'. {feedback_info}")
376
- if response.prompt_feedback and response.prompt_feedback.block_reason:
377
- reason = response.prompt_feedback.block_reason.name
378
- response_text_raw = f"Désolé, ma réponse a été bloquée ({reason})."
379
- if reason == 'SAFETY' and response.prompt_feedback.safety_ratings:
380
- blocked_cats = [r.category.name for r in response.prompt_feedback.safety_ratings if r.probability.name not in ['NEGLIGIBLE', 'LOW']]
381
- if blocked_cats: response_text_raw += f" Catégories: {', '.join(blocked_cats)}."
 
382
  else:
383
- response_text_raw = "Désolé, je n'ai pas pu générer de réponse."
 
384
  print(f" [/api/chat]: Message d'erreur généré: '{response_text_raw}'")
385
-
386
  # Conversion Markdown
387
  response_html = markdown.markdown(response_text_raw, extensions=['fenced_code', 'tables', 'nl2br'])
388
  if response_html != response_text_raw:
389
- print(f" [/api/chat]: Réponse convertie en HTML.")
390
-
391
- except ValueError as e:
392
- print(f"--- ERREUR [/api/chat]: Impossible d'extraire le texte de la réponse Gemini. Erreur: {e}")
393
- print(f" Réponse brute: {response}")
394
- response_text_raw = "Désolé, erreur interne lors de la lecture de la réponse."
395
- response_html = markdown.markdown(response_text_raw)
396
  except Exception as e_resp:
397
- print(f"--- ERREUR [/api/chat]: INATTENDUE lors traitement réponse Gemini : {e_resp}")
398
  print(f" Réponse brute: {response}")
399
  response_text_raw = f"Désolé, erreur inattendue ({type(e_resp).__name__})."
400
  response_html = markdown.markdown(response_text_raw)
401
-
402
  # --- Ajout de la réponse Assistant à l'historique de session ---
403
  assistant_history_entry = {
404
  'role': 'assistant',
405
  'text': response_html,
406
  'raw_text': response_text_raw
407
  }
 
408
  if not isinstance(session.get('chat_history'), list):
409
  print("--- ERREUR [/api/chat]: 'chat_history' n'est pas liste avant ajout assistant! Réinitialisation.")
410
  session['chat_history'] = [user_history_entry] # Garder au moins user msg
 
411
  session['chat_history'].append(assistant_history_entry)
412
-
413
  # --- Log de l'état final de l'historique pour ce tour ---
414
  history_final_turn = list(session.get('chat_history', []))
415
- print(f"--- DEBUG [/api/chat]: Historique en session FINAL après ajout assistant: {len(history_final_turn)} messages") # LOG 14
416
- # print(" [/api/chat]: Dernier message ajouté (assistant):")
417
- # pprint.pprint(history_final_turn[-1])
418
-
419
  # --- Renvoyer la réponse au frontend ---
420
  print(f"--- LOG [/api/chat]: Envoi de la réponse HTML au client.")
421
  print(f"---==================================---\n")
422
  return jsonify({'success': True, 'message': response_html})
423
-
424
  except Exception as e:
425
  print(f"--- ERREUR CRITIQUE [/api/chat]: Échec appel Gemini ou traitement réponse : {e}")
426
  # Tentative de retrait du dernier message utilisateur en cas d'erreur
@@ -433,21 +472,12 @@ def chat_api():
433
  else:
434
  print(" [/api/chat]: Dernier message n'était pas 'user', historique non modifié après erreur.")
435
  except Exception as pop_e:
436
- print(f" Erreur lors tentative retrait message user: {pop_e}")
437
  print(f"---==================================---\n")
438
  return jsonify({'success': False, 'error': f"Erreur interne: {e}"}), 500
439
-
440
  finally:
441
- # --- Nettoyage des fichiers (Google AI et local) ---
442
- # Nettoyage Google AI File
443
- if uploaded_gemini_file:
444
- try:
445
- print(f"--- LOG [/api/chat FINALLY]: Tentative suppression fichier Google AI : {uploaded_gemini_file.name}")
446
- genai.delete_file(uploaded_gemini_file.name)
447
- print(f" [/api/chat FINALLY]: Fichier Google AI '{uploaded_gemini_file.name}' supprimé.")
448
- except Exception as e_del_gcp:
449
- print(f" AVERTISSEMENT [/api/chat FINALLY]: Échec suppression fichier Google AI '{uploaded_gemini_file.name}': {e_del_gcp}")
450
- # Nettoyage Fichier Local
451
  if filepath_to_delete and os.path.exists(filepath_to_delete):
452
  try:
453
  os.remove(filepath_to_delete)
 
5
  import mimetypes
6
  from flask import Flask, request, session, jsonify, redirect, url_for, flash, render_template
7
  from dotenv import load_dotenv
8
+ from google import genai
9
+ from google.genai import types
10
  import requests
11
  from werkzeug.utils import secure_filename
12
  import markdown # Pour convertir la réponse en HTML
 
54
  MODEL_FLASH = 'gemini-2.0-flash'
55
  MODEL_PRO = 'gemini-2.5-pro-exp-03-25'
56
  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."
57
+
58
+ # Nouveaux réglages de sécurité avec le nouveau SDK
59
  SAFETY_SETTINGS = [
60
+ types.SafetySetting(
61
+ category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
62
+ threshold=types.HarmBlockThreshold.BLOCK_NONE,
63
+ ),
64
+ types.SafetySetting(
65
+ category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
66
+ threshold=types.HarmBlockThreshold.BLOCK_NONE,
67
+ ),
68
+ types.SafetySetting(
69
+ category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
70
+ threshold=types.HarmBlockThreshold.BLOCK_NONE,
71
+ ),
72
+ types.SafetySetting(
73
+ category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
74
+ threshold=types.HarmBlockThreshold.BLOCK_NONE,
75
+ )
76
  ]
77
+
78
  GEMINI_CONFIGURED = False
79
+ genai_client = None
80
  try:
81
  gemini_api_key = os.getenv("GOOGLE_API_KEY")
82
  if not gemini_api_key:
83
  print("ERREUR: Clé API GOOGLE_API_KEY manquante dans le fichier .env")
84
  else:
85
+ # Initialisation du client avec le nouveau SDK
86
+ genai_client = genai.Client(api_key=gemini_api_key)
87
+
88
+ # Vérification de la disponibilité des modèles
89
+ try:
90
+ # Le nouveau SDK peut avoir une méthode différente pour lister les modèles
91
+ models = genai_client.list_models()
92
+ models_list = [model.name for model in models]
93
+
94
+ # Vérifier si les modèles requis sont disponibles
95
+ if any(MODEL_FLASH in model for model in models_list) and any(MODEL_PRO in model for model in models_list):
96
+ print(f"Configuration Gemini effectuée. Modèles requis ({MODEL_FLASH}, {MODEL_PRO}) disponibles.")
97
+ print(f"System instruction: {SYSTEM_INSTRUCTION}")
98
+ GEMINI_CONFIGURED = True
99
+ else:
100
+ print(f"ERREUR: Les modèles requis ({MODEL_FLASH}, {MODEL_PRO}) ne sont pas tous disponibles via l'API.")
101
+ print(f"Modèles trouvés: {models_list}")
102
+ except Exception as e_models:
103
+ print(f"ERREUR lors de la vérification des modèles: {e_models}")
104
+ # En cas d'échec de la vérification des modèles, supposons que les modèles sont disponibles
105
+ print("Tentative de continuer sans vérification des modèles disponibles.")
106
+ GEMINI_CONFIGURED = True
107
 
108
  except Exception as e:
109
  print(f"ERREUR Critique lors de la configuration initiale de Gemini : {e}")
 
116
  return '.' in filename and \
117
  filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
118
 
119
+ def perform_web_search_with_gemini(query, client, model_id):
120
+ """Effectue une recherche web via l'API Google Search intégrée à Gemini."""
 
 
 
 
 
 
 
121
  try:
122
+ print(f"--- LOG WEBSEARCH: Recherche Google avec Gemini pour: '{query}'")
123
+
124
+ # Utiliser l'outil de recherche Google intégré à Gemini
125
+ response = client.models.generate_content(
126
+ model=model_id,
127
+ contents=[{"role": "user", "parts": [{"text": query}]}],
128
+ config=types.GenerateContentConfig(
129
+ tools=[{"google_search": {}}]
130
+ )
131
+ )
132
+
133
+ print("--- LOG WEBSEARCH: Résultats de recherche Google obtenus.")
134
+
135
+ # Extraction des informations de recherche pour le débogage
136
+ if hasattr(response, 'candidates') and response.candidates:
137
+ if hasattr(response.candidates[0], 'grounding_metadata'):
138
+ search_queries = getattr(response.candidates[0].grounding_metadata, 'web_search_queries', None)
139
+ if search_queries:
140
+ print(f"--- LOG WEBSEARCH: Requêtes utilisées: {search_queries}")
141
+
142
+ grounding_chunks = getattr(response.candidates[0].grounding_metadata, 'grounding_chunks', None)
143
+ if grounding_chunks:
144
+ sources = [getattr(chunk, 'web', None) for chunk in grounding_chunks]
145
+ source_titles = [getattr(source, 'title', 'Sans titre') for source in sources if source]
146
+ print(f"--- LOG WEBSEARCH: Sources utilisées: {', '.join(source_titles)}")
147
+
148
+ return response
149
+
150
+ except Exception as e:
151
+ print(f"--- LOG WEBSEARCH: Erreur lors de la recherche web avec Gemini : {e}")
152
  return None
153
 
154
+ def format_search_response(response):
155
+ """Extrait et met en forme le texte de la réponse de recherche web."""
156
+ if not response:
157
+ return "Aucun résultat de recherche web trouvé pertinent."
158
+
159
+ try:
160
+ return response.text
161
+ except Exception as e:
162
+ print(f"--- LOG WEBSEARCH: Erreur lors de l'extraction du texte de la réponse : {e}")
163
+ return "Impossible d'extraire les résultats de recherche."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
  def prepare_gemini_history(chat_history):
166
  """Convertit l'historique stocké en session au format attendu par Gemini API."""
167
+ print(f"--- DEBUG [prepare_gemini_history]: Entrée avec {len(chat_history)} messages")
168
+
169
+ # Le nouveau SDK n'attend pas la même structure d'historique que l'ancien
170
  gemini_history = []
171
+ for i, message in enumerate(list(chat_history)):
172
+ role = message.get('role')
173
  text_part = message.get('raw_text')
174
+
175
+ print(f" [prepare_gemini_history] Message {i} (rôle: {role}): raw_text présent? {'Oui' if text_part is not None else 'NON'}, contenu début: '{str(text_part)[:60]}...'")
176
+
177
+ if text_part: # Important: Ne pas ajouter de messages vides
178
+ # Format corrigé pour le nouveau SDK
179
+ if role == 'user':
180
+ gemini_history.append({
181
+ "role": "user",
182
+ "parts": [{"text": text_part}]
183
+ })
184
+ else: # assistant
185
+ gemini_history.append({
186
+ "role": "model",
187
+ "parts": [{"text": text_part}]
188
+ })
189
  else:
190
+ print(f" AVERTISSEMENT [prepare_gemini_history]: raw_text vide ou absent pour le message {i}, ignoré pour l'historique Gemini.")
191
+
192
+ print(f"--- DEBUG [prepare_gemini_history]: Sortie avec {len(gemini_history)} messages formatés pour Gemini")
 
193
  return gemini_history
194
 
195
  # --- Routes Flask ---
 
203
  @app.route('/api/history', methods=['GET'])
204
  def get_history():
205
  """Fournit l'historique de chat stocké en session au format JSON."""
206
+ print("\n--- DEBUG [/api/history]: Début requête GET ---")
207
  if 'chat_history' not in session:
208
  session['chat_history'] = []
209
  print(" [/api/history]: Session 'chat_history' initialisée (vide).")
210
 
211
  display_history = []
212
  current_history = session.get('chat_history', [])
213
+ print(f" [/api/history]: Historique récupéré de la session serveur: {len(current_history)} messages.")
 
 
 
 
214
 
215
  for i, msg in enumerate(current_history):
216
  # Vérifier la structure de chaque message récupéré
 
221
  })
222
  else:
223
  # Log si un message dans la session est mal formé
224
+ print(f" AVERTISSEMENT [/api/history]: Format invalide dans l'historique session au message {i}: {msg}")
225
 
226
+ print(f" [/api/history]: Historique préparé pour le frontend: {len(display_history)} messages.")
227
  return jsonify({'success': True, 'history': display_history})
228
 
229
  @app.route('/api/chat', methods=['POST'])
 
232
  print(f"\n---===================================---")
233
  print(f"--- DEBUG [/api/chat]: Nouvelle requête POST ---")
234
 
235
+ if not GEMINI_CONFIGURED or not genai_client:
236
  print("--- ERREUR [/api/chat]: Tentative d'appel sans configuration Gemini valide.")
237
  return jsonify({'success': False, 'error': "Le service IA n'est pas configuré correctement."}), 503
238
 
 
254
  # --- Log de l'état de l'historique AVANT toute modification ---
255
  if 'chat_history' not in session:
256
  session['chat_history'] = []
257
+ history_before_user_add = list(session.get('chat_history', []))
258
+ print(f"--- DEBUG [/api/chat]: Historique en session AVANT ajout user message: {len(history_before_user_add)} messages")
259
+
260
+ uploaded_file_part = None
 
 
 
 
261
  uploaded_filename = None
262
  filepath_to_delete = None
263
 
 
273
  uploaded_filename = filename
274
  print(f" [/api/chat]: Fichier '{filename}' sauvegardé dans '{filepath}'")
275
  mime_type = mimetypes.guess_type(filepath)[0] or 'application/octet-stream'
276
+ print(f" [/api/chat]: Préparation du fichier pour Gemini (Mime: {mime_type})...")
277
+
278
+ # Avec le nouveau SDK, la méthode d'upload a été mise à jour
279
+ with open(filepath, "rb") as f:
280
+ file_data = f.read()
281
+
282
+ # Création d'une partie de type fichier pour l'API
283
+ uploaded_file_part = {
284
+ "inline_data": {
285
+ "mime_type": mime_type,
286
+ "data": file_data
287
+ }
288
+ }
289
+ print(f" [/api/chat]: Fichier préparé pour Gemini.")
290
  except Exception as e:
291
  print(f"--- ERREUR [/api/chat]: Échec traitement/upload fichier '{filename}': {e}")
292
  if filepath_to_delete and os.path.exists(filepath_to_delete):
 
313
  session['chat_history'].append(user_history_entry)
314
 
315
  # --- Log de l'état de l'historique APRES ajout du message utilisateur ---
316
+ history_after_user_add = list(session.get('chat_history', []))
317
+ print(f"--- DEBUG [/api/chat]: Historique en session APRES ajout user message: {len(history_after_user_add)} messages")
 
 
318
 
319
+ # --- Sélection du modèle ---
320
+ selected_model_name = MODEL_PRO if use_advanced else MODEL_FLASH
321
 
322
+ # --- Préparation des contenus pour l'appel Gemini ---
323
+ final_prompt_for_gemini = raw_user_text
324
+
325
  # Gérer le cas où seul un fichier est envoyé
326
+ if uploaded_file_part and not raw_user_text:
327
+ raw_user_text = f"Décris le contenu de ce fichier : {uploaded_filename}"
 
 
 
 
 
 
 
 
 
328
  final_prompt_for_gemini = raw_user_text
329
+ print(f" [/api/chat]: Fichier seul détecté, prompt généré: '{final_prompt_for_gemini}'")
 
330
 
331
+ # --- Recherche Web si demandée ---
332
+ web_search_results = None
333
+ if use_web_search and final_prompt_for_gemini:
334
  print(f"--- LOG [/api/chat]: Activation recherche web pour: '{final_prompt_for_gemini[:60]}...'")
335
+ web_search_response = perform_web_search_with_gemini(final_prompt_for_gemini, genai_client, selected_model_name)
336
+ if web_search_response:
337
+ web_search_results = format_search_response(web_search_response)
338
+ print(f" [/api/chat]: Résultats de recherche obtenus.")
 
 
 
 
 
 
 
 
 
 
 
 
 
339
 
340
  # --- Appel à l'API Gemini ---
341
  try:
342
+ print(f"--- DEBUG [/api/chat]: Préparation de l'envoi à l'API Gemini (Modèle: {selected_model_name}) ---")
343
+
344
  # Préparer l'historique des messages PRÉCÉDENTS pour Gemini
345
+ history_for_gemini_prep = list(session.get('chat_history', []))[:-1] # Sans le dernier message utilisateur
 
346
  gemini_history_to_send = prepare_gemini_history(history_for_gemini_prep)
347
+
348
+ # Construire le contenu pour l'appel avec le nouveau SDK
349
+ contents = gemini_history_to_send.copy() # Commencer avec l'historique
350
+
351
+ # Ajouter le message actuel de l'utilisateur
352
+ current_user_parts = []
353
+
354
+ # Si un fichier est présent, l'ajouter
355
+ if uploaded_file_part:
356
+ current_user_parts.append(uploaded_file_part)
357
+
358
+ # Ajouter le texte du message
359
+ if final_prompt_for_gemini:
360
+ current_user_parts.append({"text": final_prompt_for_gemini})
361
+
362
+ if current_user_parts:
363
+ contents.append({
364
+ "role": "user",
365
+ "parts": current_user_parts
366
+ })
367
+
368
+ # Log détaillé de ce qui est envoyé
369
+ print(f" Nombre total de messages pour Gemini: {len(contents)}")
370
+ for i, content in enumerate(contents):
371
+ role = content.get("role")
372
+ parts = content.get("parts", [])
373
+ parts_info = []
374
+
375
+ for part in parts:
376
+ if isinstance(part, dict) and "text" in part:
377
+ parts_info.append(f"Text({len(part['text'])} chars): '{part['text'][:50]}...'")
378
+ elif isinstance(part, dict) and "inline_data" in part:
379
+ parts_info.append(f"File(mime={part['inline_data']['mime_type']})")
380
  else:
381
+ parts_info.append(f"Part({type(part)})")
382
+
383
+ print(f" Message {i} (role: {role}): {', '.join(parts_info)}")
384
+
385
+ # Configuration de l'appel à l'API
386
+ generate_config = types.GenerateContentConfig(
387
+ system_instruction=SYSTEM_INSTRUCTION,
388
+ safety_settings=SAFETY_SETTINGS
 
389
  )
390
+
391
+ # Ajouter la configuration de recherche web si demandée
392
+ if use_web_search:
393
+ generate_config.tools = [{"google_search": {}}]
394
+
395
+ # Appel à l'API avec le nouveau SDK
396
  print(f"--- LOG [/api/chat]: Envoi de la requête à {selected_model_name}...")
397
+ response = genai_client.models.generate_content(
398
+ model=selected_model_name,
399
+ contents=contents,
400
+ config=generate_config
401
+ )
402
+
403
  # --- Traitement de la réponse (avec logs) ---
404
  response_text_raw = ""
405
  response_html = ""
406
+
407
  try:
408
+ # Extraction du texte de la réponse avec le nouveau SDK
409
+ if hasattr(response, 'text'):
410
+ response_text_raw = response.text
411
  print(f"--- LOG [/api/chat]: Réponse reçue de Gemini (brute, début): '{response_text_raw[:100]}...'")
412
+ elif hasattr(response, 'parts'):
413
+ response_text_raw = ' '.join([str(part) for part in response.parts])
414
+ print(f"--- LOG [/api/chat]: Réponse extraite des parts: '{response_text_raw[:100]}...'")
415
  else:
416
+ # Gestion des erreurs de sécurité ou autres
417
+ if hasattr(response, 'prompt_feedback'):
418
+ feedback = response.prompt_feedback
419
+ if feedback:
420
+ block_reason = getattr(feedback, 'block_reason', None)
421
+ if block_reason:
422
+ response_text_raw = f"Désolé, ma réponse a été bloquée ({block_reason})."
423
+ else:
424
+ response_text_raw = "Désolé, je n'ai pas pu générer de réponse (restrictions de sécurité)."
425
  else:
426
+ response_text_raw = "Désolé, je n'ai pas pu générer de réponse."
427
+
428
  print(f" [/api/chat]: Message d'erreur généré: '{response_text_raw}'")
429
+
430
  # Conversion Markdown
431
  response_html = markdown.markdown(response_text_raw, extensions=['fenced_code', 'tables', 'nl2br'])
432
  if response_html != response_text_raw:
433
+ print(f" [/api/chat]: Réponse convertie en HTML.")
434
+
 
 
 
 
 
435
  except Exception as e_resp:
436
+ print(f"--- ERREUR [/api/chat]: Erreur lors du traitement de la réponse Gemini : {e_resp}")
437
  print(f" Réponse brute: {response}")
438
  response_text_raw = f"Désolé, erreur inattendue ({type(e_resp).__name__})."
439
  response_html = markdown.markdown(response_text_raw)
440
+
441
  # --- Ajout de la réponse Assistant à l'historique de session ---
442
  assistant_history_entry = {
443
  'role': 'assistant',
444
  'text': response_html,
445
  'raw_text': response_text_raw
446
  }
447
+
448
  if not isinstance(session.get('chat_history'), list):
449
  print("--- ERREUR [/api/chat]: 'chat_history' n'est pas liste avant ajout assistant! Réinitialisation.")
450
  session['chat_history'] = [user_history_entry] # Garder au moins user msg
451
+
452
  session['chat_history'].append(assistant_history_entry)
453
+
454
  # --- Log de l'état final de l'historique pour ce tour ---
455
  history_final_turn = list(session.get('chat_history', []))
456
+ print(f"--- DEBUG [/api/chat]: Historique en session FINAL après ajout assistant: {len(history_final_turn)} messages")
457
+
 
 
458
  # --- Renvoyer la réponse au frontend ---
459
  print(f"--- LOG [/api/chat]: Envoi de la réponse HTML au client.")
460
  print(f"---==================================---\n")
461
  return jsonify({'success': True, 'message': response_html})
462
+
463
  except Exception as e:
464
  print(f"--- ERREUR CRITIQUE [/api/chat]: Échec appel Gemini ou traitement réponse : {e}")
465
  # Tentative de retrait du dernier message utilisateur en cas d'erreur
 
472
  else:
473
  print(" [/api/chat]: Dernier message n'était pas 'user', historique non modifié après erreur.")
474
  except Exception as pop_e:
475
+ print(f" Erreur lors tentative retrait message user: {pop_e}")
476
  print(f"---==================================---\n")
477
  return jsonify({'success': False, 'error': f"Erreur interne: {e}"}), 500
478
+
479
  finally:
480
+ # --- Nettoyage des fichiers locaux ---
 
 
 
 
 
 
 
 
 
481
  if filepath_to_delete and os.path.exists(filepath_to_delete):
482
  try:
483
  os.remove(filepath_to_delete)