Docfile commited on
Commit
0ab0c95
·
verified ·
1 Parent(s): c2d6635

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +60 -55
app.py CHANGED
@@ -12,18 +12,22 @@ from werkzeug.utils import secure_filename
12
  import markdown # Pour convertir la réponse Markdown en HTML
13
 
14
  # --- Configuration Initiale ---
 
15
  load_dotenv() # Charge les variables depuis .env
16
 
17
  app = Flask(__name__)
 
18
  # Clé secrète FORTEMENT recommandée pour la sécurité des sessions
19
  app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'une-cle-secrete-tres-difficile-a-deviner')
20
 
21
  # Configuration pour les uploads
22
  UPLOAD_FOLDER = 'temp'
 
23
  # Extensions autorisées (incluant vidéo)
24
  ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'mp4', 'mov', 'avi', 'mkv', 'webm'}
25
  VIDEO_EXTENSIONS = {'mp4', 'mov', 'avi', 'mkv', 'webm'} # Pour identifier les vidéos
26
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
 
27
  # Augmenter la limite pour les vidéos (ex: 100MB) - Ajustez si nécessaire
28
  app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024
29
 
@@ -33,16 +37,16 @@ print(f"Dossier d'upload configuré : {os.path.abspath(UPLOAD_FOLDER)}")
33
 
34
  # --- Configuration de l'API Gemini ---
35
  # Utilisez les noms de modèles les plus récents auxquels vous avez accès
36
- MODEL_FLASH = 'gemini-2.0-flash'
37
- MODEL_PRO = 'gemini-2.5-pro-03-25' # Pro est souvent nécessaire/meilleur pour la vidéo
38
 
39
  # Instruction système pour le modèle
40
  SYSTEM_INSTRUCTION = "Tu es un assistant intelligent et amical nommé Mariam. Tu assistes les utilisateurs au mieux de tes capacités, y compris dans l'analyse de texte, d'images et de vidéos (via upload ou lien YouTube). Tu as été créé par Aenir."
41
 
42
  # Paramètres de sécurité (ajuster si nécessaire, BLOCK_NONE est très permissif)
43
  SAFETY_SETTINGS = [ {"category": c, "threshold": "BLOCK_NONE"} for c in [
44
- "HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_HATE_SPEECH",
45
- "HARM_CATEGORY_SEXUALLY_EXPLICIT", "HARM_CATEGORY_DANGEROUS_CONTENT"]]
46
 
47
  GEMINI_CONFIGURED = False
48
  gemini_client = None # Client API pour les opérations sur les fichiers (upload vidéo)
@@ -55,7 +59,7 @@ try:
55
  # Initialise le client pour les opérations sur les fichiers (upload vidéo avec polling)
56
  gemini_client = genai.Client(api_key=gemini_api_key)
57
  # Configure également l'espace de noms global pour GenerativeModel, etc.
58
-
59
 
60
  # Vérifie si les modèles requis sont disponibles
61
  print("Vérification des modèles Gemini disponibles...")
@@ -69,6 +73,7 @@ try:
69
  missing = [model for model in required_models if model not in models_list]
70
  raise ValueError(f"Les modèles Gemini requis suivants sont manquants: {missing}")
71
 
 
72
  except Exception as e:
73
  print(f"ERREUR Critique lors de la configuration initiale de Gemini : {e}")
74
  print("L'application fonctionnera sans les fonctionnalités IA.")
@@ -94,13 +99,14 @@ def is_youtube_url(url):
94
  return False
95
  # Regex simple pour les formats courants d'URL YouTube
96
  youtube_regex = re.compile(
97
- r'(https?://)?(www\.)?' # Protocole et www optionnels
98
- r'(youtube|youtu|youtube-nocookie)\.(com|be)/' # Domaines youtube.com, youtu.be, etc.
99
- r'(watch\?v=|embed/|v/|.+\?v=)?' # Différents chemins possibles
100
  r'([^&=%\?]{11})') # L'ID vidéo de 11 caractères
101
  return youtube_regex.match(url) is not None
102
 
103
  # --- Fonction d'Upload Vidéo avec Polling ---
 
104
  def upload_video_with_polling(filepath, mime_type, max_wait_seconds=300, poll_interval=10):
