Docfile commited on
Commit
5ad49c1
·
verified ·
1 Parent(s): eb41f4f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +278 -311
app.py CHANGED
@@ -1,346 +1,313 @@
 
1
  import os
2
  import json
3
- import mimetypes
4
- from flask import Flask, request, session, jsonify, redirect, url_for, flash, render_template
5
- from dotenv import load_dotenv
6
  from google import genai
7
  from google.genai import types
8
- import requests
9
  from werkzeug.utils import secure_filename
10
- import markdown # Pour convertir la réponse en HTML
11
- from flask_session import Session
12
- import pprint # Pour un affichage plus lisible des structures complexes (optionnel)
13
- import logging # Import logging
14
- import html # Pour échapper le HTML dans les messages Telegram
15
-
16
- # --- Configuration Initiale ---
17
- load_dotenv()
18
 
19
  app = Flask(__name__)
 
20
 
21
- # --- Configuration Logging ---
22
- logging.basicConfig(level=logging.DEBUG,
23
- format='%(asctime)s - %(levelname)s - %(message)s')
24
- logger = logging.getLogger(__name__)
25
-
26
- # --- Configuration Telegram ---
27
- TELEGRAM_BOT_TOKEN = "8004545342:AAGcZaoDjYg8dmbbXRsR1N3TfSSbEiAGz88"
28
- TELEGRAM_CHAT_ID = "-1002497861230"
29
- TELEGRAM_ENABLED = TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID
30
-
31
- if TELEGRAM_ENABLED:
32
- logger.info(f"Notifications Telegram activées pour le chat ID: {TELEGRAM_CHAT_ID}")
33
- else:
34
- logger.warning("Notifications Telegram désactivées. Ajoutez TELEGRAM_BOT_TOKEN et TELEGRAM_CHAT_ID dans .env")
35
 
36
- # --- Configuration Flask Standard ---
37
- app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'une-super-cle-secrete-a-changer')
38
-
39
- # Configuration pour les uploads
40
- UPLOAD_FOLDER = 'temp'
41
- # CHANGED: Ajout des extensions audio
42
- ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'mp3', 'wav', 'aac', 'ogg', 'flac'}
43
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
44
- app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024
45
 
 
46
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
47
- logger.info(f"Dossier d'upload configuré : {os.path.abspath(UPLOAD_FOLDER)}")
48
-
49
- # --- Configuration pour Flask-Session ---
50
- app.config['SESSION_TYPE'] = 'filesystem'
51
- app.config['SESSION_PERMANENT'] = False
52
- app.config['SESSION_USE_SIGNER'] = True
53
- app.config['SESSION_FILE_DIR'] = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'flask_session')
54
- app.config['SESSION_COOKIE_SAMESITE'] = 'None'
55
- app.config['SESSION_COOKIE_SECURE'] = True
56
- os.makedirs(app.config['SESSION_FILE_DIR'], exist_ok=True)
57
- logger.info(f"Dossier pour les sessions serveur configuré : {app.config['SESSION_FILE_DIR']}")
58
-
59
- server_session = Session(app)
60
 
61
- # --- Configuration de l'API Gemini ---
62
- # CHANGED: Mise à jour des noms de modèles pour utiliser les versions avec "thinking" intégré
63
- MODEL_FLASH = 'gemini-2.5-flash'
64
- MODEL_PRO = 'gemini-2.5-pro'
65
- 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. Tu peux analyser des textes, des images, des fichiers audio, exécuter du code, faire des recherches sur le web et analyser le contenu d'URLs."
66
 
