Docfile commited on
Commit
60b2cb1
·
verified ·
1 Parent(s): 68ce09b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -175
app.py CHANGED
@@ -3,69 +3,56 @@ import json
3
  from flask import Flask, render_template, request, session, redirect, url_for, flash
4
  from dotenv import load_dotenv
5
  import google.generativeai as genai
6
- import requests # Pour Serper API
7
  from werkzeug.utils import secure_filename
8
- import mimetypes # Pour vérifier le type de fichier
9
 
10
  load_dotenv()
11
 
12
  app = Flask(__name__)
13
- # Très important pour utiliser les sessions Flask !
14
  app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'une-clé-secrète-par-défaut-pour-dev')
15
- # Configuration pour les uploads
16
  UPLOAD_FOLDER = 'temp'
17
- ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg'} # Extensions autorisées
18
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
19
- app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # Limite de taille (ex: 16MB)
20
 
21
- # Créer le dossier temp s'il n'existe pas
22
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
23
 
24
- # Configuration de l'API Gemini
25
  try:
26
  genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
27
-
28
  safety_settings = [
29
  {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
30
  {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
31
  {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"},
32
  {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
33
  ]
34
-
35
  model = genai.GenerativeModel(
36
- 'gemini-1.5-flash', # Utiliser un modèle stable recommandé si 'gemini-2.0-flash-exp' cause problème
37
- # 'gemini-1.5-pro-latest', # Alternative plus puissante
38
  safety_settings=safety_settings,
39
  system_instruction="Tu es un assistant intelligent. ton but est d'assister au mieux que tu peux. tu as été créé par Aenir et tu t'appelles Mariam"
40
- # Note: 'tools' n'est pas directement utilisé ici comme dans Streamlit,
41
- # la logique de recherche web est gérée manuellement.
42
  )
43
  print("Modèle Gemini chargé.")
44
  except Exception as e:
45
  print(f"Erreur lors de la configuration de Gemini : {e}")
46
- model = None # Empêche l'app de crasher si l'API key est mauvaise
47
-
48
- # --- Fonctions Utilitaires ---
49
 
 
50
  def allowed_file(filename):
51
  return '.' in filename and \
52
  filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
53
 
54
  def perform_web_search(query):
55
- """Effectue une recherche web via l'API Serper."""
56
  conn_key = "9b90a274d9e704ff5b21c0367f9ae1161779b573"
57
  if not conn_key:
58
  print("Clé API SERPER manquante dans .env")
59
  return None
60
  search_url = "https://google.serper.dev/search"
61
- headers = {
62
- 'X-API-KEY': conn_key,
63
- 'Content-Type': 'application/json'
64
- }
65
- payload = json.dumps({"q": query})
66
  try:
67
  response = requests.post(search_url, headers=headers, data=payload, timeout=10)
68
- response.raise_for_status() # Lève une exception pour les codes d'erreur HTTP
69
  data = response.json()
70
  print("Résultats de recherche obtenus.")
71
  return data
@@ -73,232 +60,230 @@ def perform_web_search(query):
73
  print(f"Erreur lors de la recherche web : {e}")
74
  return None
75
  except json.JSONDecodeError as e:
76
- print(f"Erreur lors du décodage de la réponse JSON de Serper : {e}")
77
  print(f"Réponse reçue : {response.text}")
78
  return None
79
 
80
  def format_search_results(data):
81
- """Met en forme les résultats de recherche pour le prompt Gemini."""
82
- if not data:
83
- return "Aucun résultat de recherche trouvé."
84
-
85
- result = "Résultats de recherche web :\n"
86
-
87
- if 'knowledgeGraph' in data:
88
- kg = data['knowledgeGraph']
89
- result += f"\n## Graphe de connaissances :\n"
90
- result += f"### {kg.get('title', '')} ({kg.get('type', '')})\n"
91
- result += f"{kg.get('description', '')}\n"
92
- if 'attributes' in kg:
93
- for attr, value in kg['attributes'].items():
94
- result += f"- {attr}: {value}\n"
95
-
96
- if 'answerBox' in data:
97
- ab = data['answerBox']
98
- result += f"\n## Réponse directe :\n"
99
- result += f"{ab.get('title','')}\n{ab.get('snippet') or ab.get('answer','')}\n"
100
-
101
-
102
- if 'organic' in data and data['organic']:
103
- result += "\n## Résultats principaux :\n"
104
- for i, item in enumerate(data['organic'][:3], 1): # Limite aux 3 premiers
105
- result += f"{i}. **{item.get('title', 'N/A')}**\n"
106
- result += f" {item.get('snippet', 'N/A')}\n"
107
- result += f" Lien : {item.get('link', '#')}\n\n"
108
-
109
- if 'peopleAlsoAsk' in data and data['peopleAlsoAsk']:
110
- result += "## Questions fréquentes :\n"
111
- for i, item in enumerate(data['peopleAlsoAsk'][:2], 1): # Limite aux 2 premières
112
- result += f"{i}. **{item.get('question', 'N/A')}**\n"
113
- # result += f" {item.get('snippet', 'N/A')}\n\n" # Le snippet est souvent redondant
114
-
115
  return result
116
 
117
  def prepare_gemini_history(chat_history):
118
- """Convertit l'historique stocké en session au format attendu par Gemini API."""
119
  gemini_history = []
120
  for message in chat_history:
121
  role = 'user' if message['role'] == 'user' else 'model'
122
- parts = [message['text']]
123
- if message.get('gemini_file'): # Ajoute la référence au fichier si présente
124
- parts.insert(0, message['gemini_file']) # Met le fichier avant le texte
125
- gemini_history.append({'role': role, 'parts': parts})
 
126
  return gemini_history
127
 
 
128
  # --- Routes Flask ---
129
 
130
  @app.route('/', methods=['GET'])
131
  def index():
132
- """Affiche la page principale du chat."""
133
  if 'chat_history' not in session:
134
- session['chat_history'] = [] # Initialise l'historique
135
  if 'web_search' not in session:
136
- session['web_search'] = False # Initialise l'état de la recherche web
 
 
 
 
137
 
138
  return render_template(
139
  'index.html',
140
- chat_history=session['chat_history'],
141
- web_search_active=session['web_search'],
142
- error=session.pop('error', None), # Récupère et supprime l'erreur s'il y en a une
143
- processing_message=session.pop('processing', False) # Indicateur de traitement
144
  )
145
 
146
  @app.route('/chat', methods=['POST'])
147
  def chat():
148
- """Gère la soumission du formulaire de chat."""
149
  if not model:
150
- session['error'] = "Le modèle Gemini n'a pas pu être chargé. Vérifiez la clé API et la configuration."
151
  return redirect(url_for('index'))
152
 
153
  prompt = request.form.get('prompt', '').strip()
154
- # Met à jour l'état de la recherche web depuis la checkbox
155
  session['web_search'] = 'web_search' in request.form
156
  file = request.files.get('file')
157
  uploaded_gemini_file = None
158
- user_message_content = {'role': 'user', 'text': prompt}
 
 
 
 
 
159
 
160
  if not prompt and not file:
161
  session['error'] = "Veuillez entrer un message ou uploader un fichier."
 
162
  return redirect(url_for('index'))
163
 
164
- # --- Gestion de l'upload de fichier ---
165
  if file and file.filename != '':
166
  if allowed_file(file.filename):
167
  try:
168
- # Sécurise le nom du fichier et détermine le chemin complet
169
  filename = secure_filename(file.filename)
170
  filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
171
  file.save(filepath)
172
- print(f"Fichier sauvegardé temporairement : {filepath}")
173
-
174
- # Essayer d'uploader vers Gemini
175
- print("Upload vers Gemini en cours...")
176
- # Détecter le mime type pour l'upload Gemini
177
- mime_type = mimetypes.guess_type(filepath)[0]
178
- if not mime_type:
179
- # Fallback si la détection échoue
180
- mime_type = 'application/octet-stream'
181
- print(f"Impossible de deviner le mime_type pour {filename}, utilisation de {mime_type}")
182
 
 
183
  gemini_file_obj = genai.upload_file(path=filepath, mime_type=mime_type)
184
- uploaded_gemini_file = gemini_file_obj # Garder l'objet fichier pour l'API
185
- user_message_content['gemini_file'] = uploaded_gemini_file # Stocke la référence pour l'historique Gemini
186
- user_message_content['text'] = f"[Fichier: {filename}]\n\n{prompt}" # Modifie le texte affiché
187
- print(f"Fichier {filename} uploadé avec succès vers Gemini. MimeType: {mime_type}")
188
-
189
- # Optionnel: Supprimer le fichier local après upload vers Gemini
190
- # try:
191
- # os.remove(filepath)
192
- # print(f"Fichier temporaire {filepath} supprimé.")
193
- # except OSError as e:
194
- # print(f"Erreur lors de la suppression du fichier temporaire {filepath}: {e}")
195
-
196
 
197
  except Exception as e:
198
- print(f"Erreur lors du traitement ou de l'upload du fichier : {e}")
199
  session['error'] = f"Erreur lors du traitement du fichier : {e}"
200
- # Ne pas bloquer si l'upload échoue, on peut continuer sans le fichier
201
- # return redirect(url_for('index')) # Décommentez si l'upload doit être bloquant
202
  else:
203
  session['error'] = "Type de fichier non autorisé."
 
204
  return redirect(url_for('index'))
205
- elif file and file.filename == '':
206
- # Si un champ fichier existe mais est vide, on l'ignore simplement
207
- pass
208
-
209
- # --- Ajout du message utilisateur à l'historique de session ---
210
- if prompt or uploaded_gemini_file: # N'ajoute que s'il y a du contenu
211
- # Version simplifiée pour l'affichage HTML (sans l'objet gemini_file)
212
- display_history_message = {'role': 'user', 'text': user_message_content['text']}
213
- session['chat_history'].append(display_history_message)
214
- session.modified = True # Important car on modifie une liste dans la session
215
-
216
- # --- Préparation et Envoi à Gemini ---
217
- try:
218
- # Indiquer qu'un traitement est en cours
219
- session['processing'] = True
220
- session.modified = True
221
 
222
- # Construire le prompt final (avec recherche web si activée)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  final_prompt_parts = []
224
  if uploaded_gemini_file:
225
  final_prompt_parts.append(uploaded_gemini_file)
226
 
227
- current_prompt_text = prompt
228
-
229
- # Effectuer la recherche web si activée ET si un prompt textuel existe
230
  if session['web_search'] and prompt:
231
- print("Recherche web activée pour le prompt:", prompt)
232
- # Afficher l'indicateur de recherche
233
- # Note: dans Flask simple, l'indicateur ne sera visible qu'APRES le rechargement.
234
- # Pour un affichage dynamique, il faudrait JS/AJAX.
235
- # On peut passer une variable au template pour l'afficher avant la réponse.
236
- session['processing_web_search'] = True # Indicateur spécifique
237
- session.modified = True
238
-
239
- web_results = perform_web_search(prompt)
240
- if web_results:
241
- formatted_results = format_search_results(web_results)
242
- current_prompt_text = f"Question originale: {prompt}\n\n{formatted_results}\n\nBasé sur ces informations et ta connaissance générale, réponds à la question originale."
243
- print("Prompt modifié avec les résultats de recherche.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  else:
245
- print("Aucun résultat de recherche web obtenu ou erreur.")
246
- # On continue avec le prompt original
247
-
248
- final_prompt_parts.append(current_prompt_text)
249
-
250
- # Préparer l'historique complet pour Gemini
251
- gemini_history = prepare_gemini_history(session['chat_history'][:-1]) # Exclut le dernier message utilisateur (qui est dans final_prompt_parts)
252
 
253
- print(f"\n--- Envoi à Gemini ---")
254
- print(f"Historique envoyé: {len(gemini_history)} messages")
255
- print(f"Parties du prompt actuel: {len(final_prompt_parts)}")
256
- # print(f"Prompt textuel final: {current_prompt_text[:200]}...") # Afficher début du prompt
257
- # print("----------------------\n")
258
 
259
- # Appel à Gemini API
260
- # Utiliser generate_content qui prend l'historique + le nouveau message
261
  full_conversation = gemini_history + [{'role': 'user', 'parts': final_prompt_parts}]
262
  response = model.generate_content(full_conversation)
263
 
264
- # --- Traitement de la réponse ---
265
  response_text = response.text
266
- print(f"\n--- Réponse de Gemini ---")
267
- print(response_text[:500] + ('...' if len(response_text) > 500 else ''))
268
- # print("-------------------------\n")
269
-
270
- # Ajout de la réponse à l'historique de session
271
- session['chat_history'].append({'role': 'assistant', 'text': response_text})
 
 
 
272
  session.modified = True
273
 
274
  except Exception as e:
275
- print(f"Erreur lors de l'appel à Gemini : {e}")
276
- session['error'] = f"Une erreur s'est produite lors de la communication avec l'IA : {e}"
277
- # Si une erreur survient, on retire le message utilisateur qui a causé l'erreur
278
- # pour éviter de le renvoyer indéfiniment.
279
  if session['chat_history'] and session['chat_history'][-1]['role'] == 'user':
280
- session['chat_history'].pop()
281
- session.modified = True
282
-
283
  finally:
284
- # Indiquer que le traitement est terminé
285
  session['processing'] = False
286
- session.pop('processing_web_search', None) # Nettoyer l'indicateur de recherche
287
  session.modified = True
 
288
 
 
289
 
290
- return redirect(url_for('index')) # Redirige vers la page principale pour afficher le nouvel état
291
-
292
  @app.route('/clear', methods=['POST'])
293
  def clear_chat():
294
- """Efface l'historique de la conversation."""
295
- session.pop('chat_history', None) # Supprime l'historique de la session
296
- session.pop('web_search', None) # Réinitialise aussi le toggle web
 
297
  print("Historique de chat effacé.")
298
- return redirect(url_for('index')) # Redirige vers la page d'accueil
 
 
 
 
 
 
 
 
 
 
 
299
 
300
- # --- Démarrage de l'application ---
301
  if __name__ == '__main__':
302
- # Utiliser host='0.0.0.0' pour rendre l'app accessible sur le réseau local
303
- # Debug=True est utile pour le développement, mais à désactiver en production
304
- app.run(debug=True, host='0.0.0.0', port=5001)
 
3
  from flask import Flask, render_template, request, session, redirect, url_for, flash
4
  from dotenv import load_dotenv
5
  import google.generativeai as genai
6
+ import requests
7
  from werkzeug.utils import secure_filename
8
+ import mimetypes
9
 
10
  load_dotenv()
11
 
12
  app = Flask(__name__)
 
13
  app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'une-clé-secrète-par-défaut-pour-dev')
 
14
  UPLOAD_FOLDER = 'temp'
15
+ ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg'}
16
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
17
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
18
 
 
19
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
20
 
21
+ # --- Configuration Gemini (inchangée) ---
22
  try:
23
  genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
 
24
  safety_settings = [
25
  {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
26
  {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
27
  {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"},
28
  {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
29
  ]
 
30
  model = genai.GenerativeModel(
31
+ 'gemini-2.0-flash',
 
32
  safety_settings=safety_settings,
33
  system_instruction="Tu es un assistant intelligent. ton but est d'assister au mieux que tu peux. tu as été créé par Aenir et tu t'appelles Mariam"
 
 
34
  )
35
  print("Modèle Gemini chargé.")
36
  except Exception as e:
37
  print(f"Erreur lors de la configuration de Gemini : {e}")
38
+ model = None
 
 
39
 
40
+ # --- Fonctions Utilitaires (inchangées) ---
41
  def allowed_file(filename):
42
  return '.' in filename and \
43
  filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
44
 
45
  def perform_web_search(query):
 
46
  conn_key = "9b90a274d9e704ff5b21c0367f9ae1161779b573"
47
  if not conn_key:
48
  print("Clé API SERPER manquante dans .env")
49
  return None
50
  search_url = "https://google.serper.dev/search"
51
+ headers = { 'X-API-KEY': conn_key, 'Content-Type': 'application/json' }
52
+ payload = json.dumps({"q": query, "gl": "fr", "hl": "fr"}) # Ajout localisation FR
 
 
 
53
  try:
54
  response = requests.post(search_url, headers=headers, data=payload, timeout=10)
55
+ response.raise_for_status()
56
  data = response.json()
57
  print("Résultats de recherche obtenus.")
58
  return data
 
60
  print(f"Erreur lors de la recherche web : {e}")
61
  return None
62
  except json.JSONDecodeError as e:
63
+ print(f"Erreur lors du décodage JSON de Serper : {e}")
64
  print(f"Réponse reçue : {response.text}")
65
  return None
66
 
67
  def format_search_results(data):
68
+ if not data: return "Aucun résultat de recherche trouvé."
69
+ result = "Résultats de recherche web pertinents :\n"
70
+ # (Formatage légèrement simplifié pour clarté)
71
+ if kg := data.get('knowledgeGraph'):
72
+ result += f"\n## {kg.get('title', '')} ({kg.get('type', '')})\n{kg.get('description', '')}\n"
73
+ if ab := data.get('answerBox'):
74
+ result += f"\n## Réponse rapide :\n{ab.get('title','')}\n{ab.get('snippet') or ab.get('answer','')}\n"
75
+ if org := data.get('organic'):
76
+ result += "\n## Principaux résultats :\n"
77
+ for i, item in enumerate(org[:3], 1):
78
+ result += f"{i}. {item.get('title', 'N/A')}\n {item.get('snippet', 'N/A')}\n [{item.get('link', '#')}]\n"
79
+ # People Also Ask peut être bruyant, on peut l'omettre pour le prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  return result
81
 
82
  def prepare_gemini_history(chat_history):
 
83
  gemini_history = []
84
  for message in chat_history:
85
  role = 'user' if message['role'] == 'user' else 'model'
86
+ # Utilise la référence stockée si elle existe
87
+ parts = [message.get('gemini_file')] if message.get('gemini_file') else []
88
+ parts.append(message['text_for_gemini']) # Utilise le texte destiné à Gemini
89
+ # Filtrer les parts None qui pourraient survenir si gemini_file était None
90
+ gemini_history.append({'role': role, 'parts': [p for p in parts if p]})
91
  return gemini_history
92
 
93
+
94
  # --- Routes Flask ---
95
 
96
  @app.route('/', methods=['GET'])
97
  def index():
 
98
  if 'chat_history' not in session:
99
+ session['chat_history'] = []
100
  if 'web_search' not in session:
101
+ session['web_search'] = False
102
+
103
+ # Récupérer l'état de traitement et l'erreur pour les afficher
104
+ processing = session.get('processing', False)
105
+ error = session.pop('error', None) # Utilise pop pour ne l'afficher qu'une fois
106
 
107
  return render_template(
108
  'index.html',
109
+ chat_history=session.get('chat_history', []),
110
+ web_search_active=session.get('web_search', False),
111
+ error=error,
112
+ processing_message=processing # Passer l'état de traitement
113
  )
114
 
115
  @app.route('/chat', methods=['POST'])
116
  def chat():
 
117
  if not model:
118
+ session['error'] = "Le modèle Gemini n'a pas pu être chargé."
119
  return redirect(url_for('index'))
120
 
121
  prompt = request.form.get('prompt', '').strip()
 
122
  session['web_search'] = 'web_search' in request.form
123
  file = request.files.get('file')
124
  uploaded_gemini_file = None
125
+ file_display_name = None # Pour l'affichage
126
+
127
+ # Marquer le début du traitement DANS la session
128
+ session['processing'] = True
129
+ session['processing_web_search'] = False # Reset au début
130
+ session.modified = True # Sauvegarder la session maintenant
131
 
132
  if not prompt and not file:
133
  session['error'] = "Veuillez entrer un message ou uploader un fichier."
134
+ session['processing'] = False # Annuler le traitement
135
  return redirect(url_for('index'))
136
 
137
+ # --- Gestion Upload ---
138
  if file and file.filename != '':
139
  if allowed_file(file.filename):
140
  try:
 
141
  filename = secure_filename(file.filename)
142
  filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
143
  file.save(filepath)
144
+ print(f"Fichier sauvegardé: {filepath}")
145
+ mime_type = mimetypes.guess_type(filepath)[0] or 'application/octet-stream'
 
 
 
 
 
 
 
 
146
 
147
+ print("Upload vers Gemini...")
148
  gemini_file_obj = genai.upload_file(path=filepath, mime_type=mime_type)
149
+ uploaded_gemini_file = gemini_file_obj
150
+ file_display_name = filename # Garder le nom pour affichage
151
+ print(f"Fichier {filename} uploadé. MimeType: {mime_type}")
152
+ # Optionnel: Supprimer après upload
153
+ # os.remove(filepath)
 
 
 
 
 
 
 
154
 
155
  except Exception as e:
156
+ print(f"Erreur upload fichier : {e}")
157
  session['error'] = f"Erreur lors du traitement du fichier : {e}"
158
+ # Ne pas arrêter le traitement, continuer sans le fichier si erreur upload
 
159
  else:
160
  session['error'] = "Type de fichier non autorisé."
161
+ session['processing'] = False # Annuler le traitement
162
  return redirect(url_for('index'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
+ # --- Préparation Message Utilisateur ---
165
+ # Texte à afficher dans le chat
166
+ display_text = prompt
167
+ if file_display_name:
168
+ display_text = f"[Fichier joint : {file_display_name}]\n\n{prompt}" if prompt else f"[Fichier joint : {file_display_name}]"
169
+
170
+ # Texte à envoyer à Gemini (peut être différent si recherche web)
171
+ text_for_gemini = prompt
172
+
173
+ # Ajouter à l'historique de session (pour affichage) SEULEMENT s'il y a du contenu
174
+ if display_text:
175
+ # Garder une trace de l'objet fichier Gemini et du texte original pour l'API
176
+ session['chat_history'].append({
177
+ 'role': 'user',
178
+ 'text': display_text, # Texte pour affichage HTML
179
+ 'text_for_gemini': text_for_gemini, # Texte initial pour l'API Gemini
180
+ 'gemini_file': uploaded_gemini_file # Référence à l'objet fichier si uploadé
181
+ })
182
+ session.modified = True
183
+
184
+ # --- Logique principale (Recherche Web + Gemini) ---
185
+ try:
186
  final_prompt_parts = []
187
  if uploaded_gemini_file:
188
  final_prompt_parts.append(uploaded_gemini_file)
189
 
190
+ # Recherche Web si activée ET prompt textuel existe
 
 
191
  if session['web_search'] and prompt:
192
+ print("Activation recherche web...")
193
+ session['processing_web_search'] = True # Indiquer que la recherche est active
194
+ session.modified = True
195
+ # !! Important: Forcer la sauvegarde de session AVANT l'appel bloquant
196
+ # Ceci est un workaround car Flask sauvegarde normalement en fin de requête.
197
+ # Pour une vraie MAJ live, il faudrait AJAX/WebSockets.
198
+ from flask.sessions import SessionInterface
199
+ app.session_interface.save_session(app, session, Response()) # Pseudo-réponse
200
+
201
+ web_results = perform_web_search(prompt)
202
+ if web_results:
203
+ formatted_results = format_search_results(web_results)
204
+ text_for_gemini = f"Question originale: {prompt}\n\n{formatted_results}\n\nRéponds à la question originale en te basant sur ces informations et ta connaissance."
205
+ print("Prompt enrichi avec recherche web.")
206
+ else:
207
+ print("Pas de résultats web ou erreur.")
208
+ text_for_gemini = prompt # Garde le prompt original
209
+ session['processing_web_search'] = False # Recherche terminée
210
+
211
+ # Ajouter le texte (original ou enrichi) aux parts
212
+ if text_for_gemini: # S'assurer qu'on ajoute pas une string vide
213
+ final_prompt_parts.append(text_for_gemini)
214
+
215
+ # Préparer l'historique pour Gemini en utilisant les données stockées
216
+ # On prend tout sauf le dernier message utilisateur qui est en cours de traitement
217
+ gemini_history = prepare_gemini_history(session['chat_history'][:-1])
218
+
219
+ print(f"\n--- Envoi à Gemini ({len(gemini_history)} hist + {len(final_prompt_parts)} new parts) ---")
220
+
221
+ # Appel API Gemini
222
+ if not final_prompt_parts:
223
+ # Cas où seul un fichier a été envoyé sans prompt textuel,
224
+ # et la recherche web n'était pas activée ou n'a rien retourné.
225
+ # Il faut quand même envoyer qqchose, par ex., demander de décrire le fichier.
226
+ if uploaded_gemini_file:
227
+ final_prompt_parts.append("Décris le contenu de ce fichier.")
228
  else:
229
+ # Ne devrait pas arriver vu les checks précédents, mais par sécurité
230
+ raise ValueError("Tentative d'envoyer une requête vide à Gemini.")
 
 
 
 
 
231
 
 
 
 
 
 
232
 
 
 
233
  full_conversation = gemini_history + [{'role': 'user', 'parts': final_prompt_parts}]
234
  response = model.generate_content(full_conversation)
235
 
236
+ # --- Traitement Réponse ---
237
  response_text = response.text
238
+ print(f"--- Réponse Gemini reçue ---")
239
+
240
+ # Ajouter la réponse à l'historique (version simple pour affichage)
241
+ session['chat_history'].append({
242
+ 'role': 'assistant',
243
+ 'text': response_text,
244
+ 'text_for_gemini': response_text # Pour symétrie, même si on ne réutilise pas directement
245
+ # Pas de 'gemini_file' pour les réponses du modèle
246
+ })
247
  session.modified = True
248
 
249
  except Exception as e:
250
+ print(f"Erreur lors de l'appel à Gemini ou traitement : {e}")
251
+ session['error'] = f"Une erreur s'est produite : {e}"
252
+ # En cas d'erreur, retirer le dernier message utilisateur de l'historique
253
+ # pour éviter boucle d'erreur si le prompt est problématique.
254
  if session['chat_history'] and session['chat_history'][-1]['role'] == 'user':
255
+ session['chat_history'].pop()
256
+ session.modified = True
 
257
  finally:
258
+ # Marquer la fin du traitement DANS la session
259
  session['processing'] = False
260
+ session.pop('processing_web_search', None) # Nettoyer au cas
261
  session.modified = True
262
+ # Pas besoin de Response() ici, la sauvegarde se fera avec le redirect
263
 
264
+ return redirect(url_for('index'))
265
 
266
+ # Ajouter une route pour effacer la conversation
 
267
  @app.route('/clear', methods=['POST'])
268
  def clear_chat():
269
+ session.pop('chat_history', None)
270
+ session.pop('web_search', None)
271
+ session.pop('processing', None) # Nettoyer aussi l'état de process
272
+ session.pop('error', None)
273
  print("Historique de chat effacé.")
274
+ flash("Conversation effacée.", "info") # Message feedback (optionnel)
275
+ return redirect(url_for('index'))
276
+
277
+ # Classe Response factice pour sauvegarde session précoce (workaround)
278
+ # Attention: N'est PAS une vraie réponse HTTP. A utiliser avec prudence.
279
+ class Response:
280
+ def __init__(self):
281
+ self.headers = {}
282
+ def set_cookie(self, key, value, **kwargs):
283
+ # Potentiellement stocker les cookies si nécessaire, mais ici on ignore
284
+ pass
285
+
286
 
 
287
  if __name__ == '__main__':
288
+ # Utiliser un port différent si 5000 est pris
289
+ app.run(debug=True, host='0.0.0.0', port=5002)