105
  """
106
  Upload une vidéo via client.files.upload et attend son traitement.
@@ -118,7 +124,7 @@ def upload_video_with_polling(filepath, mime_type, max_wait_seconds=300, poll_in
118
 
119
  start_time = time.time()
120
  # Boucle de polling tant que l'état est "PROCESSING"
121
- while video_file.state == genai_types.FileState.PROCESSING:
122
  elapsed_time = time.time() - start_time
123
  # Vérifie le timeout
124
  if elapsed_time > max_wait_seconds:
@@ -130,11 +136,11 @@ def upload_video_with_polling(filepath, mime_type, max_wait_seconds=300, poll_in
130
  video_file = gemini_client.files.get(name=video_file.name)
131
 
132
  # Vérifie l'état final après la boucle
133
- if video_file.state == genai_types.FileState.FAILED:
134
  print(f"ERREUR: Le traitement de la vidéo a échoué. État: {video_file.state.name}")
135
  raise ValueError("Le traitement de la vidéo a échoué côté serveur.")
136
 
137
- if video_file.state == genai_types.FileState.ACTIVE:
138
  print(f"Traitement vidéo terminé avec succès: {video_file.uri}")
139
  return video_file # Retourne l'objet fichier SDK réussi
140
 
@@ -155,6 +161,7 @@ def upload_video_with_polling(filepath, mime_type, max_wait_seconds=300, poll_in
155
  raise # Relance l'exception originale pour qu'elle soit gérée par l'appelant
156
 
157
  # --- Fonctions de Recherche Web (inchangées - implémentez si nécessaire) ---
 
158
  def perform_web_search(query):
159
  """Effectue une recherche web via l'API Serper (Exemple)."""
160
  serper_api_key = os.getenv("SERPER_API_KEY")
@@ -171,14 +178,14 @@ def format_search_results(data):
171
  """Met en forme les résultats de recherche (Exemple)."""
172
  if not data: return "Aucun résultat de recherche web pertinent."
173
  # ... (votre implémentation du formatage) ...
174
- results = ["**Résultats Web:**"]
175
  if data.get('organic'):
176
  for item in data['organic'][:3]:
177
  results.append(f"- {item.get('title', '')}: {item.get('snippet', '')}")
178
- return "\n".join(results)
179
-
180
 
181
  # --- Préparation Historique (inchangé) ---
 
182
  def prepare_gemini_history(chat_history):
183
  """Convertit l'historique de session pour l'API Gemini (texte seulement)."""
184
  gemini_history = []
@@ -273,12 +280,12 @@ def chat_api():
273
  # Appel bloquant qui attend le traitement
274
  processed_media_file = upload_video_with_polling(filepath, mime_type)
275
  # Crée le Part Gemini à partir de l'objet File retourné
276
- uploaded_media_part = genai_types.Part(file_data=processed_media_file)
277
  else:
278
  print(" Traitement FICHIER standard...")
279
  # Utilise l'upload global plus simple pour les non-vidéos
280
  processed_media_file = genai.upload_file(path=filepath, mime_type=mime_type)
281
- uploaded_media_part = genai_types.Part(file_data=processed_media_file)
282
  print(f" Part Média ({media_type}) créé: {processed_media_file.uri}")
283
 
284
  # 2. Sinon, traiter l'URL YouTube si fournie et valide
@@ -296,8 +303,8 @@ def chat_api():
296
  youtube_uri = youtube_url # Utilise l'URL validée comme URI
297
  # Crée un Part FileData directement à partir de l'URI
298
  # On peut spécifier un mime_type générique, Gemini gère les liens YT
299
- uploaded_media_part = genai_types.Part(
300
- file_data=genai_types.FileData(file_uri=youtube_uri, mime_type="video/mp4")
301
  )
302
  print(f" Part YouTube créé pour: {youtube_uri}")
303
  # Ajoute un prompt par défaut si l'utilisateur n'en a pas mis
@@ -350,22 +357,22 @@ def chat_api():
350
  # Construit un prompt enrichi
351
  final_prompt_for_gemini = f"""Basé sur la question suivante et les informations web ci-dessous, fournis une réponse complète.
352
 
353
- Question Originale:
354
- "{prompt}"
355
 
356
- Informations Web Pertinentes:
357
- --- DEBUT RESULTATS WEB ---
358
- {formatted_results}
359
- --- FIN RESULTATS WEB ---
360
 
361
- Réponse:"""
362
  print(" Prompt enrichi avec les résultats web.")
