Docfile commited on
Commit
c23cd74
·
verified ·
1 Parent(s): a5874c3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +378 -138
app.py CHANGED
@@ -6,202 +6,442 @@ import http.client
6
  import json
7
  from werkzeug.utils import secure_filename
8
  import uuid
 
 
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  app = Flask(__name__)
11
- app.secret_key = os.urandom(24)
12
- load_dotenv()
 
 
 
 
13
 
14
  # Configure Google AI
15
- genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
 
 
 
16
 
17
- # Safety settings
 
18
  safety_settings = [
19
- {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
20
- {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
21
- {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"},
22
- {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
23
  ]
24
 
25
- # System prompt
 
 
 
 
 
 
 
 
 
26
  SYSTEM_PROMPT = """
27
- # Prompt System pour Mariam, IA conçu par youssouf
28
- [Your existing system prompt content here]
 
 
 
 
 
29
  """
30
 
31
- # Initialize Gemini model
32
- model = genai.GenerativeModel(
33
- 'gemini-2.0-flash',
34
- safety_settings=safety_settings,
35
- system_instruction=SYSTEM_PROMPT
36
- )
 
 
 
 
 
37
 
38
- # Stockage des sessions de chat
 
39
  chat_sessions = {}
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  def perform_web_search(query):
 
 
 
 
 
42
  conn = http.client.HTTPSConnection("google.serper.dev")
43
- payload = json.dumps({"q": query})
 
44
  headers = {
45
- 'X-API-KEY': '9b90a274d9e704ff5b21c0367f9ae1161779b573',
46
  'Content-Type': 'application/json'
47
  }
48
  try:
 
49
  conn.request("POST", "/search", payload, headers)
50
  res = conn.getresponse()
51
- data = json.loads(res.read().decode("utf-8"))
52
- return data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  except Exception as e:
54
- print(f"Web search error: {e}")
55
- return None
 
56
  finally:
57
  conn.close()
58
 
59
- def format_search_results(data):
60
- if not data:
61
- return "Aucun résultat trouvé"
62
-
63
- result = []
64
- if 'knowledgeGraph' in data:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  kg = data['knowledgeGraph']
66
- result.append({
67
- 'type': 'knowledge',
68
- 'title': kg.get('title', ''),
69
- 'description': kg.get('description', ''),
70
- 'category': kg.get('type', '')
71
- })
72
-
73
- if 'organic' in data:
74
- for item in data['organic'][:3]:
75
- result.append({
76
- 'type': 'organic',
77
- 'title': item['title'],
78
- 'snippet': item['snippet'],
79
- 'link': item['link']
80
- })
81
-
82
- return result
83
-
84
- UPLOAD_FOLDER = 'temp'
85
- ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'pdf', 'txt', 'mp3', 'mp4'}
86
-
87
- os.makedirs(UPLOAD_FOLDER, exist_ok=True)
88
 
89
- def allowed_file(filename):
90
- return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
91
 
92
  @app.route('/')
93
  def home():
 
 
94
  if 'session_id' not in session:
95
- session['session_id'] = str(uuid.uuid4())
96
- session['messages'] = []
97
- session['uploaded_files'] = [] # initialisation de la liste des fichiers uploadés
98
- return render_template('index.html', messages=session.get('messages', []))
 
 
 
 
 
 
 
 
 
 
 
99
 
100
  @app.route('/send_message', methods=['POST'])
101
  def send_message():
 
 
 
 
 
 
 
 
 
102
  try:
103
  data = request.json
104
- user_message = data.get('message')
105
  web_search_enabled = data.get('web_search', False)
106
-
107
- if not user_message:
108
- return jsonify({'error': 'No message provided'}), 400
109
-
110
- # Initialiser la session si nécessaire
111
- if 'session_id' not in session:
112
- session['session_id'] = str(uuid.uuid4())
113
- session['messages'] = []
114
- session['uploaded_files'] = []
115
-
116
- session_id = session['session_id']
117
-
118
- # Initialiser la session de chat si nécessaire
119
- if session_id not in chat_sessions:
120
- chat_sessions[session_id] = model.start_chat(history=[])
121
-
122
- # Intégration des fichiers uploadés dans le prompt
123
- prompt = ""
124
- if 'uploaded_files' in session and session['uploaded_files']:
125
- prompt += "Fichiers uploadés:\n"
126
- for file_info in session['uploaded_files']:
127
- prompt += f"- Nom: {file_info['filename']}, Référence: {file_info['file_id']}\n"
128
- prompt += "\n"
129
-
130
- prompt += f"Question: {user_message}"
131
-
132
- # Réaliser une recherche web si activée
133
- if web_search_enabled:
134
- web_results = perform_web_search(user_message)
135
- if web_results:
136
- formatted_results = format_search_results(web_results)
137
- prompt += f"\n\nRésultats de recherche web:\n{formatted_results}\n\nPourrais-tu analyser ces informations et me donner une réponse complète?"
138
-
139
- # Envoi du message à Gemini
140
- response = chat_sessions[session_id].send_message(prompt)
141
-
142
- # Mise à jour de l'historique de chat dans la session
143
- if 'messages' not in session:
144
- session['messages'] = []
145
-
146
- current_messages = session['messages']
147
- current_messages.append({
148
- 'role': 'user',
149
- 'content': prompt
150
- })
151
- current_messages.append({
152
- 'role': 'assistant',
153
- 'content': response.text
154
- })
155
- session['messages'] = current_messages
156
 
157
- # Optionnel : vider la liste des fichiers uploadés après utilisation
158
- session['uploaded_files'] = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
- return jsonify({
161
- 'response': response.text
162
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
  except Exception as e:
165
- print(f"Error in send_message: {e}")
166
- return jsonify({'error': str(e)}), 500
 
 
167
 
168
  @app.route('/upload', methods=['POST'])
169
  def upload_file():
 
 
 
 
 
 
 
 
170
  if 'file' not in request.files:
171
- return jsonify({'error': 'No file part'}), 400
172
-
 
173
  file = request.files['file']
174
  if file.filename == '':
175
- return jsonify({'error': 'No selected file'}), 400
176
-
 
177
  if file and allowed_file(file.filename):
178
  filename = secure_filename(file.filename)
179
- filepath = os.path.join(UPLOAD_FOLDER, filename)
180
- file.save(filepath)
181
-
 
182
  try:
183
- gemini_file = genai.upload_file(filepath)
184
- # Stocker la référence du fichier dans la session
185
- if 'uploaded_files' not in session:
186
- session['uploaded_files'] = []
187
- session['uploaded_files'].append({
188
- 'filename': filename,
189
- 'file_id': gemini_file.get('id') if isinstance(gemini_file, dict) else gemini_file
190
- })
191
- return jsonify({'success': True, 'filename': filename})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  except Exception as e:
193
- return jsonify({'error': str(e)}), 500
194
-
195
- return jsonify({'error': 'Invalid file type'}), 400
 
 
 
 
 
 
 
 
 
 
 
196
 
197
  @app.route('/clear_chat', methods=['POST'])
198
  def clear_chat():
199
- session_id = session.get('session_id')
 
 
 
 
 
 
 
 
200
  if session_id in chat_sessions:
201
  del chat_sessions[session_id]
 
 
 
202
  session['messages'] = []
203
- session['uploaded_files'] = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  return jsonify({'success': True})
205
 
 
 
206
  if __name__ == '__main__':
207
- app.run()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  import json
7
  from werkzeug.utils import secure_filename
8
  import uuid
9
+ from PIL import Image # Required for checking image files if needed, or directly by genai
10
+ import traceback # For detailed error logging
11
 
12
+ # --- Configuration ---
13
+ load_dotenv() # Charge les variables depuis le fichier .env
14
+
15
+ # Check for essential environment variables
16
+ GEMINI_API_KEY = os.getenv("GOOGLE_API_KEY")
17
+ SERPER_API_KEY = "9b90a274d9e704ff5b21c0367f9ae1161779b573" # Clé pour la recherche web (optionnelle)
18
+ FLASK_SECRET_KEY = "jdjdjdjdj" # Clé secrète pour les sessions Flask
19
+
20
+ # Validation critique des clés au démarrage
21
+ if not GEMINI_API_KEY:
22
+ raise ValueError("ERREUR CRITIQUE: La variable d'environnement GEMINI_API_KEY n'est pas définie. Vérifiez votre fichier .env")
23
+ if not FLASK_SECRET_KEY:
24
+ raise ValueError("ERREUR CRITIQUE: La variable d'environnement FLASK_SECRET_KEY n'est pas définie. Vérifiez votre fichier .env")
25
+ if not SERPER_API_KEY:
26
+ print("AVERTISSEMENT: SERPER_API_KEY n'est pas définie dans .env. La fonction de recherche web sera désactivée.")
27
+
28
+ # Configure Flask App
29
  app = Flask(__name__)
30
+ app.secret_key = FLASK_SECRET_KEY
31
+ app.config['UPLOAD_FOLDER'] = 'temp_uploads' # Dossier pour les uploads temporaires
32
+ app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # Limite la taille des uploads à 100 Mo
33
+
34
+ # Assurer l'existence du dossier d'upload
35
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
36
 
37
  # Configure Google AI
38
+ try:
39
+ genai.configure(api_key=GEMINI_API_KEY)
40
+ except Exception as e:
41
+ raise RuntimeError(f"Échec de la configuration de l'API Google AI: {e}. Vérifiez votre GEMINI_API_KEY.")
42
 
43
+
44
+ # Paramètres de Sécurité Gemini (Ajustez si nécessaire)
45
  safety_settings = [
46
+ {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
47
+ {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
48
+ {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
49
+ {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
50
  ]
51
 
52
+ # Configuration de la Génération Gemini
53
+ generation_config = {
54
+ "temperature": 0.7,
55
+ "top_p": 0.95,
56
+ "top_k": 64,
57
+ "max_output_tokens": 8192,
58
+ "response_mime_type": "text/plain", # Demande une réponse texte simple
59
+ }
60
+
61
+ # Prompt Système (Instructions pour l'IA)
62
  SYSTEM_PROMPT = """
63
+ Vous êtes Mariam, une assistante IA conversationnelle polyvalente conçue par Youssouf.
64
+ Votre objectif est d'aider les utilisateurs de manière informative, créative et engageante.
65
+ Répondez en français. Soyez concise mais complète.
66
+ Si l'utilisateur télécharge des fichiers, prenez-les en compte dans votre réponse si pertinent pour la question posée.
67
+ Si la recherche web est activée et que des résultats sont fournis via le prompt, utilisez-les pour enrichir votre réponse en citant brièvement les points clés trouvés.
68
+ Formattez vos réponses en Markdown lorsque cela améliore la lisibilité (listes, blocs de code, gras, italique, etc.).
69
+ Ne mentionnez pas explicitement les "Résultats de recherche web" dans votre réponse finale à moins que ce ne soit naturel dans le contexte. Intégrez l'information trouvée.
70
  """
71
 
72
+ # Initialisation du Modèle Gemini
73
+ try:
74
+ model = genai.GenerativeModel(
75
+ model_name="gemini-1.5-flash-latest", # Utilisation de la dernière version flash
76
+ safety_settings=safety_settings,
77
+ generation_config=generation_config,
78
+ system_instruction=SYSTEM_PROMPT
79
+ )
80
+ print("Modèle Gemini initialisé avec succès.")
81
+ except Exception as e:
82
+ raise RuntimeError(f"Échec de l'initialisation du modèle Gemini: {e}")
83
 
84
+
85
+ # Stockage en mémoire des sessions de chat (Pour la production, envisagez une solution persistante comme Redis ou une base de données)
86
  chat_sessions = {}
87
 
88
+ # Extensions de fichiers autorisées pour l'upload
89
+ ALLOWED_EXTENSIONS = {
90
+ 'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'webp', # Texte et Images
91
+ 'heic', 'heif', # Formats d'image Apple
92
+ 'mp3', 'wav', 'ogg', 'flac', # Audio
93
+ 'mp4', 'mov', 'avi', 'mkv', 'webm' # Vidéo
94
+ }
95
+
96
+ def allowed_file(filename):
97
+ """Vérifie si l'extension du fichier est autorisée."""
98
+ return '.' in filename and \
99
+ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
100
+
101
+ # --- Fonctions Utilitaires ---
102
+
103
+ def get_chat_session(session_id):
104
+ """Récupère ou crée une session de chat Gemini pour l'ID de session Flask donné."""
105
+ if session_id not in chat_sessions:
106
+ print(f"Création d'une nouvelle session de chat Gemini pour Flask session ID: {session_id}")
107
+ # L'historique est géré dynamiquement lors de l'envoi des messages
108
+ chat_sessions[session_id] = model.start_chat(history=[])
109
+ return chat_sessions[session_id]
110
+
111
  def perform_web_search(query):
112
+ """Effectue une recherche web via l'API Serper.dev."""
113
+ if not SERPER_API_KEY:
114
+ print("Recherche web ignorée : SERPER_API_KEY non configurée.")
115
+ return None # Retourne None si la clé n'est pas dispo
116
+
117
  conn = http.client.HTTPSConnection("google.serper.dev")
118
+ # Payload pour la recherche en français
119
+ payload = json.dumps({"q": query, "gl": "fr", "hl": "fr"})
120
  headers = {
121
+ 'X-API-KEY': SERPER_API_KEY,
122
  'Content-Type': 'application/json'
123
  }
124
  try:
125
+ print(f"Serper: Envoi de la requête pour '{query}'...")
126
  conn.request("POST", "/search", payload, headers)
127
  res = conn.getresponse()
128
+ data_bytes = res.read()
129
+ data_str = data_bytes.decode("utf-8")
130
+ print(f"Serper: Réponse reçue - Statut: {res.status}")
131
+
132
+ if res.status == 200:
133
+ print("Serper: Recherche réussie.")
134
+ return json.loads(data_str)
135
+ else:
136
+ print(f"Serper: Erreur API - Statut {res.status}, Réponse: {data_str}")
137
+ return {"error": f"L'API de recherche a échoué avec le statut {res.status}"}
138
+ except http.client.HTTPException as http_err:
139
+ print(f"Erreur HTTP lors de la recherche web: {http_err}")
140
+ return {"error": f"Erreur de connexion HTTP: {str(http_err)}"}
141
+ except json.JSONDecodeError as json_err:
142
+ print(f"Erreur de décodage JSON de la réponse Serper: {json_err}")
143
+ print(f"Réponse brute reçue: {data_str}")
144
+ return {"error": "Impossible de lire la réponse de l'API de recherche."}
145
  except Exception as e:
146
+ print(f"Erreur inattendue lors de la recherche web: {e}")
147
+ traceback.print_exc()
148
+ return {"error": f"Exception lors de la recherche web: {str(e)}"}
149
  finally:
150
  conn.close()
151
 
152
+ def format_search_results_for_prompt(data):
153
+ """Formate les résultats de recherche de manière concise pour les injecter dans le prompt de l'IA."""
154
+ if not data or data.get("searchParameters", {}).get("q") is None:
155
+ # Si data est vide ou ne semble pas être une réponse valide
156
+ print("Formatage recherche: Données invalides ou vides reçues.")
157
+ return "La recherche web n'a pas retourné de résultats exploitables."
158
+ if "error" in data:
159
+ print(f"Formatage recherche: Erreur détectée dans les données - {data['error']}")
160
+ return f"Note : La recherche web a rencontré une erreur ({data['error']})."
161
+
162
+ results = []
163
+ query = data.get("searchParameters", {}).get("q", "Terme inconnu")
164
+ results.append(f"Résultats de recherche web pour '{query}':")
165
+
166
+ # Boîte de réponse / Knowledge Graph (si présents)
167
+ if 'answerBox' in data:
168
+ ab = data['answerBox']
169
+ answer = ab.get('answer') or ab.get('snippet') or ab.get('title')
170
+ if answer:
171
+ results.append(f"- Réponse directe trouvée : {answer}")
172
+ elif 'knowledgeGraph' in data:
173
  kg = data['knowledgeGraph']
174
+ description = kg.get('description')
175
+ if description:
176
+ results.append(f"- Info (Knowledge Graph) : {kg.get('title', '')} - {description}")
177
+
178
+ # Résultats Organiques (naturels)
179
+ if 'organic' in data and data['organic']:
180
+ results.append("- Résultats principaux :")
181
+ for i, item in enumerate(data['organic'][:3], 1): # Limite aux 3 premiers
182
+ title = item.get('title', 'Sans titre')
183
+ snippet = item.get('snippet', 'Pas de description')
184
+ link = item.get('link', '#')
185
+ results.append(f" {i}. {title}: {snippet} (Source: {link})")
186
+ elif not results: # Si ni answerbox/KG ni organic n'ont donné qqch
187
+ return f"Aucun résultat de recherche web pertinent trouvé pour '{query}'."
188
+
189
+
190
+ print(f"Formatage recherche: {len(results)-1} éléments formatés pour le prompt.")
191
+ return "\n".join(results)
 
 
 
 
192
 
193
+
194
+ # --- Routes Flask ---
195
 
196
  @app.route('/')
197
  def home():
198
+ """Affiche l'interface de chat principale."""
199
+ # Initialise la session Flask si elle n'existe pas
200
  if 'session_id' not in session:
201
+ session_id = str(uuid.uuid4())
202
+ session['session_id'] = session_id
203
+ session['messages'] = [] # Historique pour l'affichage client
204
+ session['uploaded_files_gemini'] = [] # Stocke les objets File retournés par genai.upload_file
205
+ session.modified = True # Marque la session comme modifiée
206
+ print(f"Nouvelle session Flask créée: {session_id}")
207
+ else:
208
+ # Assure que les clés nécessaires existent même si la session existe déjà
209
+ if 'messages' not in session: session['messages'] = []
210
+ if 'uploaded_files_gemini' not in session: session['uploaded_files_gemini'] = []
211
+
212
+
213
+ # Récupère les messages de la session pour les passer au template
214
+ messages_for_template = session.get('messages', [])
215
+ return render_template('index.html', messages=messages_for_template)
216
 
217
  @app.route('/send_message', methods=['POST'])
218
  def send_message():
219
+ """Gère l'envoi d'un message à l'IA et retourne sa réponse."""
220
+ # Vérifie si la session utilisateur est valide
221
+ if 'session_id' not in session:
222
+ print("Erreur send_message: ID de session manquant.")
223
+ return jsonify({'error': 'Session expirée ou invalide. Veuillez rafraîchir la page.'}), 400
224
+
225
+ session_id = session['session_id']
226
+ print(f"\n--- Requête /send_message reçue (Session: {session_id}) ---")
227
+
228
  try:
229
  data = request.json
230
+ user_message_text = data.get('message', '').strip()
231
  web_search_enabled = data.get('web_search', False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
 
233
+ # Vérifie si le message est vide (sauf si des fichiers sont attachés)
234
+ uploaded_files = session.get('uploaded_files_gemini', [])
235
+ if not user_message_text and not uploaded_files:
236
+ print("Avertissement send_message: Message vide et aucun fichier attaché.")
237
+ return jsonify({'error': 'Veuillez entrer un message ou joindre un fichier.'}), 400
238
+
239
+ # Récupère ou crée la session de chat Gemini associée
240
+ chat_gemini = get_chat_session(session_id)
241
+
242
+ # --- Préparation du contenu pour Gemini ---
243
+ prompt_parts = [] # Liste pour contenir texte, fichiers, résultats web
244
+
245
+ # 1. Ajouter les fichiers uploadés (depuis la session)
246
+ # On utilise directement les objets File retournés par genai.upload_file
247
+ if uploaded_files:
248
+ prompt_parts.extend(uploaded_files) # Ajoute les objets File à la liste
249
+ file_names = [f.display_name for f in uploaded_files] # Noms pour log
250
+ print(f"Ajout au prompt des fichiers Gemini: {', '.join(file_names)}")
251
+ # Vider la liste des fichiers après les avoir ajoutés au prompt de CE message
252
+ # Permet d'associer les fichiers au message en cours uniquement
253
+ session['uploaded_files_gemini'] = []
254
+ session.modified = True
255
+ print("Liste des fichiers uploadés vidée pour le prochain message.")
256
+
257
 
258
+ # 2. Ajouter les résultats de recherche web (si activé et clé API dispo)
259
+ search_prompt_text = "" # Texte à ajouter au prompt textuel
260
+ if web_search_enabled and SERPER_API_KEY:
261
+ print(f"Recherche web activée pour: '{user_message_text}'")
262
+ web_results_data = perform_web_search(user_message_text)
263
+ if web_results_data:
264
+ formatted_results = format_search_results_for_prompt(web_results_data)
265
+ search_prompt_text = f"\n\n--- Informations issues de la recherche web ---\n{formatted_results}\n--- Fin des informations de recherche ---\n"
266
+ print("Résultats de recherche formatés ajoutés au contexte.")
267
+ else:
268
+ search_prompt_text = "\n\n(Note: La recherche web a été tentée mais n'a pas retourné de résultats ou a échoué.)\n"
269
+ print("Aucun résultat de recherche web ou échec.")
270
+
271
+
272
+ # 3. Ajouter le message texte de l'utilisateur (incluant le contexte de recherche si existant)
273
+ final_user_text = user_message_text + search_prompt_text
274
+ prompt_parts.append(final_user_text) # Ajoute le texte (potentiellement enrichi)
275
+ print(f"Texte final envoyé à Gemini:\n{final_user_text[:500]}...") # Log tronqué
276
+
277
+ # --- Envoi à l'API Gemini ---
278
+ print(f"Envoi de {len(prompt_parts)} partie(s) à l'API Gemini...")
279
+ ai_response_text = ""
280
+ try:
281
+ # Envoie la liste complète des parties (fichiers + texte)
282
+ response = chat_gemini.send_message(prompt_parts)
283
+ ai_response_text = response.text # Récupère le texte de la réponse
284
+ print("Réponse reçue de Gemini.")
285
+
286
+ # Vérifier si la réponse a été bloquée par les filtres de sécurité
287
+ if response.prompt_feedback.block_reason:
288
+ block_reason = response.prompt_feedback.block_reason
289
+ print(f"AVERTISSEMENT: Réponse bloquée par Gemini. Raison: {block_reason}")
290
+ ai_response_text = f"⚠️ Ma réponse a été bloquée en raison des filtres de sécurité (Raison: {block_reason}). Veuillez reformuler votre demande."
291
+
292
+ except Exception as e:
293
+ print(f"ERREUR lors de l'appel à Gemini API: {e}")
294
+ traceback.print_exc() # Log détaillé de l'erreur serveur
295
+ # Essaye de donner une erreur plus spécifique si possible
296
+ error_details = str(e)
297
+ ai_response_text = f"❌ Désolé, une erreur est survenue lors de la communication avec l'IA. Détails techniques: {error_details}"
298
+
299
+
300
+ # --- Mise à jour de l'historique de session Flask (pour l'affichage) ---
301
+ # Stocke le message *original* de l'utilisateur et la réponse de l'IA
302
+ current_messages = session.get('messages', [])
303
+ # !! Ne pas stocker le prompt enrichi dans l'historique affiché !!
304
+ current_messages.append({'role': 'user', 'content': user_message_text}) # Message original
305
+ current_messages.append({'role': 'assistant', 'content': ai_response_text}) # Réponse IA
306
+ session['messages'] = current_messages
307
+ session.modified = True
308
+ print("Historique de session Flask mis à jour.")
309
+
310
+ # --- Retourner la réponse de l'IA au client ---
311
+ print(f"--- Fin Requête /send_message (Session: {session_id}) ---")
312
+ return jsonify({'response': ai_response_text})
313
 
314
  except Exception as e:
315
+ print(f"ERREUR Inattendue dans /send_message: {e}")
316
+ traceback.print_exc()
317
+ return jsonify({'error': f'Erreur interne du serveur: {str(e)}'}), 500
318
+
319
 
320
  @app.route('/upload', methods=['POST'])
321
  def upload_file():
322
+ """Gère l'upload de fichiers, les enregistre temporairement et les prépare pour Gemini."""
323
+ if 'session_id' not in session:
324
+ print("Erreur upload: ID de session manquant.")
325
+ return jsonify({'error': 'Session expirée ou invalide. Veuillez rafraîchir la page.'}), 400
326
+
327
+ session_id = session['session_id']
328
+ print(f"\n--- Requête /upload reçue (Session: {session_id}) ---")
329
+
330
  if 'file' not in request.files:
331
+ print("Erreur upload: 'file' manquant dans request.files.")
332
+ return jsonify({'error': 'Aucun fichier trouvé dans la requête.'}), 400
333
+
334
  file = request.files['file']
335
  if file.filename == '':
336
+ print("Erreur upload: Nom de fichier vide.")
337
+ return jsonify({'error': 'Aucun fichier sélectionné.'}), 400
338
+
339
  if file and allowed_file(file.filename):
340
  filename = secure_filename(file.filename)
341
+ # Crée un chemin de sauvegarde temporaire unique (évite écrasement si même nom)
342
+ temp_filename = f"{session_id}_{uuid.uuid4().hex}_{filename}"
343
+ save_path = os.path.join(app.config['UPLOAD_FOLDER'], temp_filename)
344
+
345
  try:
346
+ print(f"Sauvegarde du fichier '{filename}' vers '{save_path}'...")
347
+ file.save(save_path)
348
+ print(f"Fichier sauvegardé localement.")
349
+
350
+ # Uploader le fichier vers Google AI Studio (API Gemini)
351
+ print(f"Upload de '{filename}' vers Google AI Studio...")
352
+ # display_name est optionnel mais utile pour le débogage ou l'affichage
353
+ gemini_file_object = genai.upload_file(path=save_path, display_name=filename)
354
+ print(f"Fichier uploadé vers Gemini. Référence obtenue: {gemini_file_object.name}") # L'objet a un attribut 'name'
355
+
356
+ # Stocker l'objet File retourné par Gemini dans la session Flask
357
+ # C'est cet objet qui sera utilisé directement dans send_message
358
+ current_uploads = session.get('uploaded_files_gemini', [])
359
+ current_uploads.append(gemini_file_object)
360
+ session['uploaded_files_gemini'] = current_uploads
361
+ session.modified = True
362
+ print(f"Objet Fichier Gemini ajouté à la session Flask (Total: {len(current_uploads)}).")
363
+
364
+ # Supprimer le fichier local temporaire après l'upload réussi vers Gemini
365
+ try:
366
+ os.remove(save_path)
367
+ print(f"Fichier local temporaire '{save_path}' supprimé.")
368
+ except OSError as e:
369
+ print(f"AVERTISSEMENT: Échec de la suppression du fichier local '{save_path}': {e}")
370
+
371
+ print(f"--- Fin Requête /upload (Session: {session_id}) - Succès ---")
372
+ return jsonify({'success': True, 'filename': filename}) # Renvoie le nom original pour l'UI
373
+
374
  except Exception as e:
375
+ print(f"ERREUR lors de l'upload ou du traitement Gemini: {e}")
376
+ traceback.print_exc()
377
+ # Nettoyer le fichier local s'il existe encore en cas d'erreur
378
+ if os.path.exists(save_path):
379
+ try:
380
+ os.remove(save_path)
381
+ print(f"Nettoyage: Fichier local '{save_path}' supprimé après erreur.")
382
+ except OSError as rm_err:
383
+ print(f"Erreur lors du nettoyage du fichier '{save_path}': {rm_err}")
384
+ print(f"--- Fin Requête /upload (Session: {session_id}) - Échec ---")
385
+ return jsonify({'error': f'Échec de l\'upload ou du traitement du fichier: {str(e)}'}), 500
386
+ else:
387
+ print(f"Erreur upload: Type de fichier non autorisé - '{file.filename}'")
388
+ return jsonify({'error': 'Type de fichier non autorisé.'}), 400
389
 
390
  @app.route('/clear_chat', methods=['POST'])
391
  def clear_chat():
392
+ """Efface l'historique de chat et les fichiers uploadés pour la session en cours."""
393
+ if 'session_id' not in session:
394
+ print("Avertissement clear_chat: Pas de session à effacer.")
395
+ return jsonify({'success': True}) # Pas d'erreur si pas de session
396
+
397
+ session_id = session['session_id']
398
+ print(f"\n--- Requête /clear_chat reçue (Session: {session_id}) ---")
399
+
400
+ # 1. Effacer la session de chat Gemini en mémoire (si elle existe)
401
  if session_id in chat_sessions:
402
  del chat_sessions[session_id]
403
+ print(f"Session de chat Gemini pour {session_id} effacée de la mémoire.")
404
+
405
+ # 2. Effacer les données pertinentes de la session Flask
406
  session['messages'] = []
407
+ files_to_delete = session.get('uploaded_files_gemini', [])
408
+ session['uploaded_files_gemini'] = []
409
+ session.modified = True
410
+ print("Historique des messages et références de fichiers effacés de la session Flask.")
411
+
412
+ # 3. (Optionnel mais recommandé) Supprimer les fichiers uploadés correspondants depuis Google AI Studio
413
+ # Cela nécessite de stocker les 'name' (IDs) des fichiers Gemini et d'appeler genai.delete_file()
414
+ # Pour l'instant, on efface juste la référence dans la session Flask.
415
+ # L'API delete_file est importante pour gérer l'espace de stockage Gemini.
416
+ if files_to_delete:
417
+ print(f"Tentative de suppression de {len(files_to_delete)} fichier(s) sur Google AI Studio...")
418
+ for file_obj in files_to_delete:
419
+ try:
420
+ print(f" Suppression de {file_obj.display_name} (ID: {file_obj.name})...")
421
+ genai.delete_file(file_obj.name)
422
+ print(f" Fichier {file_obj.name} supprimé de Google AI.")
423
+ except Exception as e:
424
+ print(f" ERREUR lors de la suppression du fichier {file_obj.name} de Google AI: {e}")
425
+ # Continuer même si un fichier ne peut être supprimé
426
+
427
+
428
+ print(f"--- Fin Requête /clear_chat (Session: {session_id}) ---")
429
  return jsonify({'success': True})
430
 
431
+
432
+ # --- Démarrage de l'Application ---
433
  if __name__ == '__main__':
434
+ # Rappel des dépendances:
435
+ # pip install Flask python-dotenv google-generativeai Pillow werkzeug requests gunicorn
436
+ print("\n" + "="*40)
437
+ print(" Démarrage du serveur Flask Assistant IA ")
438
+ print("="*40)
439
+ print("Vérifiez que votre fichier .env contient :")
440
+ print(" - GEMINI_API_KEY=VOTRE_CLE_GEMINI")
441
+ print(" - FLASK_SECRET_KEY=VOTRE_CLE_SECRET_FLASK")
442
+ print(" - SERPER_API_KEY=VOTRE_CLE_SERPER (Optionnel, pour recherche web)")
443
+ print("-"*40)
444
+ # Utiliser host='0.0.0.0' pour rendre accessible sur le réseau local
445
+ # debug=True active le rechargement automatique et le débogueur (NE PAS UTILISER EN PRODUCTION)
446
+ # Utiliser Gunicorn ou un autre serveur WSGI pour la production
447
+ app.run(debug=True, host='0.0.0.0', port=5000)