67
- SAFETY_SETTINGS = [
68
- types.SafetySetting(category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold=types.HarmBlockThreshold.BLOCK_NONE),
69
- types.SafetySetting(category=types.HarmCategory.HARM_CATEGORY_HARASSMENT, threshold=types.HarmBlockThreshold.BLOCK_NONE),
70
- types.SafetySetting(category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, threshold=types.HarmBlockThreshold.BLOCK_NONE),
71
- types.SafetySetting(category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold=types.HarmBlockThreshold.BLOCK_NONE)
72
- ]
73
-
74
- GEMINI_CONFIGURED = False
75
- genai_client = None
76
- try:
77
- gemini_api_key = os.getenv("GOOGLE_API_KEY")
78
- if not gemini_api_key:
79
- logger.error("ERREUR: Clé API GOOGLE_API_KEY manquante dans le fichier .env")
80
- else:
81
- genai_client = genai.Client(api_key=gemini_api_key)
82
- try:
83
- models = genai_client.list_models()
84
- models_list = [model.name for model in models]
85
- if any(MODEL_FLASH in model for model in models_list) and any(MODEL_PRO in model for model in models_list):
86
- logger.info(f"Configuration Gemini effectuée. Modèles requis ({MODEL_FLASH}, {MODEL_PRO}) disponibles.")
87
- logger.info(f"System instruction: {SYSTEM_INSTRUCTION}")
88
- GEMINI_CONFIGURED = True
89
- else:
90
- logger.error(f"ERREUR: Les modèles requis ({MODEL_FLASH}, {MODEL_PRO}) ne sont pas tous disponibles via l'API.")
91
- logger.error(f"Modèles trouvés: {models_list}")
92
- except Exception as e_models:
93
- logger.error(f"ERREUR lors de la vérification des modèles: {e_models}")
94
- logger.warning("Tentative de continuer sans vérification des modèles disponibles.")
95
- GEMINI_CONFIGURED = True
96
- except Exception as e:
97
- logger.critical(f"ERREUR Critique lors de la configuration initiale de Gemini : {e}")
98
- logger.warning("L'application fonctionnera sans les fonctionnalités IA.")
99
-
100
- # --- Fonctions Utilitaires ---
101
 
102
  def allowed_file(filename):
103
- """Vérifie si l'extension du fichier est autorisée."""
104
- return '.' in filename and \
105
- filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
106
-
107
- # REMOVED: La fonction perform_web_search est maintenant obsolète grâce à l'intégration des outils.
108
- # REMOVED: La fonction format_search_response est également obsolète.
109
-
110
- def prepare_gemini_history(chat_history):
111
- """Convertit l'historique stocké en session au format attendu par Gemini API."""
112
- logger.debug(f"--- DEBUG [prepare_gemini_history]: Entrée avec {len(chat_history)} messages")
113
- gemini_history = []
114
- for message in list(chat_history):
115
- role = message.get('role')
116
- text_part = message.get('raw_text')
117
- if text_part:
118
- role_gemini = "user" if role == 'user' else "model"
119
- gemini_history.append({"role": role_gemini, "parts": [{"text": text_part}]})
120
- logger.debug(f"--- DEBUG [prepare_gemini_history]: Sortie avec {len(gemini_history)} messages")
121
- return gemini_history
122
-
123
- def process_uploaded_file(file):
124
- """Traite un fichier uploadé et retourne les infos nécessaires pour Gemini."""
125
- if not file or file.filename == '':
126
- return None, None, None
127
- if allowed_file(file.filename):
128
- try:
129
- filename = secure_filename(file.filename)
130
- filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
131
- file.save(filepath)
132
- logger.debug(f" [process_uploaded_file]: Fichier '{filename}' sauvegardé dans '{filepath}'")
133
- mime_type = mimetypes.guess_type(filepath)[0] or 'application/octet-stream'
134
- with open(filepath, "rb") as f:
135
- file_data = f.read()
136
- file_part = {"inline_data": {"mime_type": mime_type, "data": file_data}}
137
- return file_part, filename, filepath
138
- except Exception as e:
139
- logger.error(f"--- ERREUR [process_uploaded_file]: Échec traitement fichier '{file.filename}': {e}")
140
- return None, None, None
141
- else:
142
- logger.error(f"--- ERREUR [process_uploaded_file]: Type de fichier non autorisé: {file.filename}")
143
- return None, None, None
144
-
145
- def send_telegram_message(message):
146
- """Envoie un message à Telegram via l'API Bot."""
147
- if not TELEGRAM_ENABLED:
148
- return False
149
- try:
150
- sanitized_message = html.escape(message)
151
- if len(sanitized_message) > 4000:
152
- sanitized_message = sanitized_message[:3997] + "..."
153
- response = requests.post(
154
- f'https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage',
155
- data={'chat_id': TELEGRAM_CHAT_ID, 'text': sanitized_message, 'parse_mode': 'HTML'}
156
- )
157
- if response.status_code == 200:
158
- logger.debug("Message Telegram envoyé avec succès")
159
- return True
160
- else:
161
- logger.error(f"Échec d'envoi du message Telegram: {response.status_code} - {response.text}")
162
- return False
163
- except Exception as e:
164
- logger.error(f"Erreur lors de l'envoi du message Telegram: {e}")
165
- return False
166
-
167
- def generate_session_id():
168
- """Génère un ID de session unique."""
169
- import uuid
170
- return str(uuid.uuid4())[:8]
171
-
172
- # --- Routes Flask ---
173
-
174
- @app.route('/')
175
- def root():
176
- if 'session_id' not in session:
177
- session['session_id'] = generate_session_id()
178
- return render_template('index.html')
179
-
180
- @app.route('/api/history', methods=['GET'])
181
- def get_history():
182
- if 'chat_history' not in session:
183
- session['chat_history'] = []
184
- return jsonify({'success': True, 'history': session.get('chat_history', [])})
185
-
186
- @app.route('/api/chat', methods=['POST'])
187
- def chat_api():
188
- logger.debug("\n---===================================---")
189
- logger.debug("--- DEBUG [/api/chat]: Nouvelle requête POST ---")
190
-
191
- if not GEMINI_CONFIGURED or not genai_client:
192
- return jsonify({'success': False, 'error': "Le service IA n'est pas configuré correctement."}), 503
193
-
194
- prompt = request.form.get('prompt', '').strip()
195
- use_web_search = request.form.get('web_search', 'false').lower() == 'true'
196
- use_advanced = request.form.get('advanced_reasoning', 'false').lower() == 'true'
197
- files = request.files.getlist('file')
198
 