363
  else:
364
  print(" Aucun résultat de recherche web trouvé ou pertinent.")
365
 
366
  # Ajouter la partie texte (originale ou enrichie) s'il y a du texte
367
  if final_prompt_for_gemini:
368
- current_gemini_parts.append(genai_types.Part(text=final_prompt_for_gemini))
369
 
370
  # Vérification de sécurité : il doit y avoir au moins une partie (média ou texte)
371
  if not current_gemini_parts:
@@ -384,36 +391,35 @@ Réponse:"""
384
  print(f" Modèle sélectionné: {selected_model_name}")
385
 
386
  # Crée l'instance du modèle spécifique pour cette requête
387
- active_model = gemini_client.models.generate_content(
388
- model_name=selected_model_name,
389
- contents = contents_for_gemini
390
- config = types.GenerateContentConfig(
391
- system_instruction=SYSTEM_INSTRUCTION,
392
- safety_settings=[
393
-
394
- types.SafetySetting(
395
- category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
396
- threshold=types.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
397
- ),
398
- types.SafetySetting(
399
- category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
400
- threshold=types.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
401
- ),
402
- types.SafetySetting(
403
- category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
404
- threshold=types.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
405
- ),
406
- types.SafetySetting(
407
- category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
408
- threshold=types.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
 
 
 
409
  )
410
- ]
411
- ),
412
- )
413
 
414
  print(f" Envoi de la requête à {selected_model_name} ({len(contents_for_gemini)} messages/tours)...")
415
-
416
- #(non-streamé pour correspondre au code précédent)
417
  response = active_model
418
 
419
  # --- Traitement de la Réponse ---
@@ -480,7 +486,6 @@ Réponse:"""
480
  except OSError as e:
481
  print(f" ERREUR lors de la suppression du fichier temporaire '{filepath_to_delete}': {e}")
482
 
483
-
484
  @app.route('/clear', methods=['POST'])
485
  def clear_chat():
486
  """Efface l'historique de chat dans la session."""
@@ -498,8 +503,8 @@ def clear_chat():
498
  flash("Conversation effacée.", "info")
499
  return redirect(url_for('root')) # Redirige vers la page d'accueil
500
 
501
-
502
  # --- Démarrage de l'application Flask ---
 
503
  if __name__ == '__main__':
504
  print("Démarrage du serveur Flask...")
505
  # Utiliser un port différent si 5000 est déjà pris (ex: 5001)
 
12
  import markdown # Pour convertir la réponse Markdown en HTML
13
 
14
  # --- Configuration Initiale ---
15
+
16
  load_dotenv() # Charge les variables depuis .env
17
 
18
  app = Flask(__name__)
19
+
20
  # Clé secrète FORTEMENT recommandée pour la sécurité des sessions
21
  app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'une-cle-secrete-tres-difficile-a-deviner')
22
 
23
  # Configuration pour les uploads
24
  UPLOAD_FOLDER = 'temp'
25
+
26
  # Extensions autorisées (incluant vidéo)
27
  ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'mp4', 'mov', 'avi', 'mkv', 'webm'}
28
  VIDEO_EXTENSIONS = {'mp4', 'mov', 'avi', 'mkv', 'webm'} # Pour identifier les vidéos
29
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
30
+
31
  # Augmenter la limite pour les vidéos (ex: 100MB) - Ajustez si nécessaire
32
  app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024
33
 
 
37
 
38
  # --- Configuration de l'API Gemini ---
39
  # Utilisez les noms de modèles les plus récents auxquels vous avez accès
40
+ MODEL_FLASH = 'gemini-1.5-flash-latest'
41
+ MODEL_PRO = 'gemini-1.5-pro-latest' # Pro est souvent nécessaire/meilleur pour la vidéo
42
 
43
  # Instruction système pour le modèle
44
  SYSTEM_INSTRUCTION = "Tu es un assistant intelligent et amical nommé Mariam. Tu assistes les utilisateurs au mieux de tes capacités, y compris dans l'analyse de texte, d'images et de vidéos (via upload ou lien YouTube). Tu as été créé par Aenir."
45
 
