Docfile commited on
Commit
fe12a9c
·
verified ·
1 Parent(s): fac1807

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -403
app.py DELETED
@@ -1,403 +0,0 @@
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
- import google.generativeai as genai
7
- from werkzeug.utils import secure_filename
8
- import markdown # Pour convertir la réponse en HTML
9
- from flask_session import Session # <-- Importer Session
10
- import pprint # Pour un affichage plus lisible des structures complexes (optionnel)
11
-
12
- # --- Configuration Initiale ---
13
- load_dotenv()
14
-
15
- app = Flask(__name__)
16
-
17
- # --- Configuration Flask Standard ---
18
- # Clé secrète FORTEMENT recommandée (vous l'avez déjà)
19
- # Gardez-la secrète en production !
20
- app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'une-super-cle-secrete-a-changer')
21
-
22
- # Configuration pour les uploads (vous l'avez déjà)
23
- UPLOAD_FOLDER = 'temp'
24
- ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg'}
25
- app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
26
- app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024 # Limite de taille (ex: 25MB)
27
-
28
- # Créer le dossier temp s'il n'existe pas (vous l'avez déjà)
29
- os.makedirs(UPLOAD_FOLDER, exist_ok=True)
30
- print(f"Dossier d'upload configuré : {os.path.abspath(UPLOAD_FOLDER)}")
31
-
32
- # --- Configuration pour Flask-Session (Backend Filesystem) ---
33
- app.config['SESSION_TYPE'] = 'filesystem' # Indique d'utiliser le stockage par fichiers
34
- app.config['SESSION_PERMANENT'] = False # La session expire quand le navigateur est fermé
35
- app.config['SESSION_USE_SIGNER'] = True # Signe l'ID de session dans le cookie pour sécurité
36
- app.config['SESSION_FILE_DIR'] = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'flask_session') # Chemin où stocker les fichiers de session
37
-
38
- app.config['SESSION_COOKIE_SAMESITE'] = 'None'
39
- # Nécessite HTTPS pour que 'None' fonctionne
40
- app.config['SESSION_COOKIE_SECURE'] = True
41
-
42
- # Crée le dossier pour les sessions filesystem s'il n'existe pas
43
- os.makedirs(app.config['SESSION_FILE_DIR'], exist_ok=True)
44
- print(f"Dossier pour les sessions serveur configuré : {app.config['SESSION_FILE_DIR']}")
45
-
46
- # --- Initialisation de Flask-Session ---
47
- server_session = Session(app)
48
-
49
- # --- Configuration de l'API Gemini ---
50
- MODEL_FLASH = 'gemini-2.0-flash' # Mise à jour des modèles
51
- MODEL_PRO = 'gemini-2.5-pro-exp-03-25' # Mise à jour des modèles
52
- 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."
53
- SAFETY_SETTINGS = [
54
- {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
55
- {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
56
- {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"},
57
- {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
58
- ]
59
- GEMINI_CONFIGURED = False
60
- try:
61
- gemini_api_key = os.getenv("GOOGLE_API_KEY")
62
- if not gemini_api_key:
63
- print("ERREUR: Clé API GOOGLE_API_KEY manquante dans le fichier .env")
64
- else:
65
- genai.configure(api_key=gemini_api_key)
66
- models_list = [m.name for m in genai.list_models()]
67
- if f'models/{MODEL_FLASH}' in models_list and f'models/{MODEL_PRO}' in models_list:
68
- print(f"Configuration Gemini effectuée. Modèles requis ({MODEL_FLASH}, {MODEL_PRO}) disponibles.")
69
- print(f"System instruction: {SYSTEM_INSTRUCTION}")
70
- GEMINI_CONFIGURED = True
71
- else:
72
- print(f"ERREUR: Les modèles requis ({MODEL_FLASH}, {MODEL_PRO}) ne sont pas tous disponibles via l'API.")
73
- print(f"Modèles trouvés: {models_list}")
74
-
75
- except Exception as e:
76
- print(f"ERREUR Critique lors de la configuration initiale de Gemini : {e}")
77
- print("L'application fonctionnera sans les fonctionnalités IA.")
78
-
79
- # --- Fonctions Utilitaires ---
80
-
81
- def allowed_file(filename):
82
- """Vérifie si l'extension du fichier est autorisée."""
83
- return '.' in filename and \
84
- filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
85
-
86
- def prepare_gemini_history(chat_history):
87
- """Convertit l'historique stocké en session au format attendu par Gemini API."""
88
- print(f"--- DEBUG [prepare_gemini_history]: Entrée avec {len(chat_history)} messages") # LOG 1
89
- gemini_history = []
90
- for i, message in enumerate(list(chat_history)): # Utiliser list() pour itérer sur une copie
91
- role = 'user' if message.get('role') == 'user' else 'model'
92
- text_part = message.get('raw_text')
93
- # Log détaillé pour chaque message traité
94
- print(f" [prepare_gemini_history] Message {i} (rôle session: {message.get('role')}, rôle gemini: {role}): raw_text présent? {'Oui' if text_part is not None else 'NON'}, contenu début: '{str(text_part)[:60]}...'") # LOG 2
95
-
96
- if text_part: # Important: Ne pas ajouter de messages vides à l'historique Gemini
97
- parts = [text_part]
98
- gemini_history.append({'role': role, 'parts': parts})
99
- else:
100
- # Log si un message est ignoré car vide
101
- print(f" AVERTISSEMENT [prepare_gemini_history]: raw_text vide ou absent pour le message {i}, ignoré pour l'historique Gemini.") # LOG 3
102
-
103
- print(f"--- DEBUG [prepare_gemini_history]: Sortie avec {len(gemini_history)} messages formatés pour Gemini") # LOG 4
104
- return gemini_history
105
-
106
- # --- Routes Flask ---
107
-
108
- @app.route('/')
109
- def root():
110
- """Sert la page HTML principale."""
111
- print("--- LOG: Appel route '/' ---")
112
- return render_template('index.html')
113
-
114
- @app.route('/api/history', methods=['GET'])
115
- def get_history():
116
- """Fournit l'historique de chat stocké en session au format JSON."""
117
- print("\n--- DEBUG [/api/history]: Début requête GET ---") # LOG 5
118
- if 'chat_history' not in session:
119
- session['chat_history'] = []
120
- print(" [/api/history]: Session 'chat_history' initialisée (vide).")
121
-
122
- display_history = []
123
- current_history = session.get('chat_history', [])
124
- print(f" [/api/history]: Historique récupéré de la session serveur: {len(current_history)} messages.") # LOG 6
125
-
126
- # Optionnel: Afficher la structure brute pour un debug profond
127
- # print(" [/api/history]: Contenu brut de l'historique session:")
128
- # pprint.pprint(current_history)
129
-
130
- for i, msg in enumerate(current_history):
131
- # Vérifier la structure de chaque message récupéré
132
- if isinstance(msg, dict) and 'role' in msg and 'text' in msg:
133
- display_history.append({
134
- 'role': msg.get('role'),
135
- 'text': msg.get('text') # On envoie bien le HTML ('text') au frontend
136
- })
137
- else:
138
- # Log si un message dans la session est mal formé
139
- print(f" AVERTISSEMENT [/api/history]: Format invalide dans l'historique session au message {i}: {msg}") # LOG 7
140
-
141
- print(f" [/api/history]: Historique préparé pour le frontend: {len(display_history)} messages.") # LOG 8
142
- return jsonify({'success': True, 'history': display_history})
143
-
144
- @app.route('/api/chat', methods=['POST'])
145
- def chat_api():
146
- """Gère les nouvelles requêtes de chat via AJAX."""
147
- print(f"\n---===================================---")
148
- print(f"--- DEBUG [/api/chat]: Nouvelle requête POST ---")
149
-
150
- if not GEMINI_CONFIGURED:
151
- print("--- ERREUR [/api/chat]: Tentative d'appel sans configuration Gemini valide.")
152
- return jsonify({'success': False, 'error': "Le service IA n'est pas configuré correctement."}), 503
153
-
154
- # Récupération des données du formulaire
155
- prompt = request.form.get('prompt', '').strip()
156
- use_web_search = request.form.get('web_search', 'false').lower() == 'true'
157
- file = request.files.get('file')
158
- use_advanced = request.form.get('advanced_reasoning', 'false').lower() == 'true'
159
-
160
- print(f" [/api/chat]: Prompt reçu: '{prompt[:50]}...'")
161
- print(f" [/api/chat]: Recherche Web: {use_web_search}, Raisonnement Avancé: {use_advanced}")
162
- print(f" [/api/chat]: Fichier: {file.filename if file else 'Aucun'}")
163
-
164
- # Validation
165
- if not prompt and not file:
166
- print("--- ERREUR [/api/chat]: Prompt et fichier vides.")
167
- return jsonify({'success': False, 'error': 'Veuillez fournir un message ou un fichier.'}), 400
168
-
169
- # --- Log de l'état de l'historique AVANT toute modification ---
170
- if 'chat_history' not in session:
171
- session['chat_history'] = []
172
- history_before_user_add = list(session.get('chat_history', [])) # Copie pour le log
173
- print(f"--- DEBUG [/api/chat]: Historique en session AVANT ajout user message: {len(history_before_user_add)} messages") # LOG 9
174
- # Optionnel: Afficher les derniers messages pour voir le contexte précédent
175
- # if history_before_user_add:
176
- # print(" [/api/chat]: Dernier(s) message(s) avant ajout:")
177
- # pprint.pprint(history_before_user_add[-2:]) # Afficher les 2 derniers
178
-
179
- uploaded_gemini_file = None
180
- uploaded_filename = None
181
- filepath_to_delete = None
182
-
183
- # --- Gestion de l'upload de fichier (avec logs) ---
184
- if file and file.filename != '':
185
- print(f"--- LOG [/api/chat]: Traitement du fichier '{file.filename}'")
186
- if allowed_file(file.filename):
187
- try:
188
- filename = secure_filename(file.filename)
189
- filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
190
- file.save(filepath)
191
- filepath_to_delete = filepath
192
- uploaded_filename = filename
193
- print(f" [/api/chat]: Fichier '{filename}' sauvegardé dans '{filepath}'")
194
- mime_type = mimetypes.guess_type(filepath)[0] or 'application/octet-stream'
195
- print(f" [/api/chat]: Upload Google AI (Mime: {mime_type})...")
196
- uploaded_gemini_file = genai.upload_file(path=filepath, mime_type=mime_type)
197
- print(f" [/api/chat]: Fichier Google AI '{uploaded_gemini_file.name}' uploadé.")
198
- except Exception as e:
199
- print(f"--- ERREUR [/api/chat]: Échec traitement/upload fichier '{filename}': {e}")
200
- if filepath_to_delete and os.path.exists(filepath_to_delete):
201
- try: os.remove(filepath_to_delete)
202
- except OSError as del_e: print(f" Erreur suppression fichier temp après erreur: {del_e}")
203
- return jsonify({'success': False, 'error': f"Erreur traitement fichier: {e}"}), 500
204
- else:
205
- print(f"--- ERREUR [/api/chat]: Type de fichier non autorisé: {file.filename}")
206
- return jsonify({'success': False, 'error': f"Type de fichier non autorisé."}), 400
207
-
208
- # --- Préparation du message utilisateur pour l'historique et Gemini ---
209
- raw_user_text = prompt
210
- display_user_text = f"[{uploaded_filename}] {prompt}" if uploaded_filename and prompt else (prompt or f"[{uploaded_filename}]")
211
- user_history_entry = {
212
- 'role': 'user',
213
- 'text': display_user_text,
214
- 'raw_text': raw_user_text,
215
- }
216
-
217
- # Ajout à l'historique de session (vérifier que c'est une liste)
218
- if not isinstance(session.get('chat_history'), list):
219
- print("--- ERREUR [/api/chat]: 'chat_history' n'est pas une liste! Réinitialisation.")
220
- session['chat_history'] = []
221
- session['chat_history'].append(user_history_entry)
222
-
223
- # --- Log de l'état de l'historique APRES ajout du message utilisateur ---
224
- history_after_user_add = list(session.get('chat_history', [])) # Nouvelle copie
225
- print(f"--- DEBUG [/api/chat]: Historique en session APRES ajout user message: {len(history_after_user_add)} messages") # LOG 10
226
- # print(" [/api/chat]: Dernier message ajouté (user):")
227
- # pprint.pprint(history_after_user_add[-1])
228
-
229
-
230
- # --- Préparation des 'parts' pour l'appel Gemini ACTUEL ---
231
- current_gemini_parts = []
232
- # Gérer le cas où seul un fichier est envoyé
233
- if uploaded_gemini_file and not raw_user_text:
234
- raw_user_text = f"Décris le contenu de ce fichier : {uploaded_filename}"
235
- final_prompt_for_gemini = raw_user_text
236
- current_gemini_parts.append(uploaded_gemini_file)
237
- current_gemini_parts.append(final_prompt_for_gemini)
238
- print(f" [/api/chat]: Fichier seul détecté, prompt généré: '{final_prompt_for_gemini}'")
239
- elif uploaded_gemini_file and raw_user_text:
240
- final_prompt_for_gemini = raw_user_text
241
- current_gemini_parts.append(uploaded_gemini_file)
242
- current_gemini_parts.append(final_prompt_for_gemini)
243
- else: # Seulement du texte (ou ni texte ni fichier valide, géré plus bas)
244
- final_prompt_for_gemini = raw_user_text
245
- if final_prompt_for_gemini:
246
- current_gemini_parts.append(final_prompt_for_gemini)
247
-
248
- # --- Préparation des paramètres de configuration et modèle ---
249
- selected_model_name = MODEL_PRO if use_advanced else MODEL_FLASH
250
-
251
- # Vérifier si on a quelque chose à envoyer
252
- if not current_gemini_parts:
253
- print("--- ERREUR [/api/chat]: Aucune donnée (texte ou fichier valide) à envoyer après traitement.")
254
- if session.get('chat_history'): session['chat_history'].pop() # Retirer le user message vide/inutile
255
- return jsonify({'success': False, 'error': "Impossible d'envoyer une requête vide."}), 400
256
-
257
- # --- Appel à l'API Gemini ---
258
- try:
259
- # Préparer l'historique des messages PRÉCÉDENTS pour Gemini
260
- # Important: Utiliser une copie de l'historique SANS le dernier message utilisateur ajouté
261
- history_for_gemini_prep = list(session.get('chat_history', []))[:-1]
262
- gemini_history_to_send = prepare_gemini_history(history_for_gemini_prep)
263
-
264
- # Construire le contenu complet pour l'appel
265
- contents_for_gemini = gemini_history_to_send + [{'role': 'user', 'parts': current_gemini_parts}]
266
-
267
- # --- LOG DÉTAILLÉ : Ce qui est envoyé à l'API ---
268
- print(f"--- DEBUG [/api/chat]: Préparation de l'envoi à l'API Gemini (Modèle: {selected_model_name}) ---") # LOG 11
269
- print(f" Nombre total de tours (historique + actuel): {len(contents_for_gemini)}") # LOG 12a
270
- print(f" Nombre de messages d'historique formatés envoyés: {len(gemini_history_to_send)}") # LOG 12b
271
- print(f" Google Search activé: {use_web_search}")
272
- print(f" Contenu détaillé des 'parts' envoyées:") # LOG 13
273
- for i, turn in enumerate(contents_for_gemini):
274
- role = turn.get('role')
275
- parts_details = []
276
- for part in turn.get('parts', []):
277
- if isinstance(part, str):
278
- parts_details.append(f"Text({len(part)} chars): '{part[:60].replace(chr(10), ' ')}...'") # Remplacer newline pour log sur 1 ligne
279
- elif hasattr(part, 'name') and hasattr(part, 'mime_type'):
280
- parts_details.append(f"File(name={part.name}, mime={part.mime_type})")
281
- else:
282
- parts_details.append(f"UnknownPart({type(part)})")
283
- print(f" Turn {i} (role: {role}): {', '.join(parts_details)}")
284
- print("--------------------------------------------------------------------")
285
-
286
- # Créer l'instance du modèle et appeler l'API
287
- active_model = genai.GenerativeModel(
288
- model_name=selected_model_name,
289
- safety_settings=SAFETY_SETTINGS,
290
- system_instruction=SYSTEM_INSTRUCTION
291
- )
292
- print(f"--- LOG [/api/chat]: Envoi de la requête à {selected_model_name}...")
293
-
294
- # Modification pour utiliser google_search_retrieval si la recherche web est activée
295
- if use_web_search:
296
- print(f"--- LOG [/api/chat]: Activation Google Search Retrieval...")
297
- response = active_model.generate_content(
298
- contents=contents_for_gemini,
299
- tools='google_search'
300
- )
301
- else:
302
- response = active_model.generate_content(
303
- contents=contents_for_gemini
304
- )
305
-
306
- # --- Traitement de la réponse (avec logs) ---
307
- response_text_raw = ""
308
- response_html = ""
309
- search_metadata = None
310
-
311
- try:
312
- if response.parts:
313
- response_text_raw = response.text # .text concatène les parts textuelles
314
- print(f"--- LOG [/api/chat]: Réponse reçue de Gemini (brute, début): '{response_text_raw[:100]}...'")
315
-
316
- # Récupération des métadonnées de recherche si disponibles
317
- if use_web_search and hasattr(response, 'candidates') and response.candidates:
318
- candidate = response.candidates[0]
319
- if hasattr(candidate, 'citation_metadata'):
320
- metadata = candidate.citation_metadata
321
- search_metadata = {}
322
-
323
- # Extraire les citations et références
324
- if hasattr(metadata, 'citations'):
325
- search_pages = []
326
- for citation in metadata.citations:
327
- if hasattr(citation, 'start_index') and hasattr(citation, 'end_index'):
328
- search_pages.append({
329
- "title": citation.title if hasattr(citation, 'title') else "Sans titre",
330
- "url": citation.uri if hasattr(citation, 'uri') else "#",
331
- "snippet": f"Citation: {citation.start_index}-{citation.end_index}"
332
- })
333
- if search_pages:
334
- search_metadata["search_pages"] = search_pages
335
-
336
- print(f"--- LOG [/api/chat]: Métadonnées de citation récupérées:")
337
- if search_metadata and search_metadata.get("search_pages"):
338
- print(f" Pages: {len(search_metadata['search_pages'])} sources")
339
- else:
340
- feedback_info = f"Feedback: {response.prompt_feedback}" if response.prompt_feedback else "Pas de feedback détaillé."
341
- print(f"--- AVERTISSEMENT [/api/chat]: Réponse Gemini sans 'parts'. {feedback_info}")
342
- if response.prompt_feedback and response.prompt_feedback.block_reason:
343
- reason = response.prompt_feedback.block_reason.name
344
- response_text_raw = f"Désolé, ma réponse a été bloquée ({reason})."
345
-
346
- # Convertir le Markdown en HTML pour l'affichage
347
- response_html = markdown.markdown(response_text_raw, extensions=['extra'])
348
- print(f"--- LOG [/api/chat]: Réponse convertie en HTML")
349
-
350
- # --- Créer l'entrée d'historique et l'ajouter à la session ---
351
- assistant_history_entry = {
352
- 'role': 'assistant',
353
- 'text': response_html, # Version HTML pour affichage
354
- 'raw_text': response_text_raw, # Version brute pour l'historique Gemini
355
- 'search_metadata': search_metadata # Métadonnées de recherche
356
- }
357
-
358
- # Ajouter à l'historique en vérifiant que c'est bien une liste
359
- if not isinstance(session.get('chat_history'), list):
360
- print("--- ERREUR [/api/chat]: 'chat_history' n'est pas une liste pendant l'ajout de réponse!")
361
- session['chat_history'] = [] # Reset
362
- session['chat_history'].append(assistant_history_entry)
363
-
364
- # --- Nettoyer le fichier temporaire si nécessaire ---
365
- if filepath_to_delete and os.path.exists(filepath_to_delete):
366
- try:
367
- os.remove(filepath_to_delete)
368
- print(f"--- LOG [/api/chat]: Fichier temporaire '{filepath_to_delete}' supprimé.")
369
- except OSError as e:
370
- print(f"--- ERREUR [/api/chat]: Impossible de supprimer le fichier temporaire: {e}")
371
-
372
- # --- Préparer la réponse JSON pour le client ---
373
- response_object = {
374
- 'success': True,
375
- 'text': response_html,
376
- 'search_metadata': search_metadata
377
- }
378
- return jsonify(response_object)
379
-
380
- except Exception as process_error:
381
- print(f"--- ERREUR [/api/chat]: Traitement de la réponse: {process_error}")
382
- # Nettoyer en cas d'erreur
383
- if filepath_to_delete and os.path.exists(filepath_to_delete):
384
- try: os.remove(filepath_to_delete)
385
- except OSError as e: print(f" Erreur suppression fichier temp après erreur: {e}")
386
- return jsonify({'success': False, 'error': f"Erreur traitement réponse: {process_error}"}), 500
387
-
388
- except Exception as call_error:
389
- print(f"--- ERREUR [/api/chat]: Appel à l'API Gemini: {call_error}")
390
- # Nettoyer en cas d'erreur
391
- if filepath_to_delete and os.path.exists(filepath_to_delete):
392
- try: os.remove(filepath_to_delete)
393
- except OSError as e: print(f" Erreur suppression fichier temp après erreur: {e}")
394
- return jsonify({'success': False, 'error': f"Erreur API Gemini: {call_error}"}), 500
395
-
396
- if __name__ == '__main__':
397
- # Vérifier si la variable d'environnement API_KEY est définie
398
- if 'GOOGLE_API_KEY' not in os.environ:
399
- print("Attention: La variable d'environnement GOOGLE_API_KEY n'est pas définie.")
400
- print("Vous devrez la définir avant d'exécuter l'application:")
401
- print("export GOOGLE_API_KEY=votre_clé_api")
402
-
403
- app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))