199
- logger.debug(f" [/api/chat]: Prompt reçu: '{prompt[:50]}...'")
200
- logger.debug(f" [/api/chat]: Recherche Web: {use_web_search}, Raisonnement Avancé: {use_advanced}")
201
- logger.debug(f" [/api/chat]: Nombre de fichiers: {len(files) if files else 0}")
 
 
202
 
203
- if not prompt and not files:
204
- return jsonify({'success': False, 'error': 'Veuillez fournir un message ou au moins un fichier.'}), 400
205
-
206
- if 'session_id' not in session:
207
- session['session_id'] = generate_session_id()
208
- session_id = session['session_id']
209
-
210
- if 'chat_history' not in session:
211
- session['chat_history'] = []
212
-
213
- uploaded_file_parts = []
214
- uploaded_filenames = []
215
- filepaths_to_delete = []
216
 
217
- for file in files:
218
- if file and file.filename != '':
219
- file_part, filename, filepath = process_uploaded_file(file)
220
- if file_part:
221
- uploaded_file_parts.append(file_part)
222
- uploaded_filenames.append(filename)
223
- filepaths_to_delete.append(filepath)
224
-
225
- raw_user_text = prompt
226
- files_text = ", ".join([f"[{filename}]" for filename in uploaded_filenames]) if uploaded_filenames else ""
227
- display_user_text = f"{files_text} {prompt}" if files_text and prompt else (prompt or files_text)
228
 
229
- user_history_entry = {'role': 'user', 'text': display_user_text, 'raw_text': raw_user_text}
230
- session.setdefault('chat_history', []).append(user_history_entry)
231
-
232
- if TELEGRAM_ENABLED:
233
- files_info = f"[{len(uploaded_filenames)} fichiers] " if uploaded_filenames else ""
234
- telegram_message = f"🔵 NOUVEAU MESSAGE (Session {session_id})\n\n{files_info}{prompt}"
235
- send_telegram_message(telegram_message)
236
-
237
- selected_model_name = MODEL_PRO if use_advanced else MODEL_FLASH
238
 
239
- # --- NOUVELLE LOGIQUE UNIFIÉE ---
240
- # Préparation de l'historique et du message courant
241
- history_for_gemini = list(session.get('chat_history', []))[:-1]
242
- gemini_history_to_send = prepare_gemini_history(history_for_gemini)
243
- contents = gemini_history_to_send.copy()
244
-
245
- current_user_parts = uploaded_file_parts.copy()
246
- if raw_user_text:
247
- current_user_parts.append({"text": raw_user_text})
248
- elif uploaded_filenames and not raw_user_text:
249
- # Si seulement un fichier est envoyé, générer un prompt
250
- current_user_parts.append({"text": f"Décris le contenu de ce(s) fichier(s) : {', '.join(uploaded_filenames)}"})
251
-
252
- contents.append({"role": "user", "parts": current_user_parts})
253
-
254
- # NEW: Définition des outils à utiliser
255
- tools = [
256
- {"url_context": {}}, # Pour analyser les URLs dans le prompt
257
- {"code_execution": {}}, # Pour exécuter du code Python
258
- ]
259
 