46
  # Paramètres de sécurité (ajuster si nécessaire, BLOCK_NONE est très permissif)
47
  SAFETY_SETTINGS = [ {"category": c, "threshold": "BLOCK_NONE"} for c in [
48
+ "HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_HATE_SPEECH",
49
+ "HARM_CATEGORY_SEXUALLY_EXPLICIT", "HARM_CATEGORY_DANGEROUS_CONTENT"]]
50
 
51
  GEMINI_CONFIGURED = False
52
  gemini_client = None # Client API pour les opérations sur les fichiers (upload vidéo)
 
59
  # Initialise le client pour les opérations sur les fichiers (upload vidéo avec polling)
60
  gemini_client = genai.Client(api_key=gemini_api_key)
61
  # Configure également l'espace de noms global pour GenerativeModel, etc.
62
+
63
 
64
  # Vérifie si les modèles requis sont disponibles
65
  print("Vérification des modèles Gemini disponibles...")
 
73
  missing = [model for model in required_models if model not in models_list]
74
  raise ValueError(f"Les modèles Gemini requis suivants sont manquants: {missing}")
75
 
76
+
77
  except Exception as e:
78
  print(f"ERREUR Critique lors de la configuration initiale de Gemini : {e}")
79
  print("L'application fonctionnera sans les fonctionnalités IA.")
 
99
  return False
100
  # Regex simple pour les formats courants d'URL YouTube
101
  youtube_regex = re.compile(
102
+ r'(https?://)?(www.)?' # Protocole et www optionnels
103
+ r'(youtube|youtu|youtube-nocookie).(com|be)/' # Domaines youtube.com, youtu.be, etc.
104
+ r'(watch?v=|embed/|v/|.+?v=)?' # Différents chemins possibles
105
  r'([^&=%\?]{11})') # L'ID vidéo de 11 caractères
106
  return youtube_regex.match(url) is not None
107
 
108
  # --- Fonction d'Upload Vidéo avec Polling ---
109
+
110
  def upload_video_with_polling(filepath, mime_type, max_wait_seconds=300, poll_interval=10):
111
  """
112
  Upload une vidéo via client.files.upload et attend son traitement.
 
124
 
125
  start_time = time.time()
126
  # Boucle de polling tant que l'état est "PROCESSING"
127
+ while video_file.state == genai.types.FileState.PROCESSING:
128
  elapsed_time = time.time() - start_time
129
  # Vérifie le timeout
130
  if elapsed_time > max_wait_seconds:
 
136
  video_file = gemini_client.files.get(name=video_file.name)
137
 
138
  # Vérifie l'état final après la boucle
139
+ if video_file.state == genai.types.FileState.FAILED:
140
  print(f"ERREUR: Le traitement de la vidéo a échoué. État: {video_file.state.name}")
141
  raise ValueError("Le traitement de la vidéo a échoué côté serveur.")
142
 
143
+ if video_file.state == genai.types.FileState.ACTIVE:
144
  print(f"Traitement vidéo terminé avec succès: {video_file.uri}")
145
  return video_file # Retourne l'objet fichier SDK réussi
146
 
 
161
  raise # Relance l'exception originale pour qu'elle soit gérée par l'appelant
162
 
163
  # --- Fonctions de Recherche Web (inchangées - implémentez si nécessaire) ---
164
+
165
  def perform_web_search(query):
166
  """Effectue une recherche web via l'API Serper (Exemple)."""
167
  serper_api_key = os.getenv("SERPER_API_KEY")
 
178
  """Met en forme les résultats de recherche (Exemple)."""
179
  if not data: return "Aucun résultat de recherche web pertinent."
180
  # ... (votre implémentation du formatage) ...
181
+ results = ["Résultats Web:"]
182
  if data.get('organic'):
183
  for item in data['organic'][:3]:
184
  results.append(f"- {item.get('title', '')}: {item.get('snippet', '')}")
185
+ return "\n".join(results)
 
186
 
187
  # --- Préparation Historique (inchangé) ---
188
+
189
  def prepare_gemini_history(chat_history):
190
  """Convertit l'historique de session pour l'API Gemini (texte seulement)."""
191
  gemini_history = []
 
280
  # Appel bloquant qui attend le traitement
281
  processed_media_file = upload_video_with_polling(filepath, mime_type)