260
- tools.append({"google_search": {}})
261
- logger.debug(f" [/api/chat]: Outils activés: {[tool.popitem()[0] for tool in tools]}")
 
 
 
 
 
 
 
262
 
263
- # Configuration finale de la requête
264
- generate_config = types.GenerateContentConfig(
265
- system_instruction=SYSTEM_INSTRUCTION,
266
- safety_settings=SAFETY_SETTINGS,
267
- tools=tools, # Intégration des outils
268
- )
269
- # Le "thinking" est activé par défaut sur les modèles 2.5, pas besoin de configurer `thinking_config` pour le mode dynamique.
270
 
271
- try:
272
- logger.debug(f"--- LOG [/api/chat]: Envoi de la requête unifiée à {selected_model_name}...")
273
- response = genai_client.models.generate_content(
274
- model=selected_model_name,
275
- contents=contents,
276
- config=generate_config
277
- )
278
-
279
- response_text_raw = ""
280
- response_html = ""
281
  try:
282
- if hasattr(response, 'text'):
283
- response_text_raw = response.text
284
- logger.debug(f"--- LOG [/api/chat]: Réponse reçue (début): '{response_text_raw[:100]}...'")
285
- elif hasattr(response, 'prompt_feedback') and response.prompt_feedback:
286
- feedback = response.prompt_feedback
287
- block_reason = getattr(feedback, 'block_reason', 'inconnue')
288
- response_text_raw = f"Désolé, ma réponse a été bloquée (raison : {block_reason})."
289
- else:
290
- response_text_raw = "Désolé, je n'ai pas pu générer de réponse."
291
 
292
- response_html = markdown.markdown(response_text_raw, extensions=['fenced_code', 'tables', 'nl2br'])
293
- except Exception as e_resp:
294
- logger.error(f"--- ERREUR [/api/chat]: Erreur lors du traitement de la réponse: {e_resp}")
295
- response_text_raw = f"Désolé, erreur inattendue ({type(e_resp).__name__})."
296
- response_html = markdown.markdown(response_text_raw)
297
-
298
- assistant_history_entry = {'role': 'assistant', 'text': response_html, 'raw_text': response_text_raw}
299
- session.setdefault('chat_history', []).append(assistant_history_entry)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
 
301
- if TELEGRAM_ENABLED:
302
- telegram_message = f"🟢 RÉPONSE IA (Session {session_id})\n\n{response_text_raw[:3900]}"
303
- send_telegram_message(telegram_message)
304
 
305
- logger.debug("--- LOG [/api/chat]: Envoi de la réponse HTML au client.\n---==================================---\n")
306
- return jsonify({'success': True, 'message': response_html})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
 
 
 
308
  except Exception as e:
309
- logger.critical(f"--- ERREUR CRITIQUE [/api/chat]: Échec appel Gemini ou traitement réponse : {e}")
310
- # Retirer le dernier message utilisateur en cas d'échec
311
- current_history = session.get('chat_history')
312
- if isinstance(current_history, list) and current_history and current_history[-1].get('role') == 'user':
313
- current_history.pop()
314
-
315
- if TELEGRAM_ENABLED:
316
- error_message = f"🔴 ERREUR (Session {session_id})\n\nPrompt: {prompt[:100]}\n\nErreur: {str(e)}"
317
- send_telegram_message(error_message)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
 
319
- return jsonify({'success': False, 'error': f"Erreur interne: {e}"}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
- finally:
322
- for filepath in filepaths_to_delete:
323
- if filepath and os.path.exists(filepath):
324
- try:
325
- os.remove(filepath)
326
- logger.debug(f"--- LOG [/api/chat FINALLY]: Fichier temporaire '{filepath}' supprimé.")
327
- except OSError as e_del:
328
- logger.error(f"--- ERREUR [/api/chat FINALLY]: Échec suppression fichier '{filepath}': {e_del}")
329
 
330
- @app.route('/clear', methods=['POST'])
331
- def clear_chat():
332
- logger.debug("\n--- DEBUG [/clear]: Requête POST reçue ---")
333
- if TELEGRAM_ENABLED and 'session_id' in session:
334
- end_message = f"⚪ SESSION TERMINÉE (Session {session.get('session_id')})"
335
- send_telegram_message(end_message)
336
- session.clear()
337
- if 'XMLHttpRequest' == request.headers.get('X-Requested-With'):
338
- return jsonify({'success': True, 'message': 'Historique effacé.'})
339
- else:
340
- flash("Conversation effacée.", "info")
341
- return redirect(url_for('root'))
 
 
 
 
 
 
 
 
342
 
343
  if __name__ == '__main__':
344
- logger.info("--- Démarrage du serveur Flask ---")
345
- port = int(os.environ.get('PORT', 5001))
346
- app.run(debug=True, host='0.0.0.0', port=port)
 
1
+ from flask import Flask, render_template, request, jsonify, Response, session
2
  import os
3
  import json
4
+ import base64
 
 
5
  from google import genai
6
  from google.genai import types
 
7
  from werkzeug.utils import secure_filename
8
+ import uuid
9
+ from datetime import datetime
10
+ import io
11
+ from typing import Optional, List, Dict, Union
 
 
 
 
12
 
13
  app = Flask(__name__)
14
+ app.secret_key = 'your-secret-key-here' # Changez cette clé !
15
 
16
+ # Configuration de l'upload
17
+ UPLOAD_FOLDER = 'uploads'
18
+ ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'pdf', 'mp4', 'mov', 'txt', 'csv', 'json'}
19
+ MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB max file size
 
 
 
 
 
 
 
 
 
 
20
 
 
 
 
 
 
 
 
21
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
22
+ app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH
23
 
24
+ # Créer le dossier uploads s'il n'existe pas
25
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ # Initialisation du client Gemini
28
+ API_KEY = "AIzaSyAMYpF67aqFnWDJESWOx1dC-w3sEU29VcM"
29
+ client = genai.Client(api_key=API_KEY)
 
 
30
 