282
  # Crée le Part Gemini à partir de l'objet File retourné
283
+ uploaded_media_part = genai.types.Part(file_data=processed_media_file)
284
  else:
285
  print(" Traitement FICHIER standard...")
286
  # Utilise l'upload global plus simple pour les non-vidéos
287
  processed_media_file = genai.upload_file(path=filepath, mime_type=mime_type)
288
+ uploaded_media_part = genai.types.Part(file_data=processed_media_file)
289
  print(f" Part Média ({media_type}) créé: {processed_media_file.uri}")
290
 
291
  # 2. Sinon, traiter l'URL YouTube si fournie et valide
 
303
  youtube_uri = youtube_url # Utilise l'URL validée comme URI
304
  # Crée un Part FileData directement à partir de l'URI
305
  # On peut spécifier un mime_type générique, Gemini gère les liens YT
306
+ uploaded_media_part = genai.types.Part(
307
+ file_data=genai.types.FileData(file_uri=youtube_uri, mime_type="video/mp4")
308
  )
309
  print(f" Part YouTube créé pour: {youtube_uri}")
310
  # Ajoute un prompt par défaut si l'utilisateur n'en a pas mis
 
357
  # Construit un prompt enrichi
358
  final_prompt_for_gemini = f"""Basé sur la question suivante et les informations web ci-dessous, fournis une réponse complète.
359
 
360
+ Question Originale:
361
+ "{prompt}"
362
 
363
+ Informations Web Pertinentes:
364
+ --- DEBUT RESULTATS WEB ---
365
+ {formatted_results}
366
+ --- FIN RESULTATS WEB ---
367
 
368
+ Réponse:"""
369
  print(" Prompt enrichi avec les résultats web.")
370
  else:
371
  print(" Aucun résultat de recherche web trouvé ou pertinent.")
372
 
373
  # Ajouter la partie texte (originale ou enrichie) s'il y a du texte
374
  if final_prompt_for_gemini:
375
+ current_gemini_parts.append(genai.types.Part(text=final_prompt_for_gemini))
376
 
377
  # Vérification de sécurité : il doit y avoir au moins une partie (média ou texte)
378
  if not current_gemini_parts:
 
391
  print(f" Modèle sélectionné: {selected_model_name}")
392
 
393
  # Crée l'instance du modèle spécifique pour cette requête
394
+ active_model = gemini_client.models.generate_content(
395
+ model_name=selected_model_name,
396
+ contents = contents_for_gemini, # Virgule ajoutée ici
397
+ config = genai.types.GenerateContentConfig(
398
+ system_instruction=SYSTEM_INSTRUCTION,
399
+ safety_settings=[
400
+
401
+ genai.types.SafetySetting(
402
+ category=genai.types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
403
+ threshold=genai.types.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
404
+ ),
405
+ genai.types.SafetySetting(
406
+ category=genai.types.HarmCategory.HARM_CATEGORY_HARASSMENT,
407
+ threshold=genai.types.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
408
+ ),
409
+ genai.types.SafetySetting(
410
+ category=genai.types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
411
+ threshold=genai.types.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
412
+ ),
413
+ genai.types.SafetySetting(
414
+ category=genai.types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
415
+ threshold=genai.types.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
416
+ )
417
+ ]
418
+ ),
419
  )
 
 
 
420
 
421
  print(f" Envoi de la requête à {selected_model_name} ({len(contents_for_gemini)} messages/tours)...")
422
+ # Appel API (non-streamé pour correspondre au code précédent)
 
423
  response = active_model
424
 
425
  # --- Traitement de la Réponse ---
 
486
  except OSError as e:
487
  print(f" ERREUR lors de la suppression du fichier temporaire '{filepath_to_delete}': {e}")
488
 
 
489
  @app.route('/clear', methods=['POST'])
490
  def clear_chat():
491
  """Efface l'historique de chat dans la session."""
 
503
  flash("Conversation effacée.", "info")
504
  return redirect(url_for('root')) # Redirige vers la page d'accueil
505
 
 
506
  # --- Démarrage de l'application Flask ---
507
+
508
  if __name__ == '__main__':
509
  print("Démarrage du serveur Flask...")
510
  # Utiliser un port différent si 5000 est déjà pris (ex: 5001)