31
+ # Configuration des modèles disponibles
32
+ MODELS = {
33
+ "Gemini 2.5 Flash": "gemini-2.5-flash",
34
+ "Gemini 2.5 Pro": "gemini-2.5-pro",
35
+ "Gemini 2.5 Flash Lite": "gemini-2.5-flash-lite",
36
+ "Gemini 2.0 Flash": "gemini-2.0-flash"
37
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  def allowed_file(filename):
40
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
41
+
42
+ def configure_tools() -> List[types.Tool]:
43
+ """Configure tous les outils par défaut"""
44
+ tools = []
45
+ tools.append(types.Tool(code_execution=types.ToolCodeExecution()))
46
+ tools.append(types.Tool(google_search=types.GoogleSearch()))
47
+ tools.append(types.Tool(url_context=types.UrlContext()))
48
+ return tools
49
+
50
+ def get_generation_config(
51
+ model: str,
52
+ thinking_enabled: bool = True,
53
+ include_thoughts: bool = False
54
+ ) -> types.GenerateContentConfig:
55
+ """Configuration optimisée par défaut"""
56
+ config_dict = {
57
+ "temperature": 0.7,
58
+ "max_output_tokens": 8192,
59
+ "top_p": 0.9,
60
+ "top_k": 40,
61
+ "tools": configure_tools()
62
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ if thinking_enabled:
65
+ config_dict["thinking_config"] = types.ThinkingConfig(
66
+ thinking_budget=-1, # Dynamique par défaut
67
+ include_thoughts=include_thoughts
68
+ )
69
 
70
+ return types.GenerateContentConfig(**config_dict)
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
+ def process_uploaded_file(file) -> Optional[types.Part]:
73
+ """Traite les fichiers uploadés"""
74
+ if not file:
75
+ return None
 
 
 
 
 
 
 
76
 
77
+ file_bytes = file.read()
78
+ mime_type = file.content_type
 
 
 
 
 
 
 
79
 
80
+ # Réinitialiser le pointeur du fichier
81
+ file.seek(0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
+ # Gestion des différents types de fichiers
84
+ if mime_type.startswith("image/"):
85
+ return types.Part.from_bytes(data=file_bytes, mime_type=mime_type)
86
+ elif mime_type == "application/pdf":
87
+ return types.Part.from_bytes(data=file_bytes, mime_type=mime_type)
88
+ elif mime_type.startswith("video/"):
89
+ return types.Part.from_bytes(data=file_bytes, mime_type=mime_type)
90
+ elif mime_type in ["text/plain", "text/csv", "application/json"]:
91
+ return types.Part.from_bytes(data=file_bytes, mime_type=mime_type)
92
 
93
+ return None
 
 
 
 
 
 
94
 
95
+ @app.route('/')
96
+ def index():
97
+ """Page principale"""
98
+ return render_template('index.html', models=MODELS)
99
+
100
+ @app.route('/send_message', methods=['POST'])
101
+ def send_message():
102
+ """Endpoint pour envoyer un message avec streaming"""
103
+ def generate():
 
104
  try:
105
+ # Récupération des données
106
+ data = request.get_json()
107
+ message = data.get('message', '')
108
+ model = data.get('model', 'gemini-2.5-flash')
109
+ thinking_enabled = data.get('thinking_enabled', True)
110
+ include_thoughts = data.get('include_thoughts', False)
 
 
 
111
 
112
+ if not message.strip():
113
+ yield f"data: {json.dumps({'error': 'Message vide'})}\n\n"
114
+ return
115
+
116
+ # Configuration de génération
117
+ generation_config = get_generation_config(
118
+ model=model,
119
+ thinking_enabled=thinking_enabled,
120
+ include_thoughts=include_thoughts
121
+ )
122
+
123
+ # Initialisation ou récupération du chat
124
+ chat_id = session.get('chat_id')
125
+ if not chat_id:
126
+ chat_id = str(uuid.uuid4())
127
+ session['chat_id'] = chat_id
128
+ session['chat'] = client.chats.create(
129
+ model=model,
130
+ config=generation_config
131
+ )
132
+
133
+ chat = session.get('chat')
134
+ if not chat:
135
+ session['chat'] = client.chats.create(
136
+ model=model,
137
+ config=generation_config
138
+ )
139
+ chat = session['chat']
140
+
141
+ # Préparation du contenu
142
+ contents = [message]
143
+
144
+ # Envoi du message avec streaming
145
+ response_stream = chat.send_message_stream(
146
+ contents,
147
+ config=generation_config
148
+ )
149
+
150
+ # Streaming de la réponse
151
+ full_response = ""
152
+ thoughts_content = ""
153
+
154
+ for chunk in response_stream:
155
+ for part in chunk.candidates[0].content.parts:
156
+ if part.text:
157
+ if part.thought and include_thoughts:
158
+ thoughts_content += part.text
159
+ yield f"data: {json.dumps({'type': 'thought', 'content': part.text})}\n\n"
160
+ else:
161
+ full_response += part.text
162
+ yield f"data: {json.dumps({'type': 'text', 'content': part.text})}\n\n"
163
+
164
+ # Message de fin
165
+ yield f"data: {json.dumps({'type': 'end', 'full_response': full_response})}\n\n"
166
+
167
+ except Exception as e:
168
+ yield f"data: {json.dumps({'error': str(e)})}\n\n"
169
+
170
+ return Response(generate(), mimetype='text/event-stream')
171
+
172
+ @app.route('/upload_file', methods=['POST'])
173
+ def upload_file():
174
+ """Endpoint pour uploader des fichiers"""
175
+ try:
176
+ if 'file' not in request.files:
177
+ return jsonify({'error': 'Aucun fichier fourni'}), 400
178
 
179
+ file = request.files['file']
180
+ if file.filename == '':
181
+ return jsonify({'error': 'Aucun fichier sélectionné'}), 400
182
 
183
+ if file and allowed_file(file.filename):
184
+ filename = secure_filename(file.filename)
185
+ file_id = str(uuid.uuid4())
186
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], f"{file_id}_{filename}")
187
+
188
+ # Sauvegarder le fichier
189
+ file.save(file_path)
190
+
191
+ # Traiter le fichier pour Gemini
192
+ with open(file_path, 'rb') as f:
193
+ file_part = process_uploaded_file(f)
194
+
195
+ if file_part:
196
+ # Stocker les informations du fichier en session
197
+ if 'uploaded_files' not in session:
198
+ session['uploaded_files'] = {}
199
+
200
+ session['uploaded_files'][file_id] = {
201
+ 'filename': filename,
202
+ 'path': file_path,
203
+ 'mime_type': file.content_type
204
+ }
205
+
206
+ return jsonify({
207
+ 'success': True,
208
+ 'file_id': file_id,
209
+ 'filename': filename,
210
+ 'mime_type': file.content_type
211
+ })
212
+ else:
213
+ # Supprimer le fichier si le traitement a échoué
214
+ os.remove(file_path)
215
+ return jsonify({'error': 'Type de fichier non supporté'}), 400
216
 
217
+ return jsonify({'error': 'Type de fichier non autorisé'}), 400
218
+
219
  except Exception as e:
220
+ return jsonify({'error': str(e)}), 500
221
+
222
+ @app.route('/send_message_with_file', methods=['POST'])
223
+ def send_message_with_file():
224
+ """Endpoint pour envoyer un message avec fichier attaché"""
225
+ def generate():
226
+ try:
227
+ data = request.get_json()
228
+ message = data.get('message', '')
229
+ model = data.get('model', 'gemini-2.5-flash')
230
+ thinking_enabled = data.get('thinking_enabled', True)
231
+ include_thoughts = data.get('include_thoughts', False)
232
+ file_id = data.get('file_id')
233
+
234
+ if not message.strip():
235
+ yield f"data: {json.dumps({'error': 'Message vide'})}\n\n"
236
+ return
237
+
238
+ # Configuration de génération
239
+ generation_config = get_generation_config(
240
+ model=model,
241
+ thinking_enabled=thinking_enabled,
242
+ include_thoughts=include_thoughts
243
+ )
244
+
245
+ # Récupération du chat
246
+ chat = session.get('chat')
247
+ if not chat:
248
+ session['chat'] = client.chats.create(
249
+ model=model,
250
+ config=generation_config
251
+ )
252
+ chat = session['chat']
253
+
254
+ # Préparation du contenu
255
+ contents = [message]
256
+
257
+ # Ajout du fichier si présent
258
+ if file_id and 'uploaded_files' in session and file_id in session['uploaded_files']:
259
+ file_info = session['uploaded_files'][file_id]
260
+ with open(file_info['path'], 'rb') as f:
261
+ file_part = process_uploaded_file(f)
262
+ if file_part:
263
+ contents.append(file_part)
264
+
265
+ # Envoi du message avec streaming
266
+ response_stream = chat.send_message_stream(
267
+ contents,
268
+ config=generation_config
269
+ )
270
 
271
+ # Streaming de la réponse
272
+ full_response = ""
273
+
274
+ for chunk in response_stream:
275
+ for part in chunk.candidates[0].content.parts:
276
+ if part.text:
277
+ if part.thought and include_thoughts:
278
+ yield f"data: {json.dumps({'type': 'thought', 'content': part.text})}\n\n"
279
+ else:
280
+ full_response += part.text
281
+ yield f"data: {json.dumps({'type': 'text', 'content': part.text})}\n\n"
282
+
283
+ # Message de fin
284
+ yield f"data: {json.dumps({'type': 'end', 'full_response': full_response})}\n\n"
285
+
286
+ except Exception as e:
287
+ yield f"data: {json.dumps({'error': str(e)})}\n\n"
288
 
289
+ return Response(generate(), mimetype='text/event-stream')
 
 
 
 
 
 
 
290
 
291
+ @app.route('/reset_chat', methods=['POST'])
292
+ def reset_chat():
293
+ """Reset la conversation"""
294
+ try:
295
+ session.pop('chat_id', None)
296
+ session.pop('chat', None)
297
+
298
+ # Nettoyer les fichiers uploadés
299
+ if 'uploaded_files' in session:
300
+ for file_info in session['uploaded_files'].values():
301
+ try:
302
+ if os.path.exists(file_info['path']):
303
+ os.remove(file_info['path'])
304
+ except:
305
+ pass
306
+ session.pop('uploaded_files', None)
307
+
308
+ return jsonify({'success': True})
309
+ except Exception as e:
310
+ return jsonify({'error': str(e)}), 500
311
 
312
  if __name__ == '__main__':
313
+ app.run(debug=True, host='0.0.0.0', port=5000)