Update app.py
Browse files
app.py
CHANGED
@@ -1,11 +1,13 @@
|
|
1 |
import os
|
2 |
import json
|
3 |
-
|
|
|
4 |
from dotenv import load_dotenv
|
5 |
import google.generativeai as genai
|
6 |
import requests
|
7 |
from werkzeug.utils import secure_filename
|
8 |
import mimetypes
|
|
|
9 |
|
10 |
load_dotenv()
|
11 |
|
@@ -23,12 +25,11 @@ try:
|
|
23 |
genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
|
24 |
safety_settings = [
|
25 |
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
|
26 |
-
|
27 |
-
{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"},
|
28 |
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
|
29 |
]
|
30 |
model = genai.GenerativeModel(
|
31 |
-
'gemini-
|
32 |
safety_settings=safety_settings,
|
33 |
system_instruction="Tu es un assistant intelligent. ton but est d'assister au mieux que tu peux. tu as été créé par Aenir et tu t'appelles Mariam"
|
34 |
)
|
@@ -43,13 +44,13 @@ def allowed_file(filename):
|
|
43 |
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
44 |
|
45 |
def perform_web_search(query):
|
46 |
-
conn_key = "
|
47 |
if not conn_key:
|
48 |
print("Clé API SERPER manquante dans .env")
|
49 |
return None
|
50 |
search_url = "https://google.serper.dev/search"
|
51 |
-
headers = {
|
52 |
-
payload = json.dumps({"q": query
|
53 |
try:
|
54 |
response = requests.post(search_url, headers=headers, data=payload, timeout=10)
|
55 |
response.raise_for_status()
|
@@ -60,230 +61,187 @@ def perform_web_search(query):
|
|
60 |
print(f"Erreur lors de la recherche web : {e}")
|
61 |
return None
|
62 |
except json.JSONDecodeError as e:
|
63 |
-
print(f"Erreur lors du décodage JSON de Serper : {e}")
|
64 |
print(f"Réponse reçue : {response.text}")
|
65 |
return None
|
66 |
|
|
|
67 |
def format_search_results(data):
|
|
|
68 |
if not data: return "Aucun résultat de recherche trouvé."
|
69 |
-
result = "Résultats de recherche web
|
70 |
-
# (
|
71 |
-
if
|
72 |
-
result +=
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
result += f"{i}. {item.get('title', 'N/A')}\n {item.get('snippet', 'N/A')}\n [{item.get('link', '#')}]\n"
|
79 |
-
# People Also Ask peut être bruyant, on peut l'omettre pour le prompt
|
80 |
return result
|
81 |
|
|
|
82 |
def prepare_gemini_history(chat_history):
|
83 |
gemini_history = []
|
84 |
for message in chat_history:
|
85 |
role = 'user' if message['role'] == 'user' else 'model'
|
86 |
-
#
|
87 |
-
|
88 |
-
parts
|
89 |
-
|
90 |
-
|
|
|
91 |
return gemini_history
|
92 |
|
93 |
-
|
94 |
# --- Routes Flask ---
|
95 |
|
96 |
@app.route('/', methods=['GET'])
|
97 |
def index():
|
|
|
98 |
if 'chat_history' not in session:
|
99 |
session['chat_history'] = []
|
100 |
-
|
101 |
-
|
|
|
102 |
|
103 |
-
#
|
104 |
-
|
105 |
-
error = session.pop('error', None) # Utilise pop pour ne l'afficher qu'une fois
|
106 |
|
107 |
return render_template(
|
108 |
'index.html',
|
109 |
-
chat_history=
|
110 |
-
web_search_active=
|
111 |
-
error=error,
|
112 |
-
processing_message=processing # Passer l'état de traitement
|
113 |
)
|
114 |
|
115 |
-
@app.route('/chat', methods=['POST'])
|
116 |
-
def
|
|
|
117 |
if not model:
|
118 |
-
|
119 |
-
return redirect(url_for('index'))
|
120 |
|
121 |
prompt = request.form.get('prompt', '').strip()
|
122 |
-
|
123 |
file = request.files.get('file')
|
124 |
-
uploaded_gemini_file = None
|
125 |
-
|
126 |
-
|
127 |
-
# Marquer le début du traitement DANS la session
|
128 |
-
session['processing'] = True
|
129 |
-
session['processing_web_search'] = False # Reset au début
|
130 |
-
session.modified = True # Sauvegarder la session maintenant
|
131 |
|
132 |
if not prompt and not file:
|
133 |
-
|
134 |
-
|
135 |
-
|
|
|
|
|
|
|
|
|
|
|
136 |
|
137 |
-
# --- Gestion Upload ---
|
138 |
if file and file.filename != '':
|
139 |
if allowed_file(file.filename):
|
140 |
try:
|
141 |
filename = secure_filename(file.filename)
|
142 |
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
143 |
file.save(filepath)
|
144 |
-
|
145 |
-
|
146 |
|
147 |
-
|
|
|
148 |
gemini_file_obj = genai.upload_file(path=filepath, mime_type=mime_type)
|
149 |
-
uploaded_gemini_file = gemini_file_obj
|
150 |
-
|
151 |
-
print(f"Fichier {filename}
|
152 |
-
|
|
|
153 |
# os.remove(filepath)
|
154 |
|
155 |
except Exception as e:
|
156 |
print(f"Erreur upload fichier : {e}")
|
157 |
-
|
158 |
-
|
159 |
else:
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
#
|
176 |
-
session
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
try:
|
186 |
-
|
187 |
-
if uploaded_gemini_file:
|
188 |
-
final_prompt_parts.append(uploaded_gemini_file)
|
189 |
-
|
190 |
-
# Recherche Web si activée ET prompt textuel existe
|
191 |
-
if session['web_search'] and prompt:
|
192 |
-
print("Activation recherche web...")
|
193 |
-
session['processing_web_search'] = True # Indiquer que la recherche est active
|
194 |
-
session.modified = True
|
195 |
-
# !! Important: Forcer la sauvegarde de session AVANT l'appel bloquant
|
196 |
-
# Ceci est un workaround car Flask sauvegarde normalement en fin de requête.
|
197 |
-
# Pour une vraie MAJ live, il faudrait AJAX/WebSockets.
|
198 |
-
from flask.sessions import SessionInterface
|
199 |
-
app.session_interface.save_session(app, session, Response()) # Pseudo-réponse
|
200 |
-
|
201 |
-
web_results = perform_web_search(prompt)
|
202 |
-
if web_results:
|
203 |
-
formatted_results = format_search_results(web_results)
|
204 |
-
text_for_gemini = f"Question originale: {prompt}\n\n{formatted_results}\n\nRéponds à la question originale en te basant sur ces informations et ta connaissance."
|
205 |
-
print("Prompt enrichi avec recherche web.")
|
206 |
-
else:
|
207 |
-
print("Pas de résultats web ou erreur.")
|
208 |
-
text_for_gemini = prompt # Garde le prompt original
|
209 |
-
session['processing_web_search'] = False # Recherche terminée
|
210 |
-
|
211 |
-
# Ajouter le texte (original ou enrichi) aux parts
|
212 |
-
if text_for_gemini: # S'assurer qu'on ajoute pas une string vide
|
213 |
-
final_prompt_parts.append(text_for_gemini)
|
214 |
-
|
215 |
-
# Préparer l'historique pour Gemini en utilisant les données stockées
|
216 |
-
# On prend tout sauf le dernier message utilisateur qui est en cours de traitement
|
217 |
gemini_history = prepare_gemini_history(session['chat_history'][:-1])
|
|
|
218 |
|
219 |
-
|
|
|
220 |
|
221 |
-
|
222 |
-
|
223 |
-
# Cas où seul un fichier a été envoyé sans prompt textuel,
|
224 |
-
# et la recherche web n'était pas activée ou n'a rien retourné.
|
225 |
-
# Il faut quand même envoyer qqchose, par ex., demander de décrire le fichier.
|
226 |
-
if uploaded_gemini_file:
|
227 |
-
final_prompt_parts.append("Décris le contenu de ce fichier.")
|
228 |
-
else:
|
229 |
-
# Ne devrait pas arriver vu les checks précédents, mais par sécurité
|
230 |
-
raise ValueError("Tentative d'envoyer une requête vide à Gemini.")
|
231 |
|
|
|
|
|
|
|
|
|
232 |
|
233 |
-
|
234 |
-
response = model.generate_content(full_conversation)
|
235 |
|
236 |
-
#
|
237 |
-
|
238 |
-
print(f"--- Réponse Gemini reçue ---")
|
239 |
-
|
240 |
-
# Ajouter la réponse à l'historique (version simple pour affichage)
|
241 |
-
session['chat_history'].append({
|
242 |
-
'role': 'assistant',
|
243 |
-
'text': response_text,
|
244 |
-
'text_for_gemini': response_text # Pour symétrie, même si on ne réutilise pas directement
|
245 |
-
# Pas de 'gemini_file' pour les réponses du modèle
|
246 |
-
})
|
247 |
session.modified = True
|
248 |
|
|
|
|
|
249 |
except Exception as e:
|
250 |
-
print(f"Erreur lors de l'appel à Gemini
|
251 |
-
|
252 |
-
|
253 |
-
# pour éviter boucle d'erreur si le prompt est problématique.
|
254 |
-
if session['chat_history'] and session['chat_history'][-1]['role'] == 'user':
|
255 |
-
session['chat_history'].pop()
|
256 |
-
session.modified = True
|
257 |
-
finally:
|
258 |
-
# Marquer la fin du traitement DANS la session
|
259 |
-
session['processing'] = False
|
260 |
-
session.pop('processing_web_search', None) # Nettoyer au cas où
|
261 |
session.modified = True
|
262 |
-
|
263 |
|
264 |
-
return redirect(url_for('index'))
|
265 |
|
266 |
-
# Ajouter une route pour effacer la conversation
|
267 |
@app.route('/clear', methods=['POST'])
|
268 |
def clear_chat():
|
|
|
269 |
session.pop('chat_history', None)
|
270 |
-
session.pop('web_search', None)
|
271 |
-
session.pop('processing', None) # Nettoyer aussi l'état de process
|
272 |
-
session.pop('error', None)
|
273 |
print("Historique de chat effacé.")
|
274 |
-
flash("Conversation effacée.", "info") #
|
275 |
return redirect(url_for('index'))
|
276 |
|
277 |
-
#
|
278 |
-
# Attention: N'est PAS une vraie réponse HTTP. A utiliser avec prudence.
|
279 |
-
class Response:
|
280 |
-
def __init__(self):
|
281 |
-
self.headers = {}
|
282 |
-
def set_cookie(self, key, value, **kwargs):
|
283 |
-
# Potentiellement stocker les cookies si nécessaire, mais ici on ignore
|
284 |
-
pass
|
285 |
-
|
286 |
-
|
287 |
if __name__ == '__main__':
|
288 |
-
|
289 |
-
app.run(debug=True, host='0.0.0.0', port=5002)
|
|
|
1 |
import os
|
2 |
import json
|
3 |
+
# Importer jsonify pour les réponses API
|
4 |
+
from flask import Flask, render_template, request, session, redirect, url_for, flash, jsonify
|
5 |
from dotenv import load_dotenv
|
6 |
import google.generativeai as genai
|
7 |
import requests
|
8 |
from werkzeug.utils import secure_filename
|
9 |
import mimetypes
|
10 |
+
import markdown # <-- Importer la bibliothèque Markdown
|
11 |
|
12 |
load_dotenv()
|
13 |
|
|
|
25 |
genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
|
26 |
safety_settings = [
|
27 |
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
|
28 |
+
# ... (autres catégories)
|
|
|
29 |
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
|
30 |
]
|
31 |
model = genai.GenerativeModel(
|
32 |
+
'gemini-1.5-flash',
|
33 |
safety_settings=safety_settings,
|
34 |
system_instruction="Tu es un assistant intelligent. ton but est d'assister au mieux que tu peux. tu as été créé par Aenir et tu t'appelles Mariam"
|
35 |
)
|
|
|
44 |
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
45 |
|
46 |
def perform_web_search(query):
|
47 |
+
conn_key = os.getenv("SERPER_API_KEY")
|
48 |
if not conn_key:
|
49 |
print("Clé API SERPER manquante dans .env")
|
50 |
return None
|
51 |
search_url = "https://google.serper.dev/search"
|
52 |
+
headers = {'X-API-KEY': conn_key, 'Content-Type': 'application/json'}
|
53 |
+
payload = json.dumps({"q": query})
|
54 |
try:
|
55 |
response = requests.post(search_url, headers=headers, data=payload, timeout=10)
|
56 |
response.raise_for_status()
|
|
|
61 |
print(f"Erreur lors de la recherche web : {e}")
|
62 |
return None
|
63 |
except json.JSONDecodeError as e:
|
64 |
+
print(f"Erreur lors du décodage de la réponse JSON de Serper : {e}")
|
65 |
print(f"Réponse reçue : {response.text}")
|
66 |
return None
|
67 |
|
68 |
+
|
69 |
def format_search_results(data):
|
70 |
+
# (Fonction inchangée - assurez-vous qu'elle renvoie du texte formaté)
|
71 |
if not data: return "Aucun résultat de recherche trouvé."
|
72 |
+
result = "Résultats de recherche web :\n"
|
73 |
+
# ... (reste de la fonction inchangé) ...
|
74 |
+
if 'organic' in data and data['organic']:
|
75 |
+
result += "\n## Résultats principaux :\n"
|
76 |
+
for i, item in enumerate(data['organic'][:3], 1):
|
77 |
+
result += f"{i}. **{item.get('title', 'N/A')}**\n" # Format Markdown
|
78 |
+
result += f" {item.get('snippet', 'N/A')}\n"
|
79 |
+
result += f" [Lien]({item.get('link', '#')})\n\n" # Lien Markdown
|
80 |
+
# ... (reste de la fonction inchangé) ...
|
|
|
|
|
81 |
return result
|
82 |
|
83 |
+
|
84 |
def prepare_gemini_history(chat_history):
|
85 |
gemini_history = []
|
86 |
for message in chat_history:
|
87 |
role = 'user' if message['role'] == 'user' else 'model'
|
88 |
+
# Utiliser le 'raw_text' pour Gemini, pas le HTML rendu
|
89 |
+
text_part = message.get('raw_text', message.get('text', ''))
|
90 |
+
parts = [text_part]
|
91 |
+
if message.get('gemini_file_ref'): # Utiliser une clé différente pour la référence interne
|
92 |
+
parts.insert(0, message['gemini_file_ref'])
|
93 |
+
gemini_history.append({'role': role, 'parts': parts})
|
94 |
return gemini_history
|
95 |
|
|
|
96 |
# --- Routes Flask ---
|
97 |
|
98 |
@app.route('/', methods=['GET'])
|
99 |
def index():
|
100 |
+
"""Affiche la page principale du chat."""
|
101 |
if 'chat_history' not in session:
|
102 |
session['chat_history'] = []
|
103 |
+
# L'état 'web_search' est maintenant géré côté client initialement,
|
104 |
+
# mais on peut le pré-cocher depuis la session si on veut le persister.
|
105 |
+
web_search_initial_state = session.get('web_search', False)
|
106 |
|
107 |
+
# On ne passe que l'historique nécessaire à l'affichage initial
|
108 |
+
display_history = session['chat_history']
|
|
|
109 |
|
110 |
return render_template(
|
111 |
'index.html',
|
112 |
+
chat_history=display_history,
|
113 |
+
web_search_active=web_search_initial_state
|
|
|
|
|
114 |
)
|
115 |
|
116 |
+
@app.route('/api/chat', methods=['POST'])
|
117 |
+
def chat_api():
|
118 |
+
"""Gère les requêtes de chat AJAX et retourne du JSON."""
|
119 |
if not model:
|
120 |
+
return jsonify({'success': False, 'error': "Le modèle Gemini n'est pas configuré."}), 500
|
|
|
121 |
|
122 |
prompt = request.form.get('prompt', '').strip()
|
123 |
+
use_web_search = request.form.get('web_search') == 'true'
|
124 |
file = request.files.get('file')
|
125 |
+
uploaded_gemini_file = None # Référence à l'objet fichier uploadé à Gemini
|
126 |
+
uploaded_filename = None # Juste le nom pour référence
|
|
|
|
|
|
|
|
|
|
|
127 |
|
128 |
if not prompt and not file:
|
129 |
+
return jsonify({'success': False, 'error': 'Message ou fichier requis.'}), 400
|
130 |
+
|
131 |
+
# Mettre à jour l'état de la recherche web dans la session si on veut le persister
|
132 |
+
session['web_search'] = use_web_search
|
133 |
+
|
134 |
+
# --- Gestion de l'upload ---
|
135 |
+
user_message_parts_for_gemini = []
|
136 |
+
raw_user_text = prompt # Texte brut pour l'historique Gemini
|
137 |
|
|
|
138 |
if file and file.filename != '':
|
139 |
if allowed_file(file.filename):
|
140 |
try:
|
141 |
filename = secure_filename(file.filename)
|
142 |
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
143 |
file.save(filepath)
|
144 |
+
uploaded_filename = filename # Garder le nom
|
145 |
+
print(f"Fichier sauvegardé : {filepath}")
|
146 |
|
147 |
+
mime_type = mimetypes.guess_type(filepath)[0] or 'application/octet-stream'
|
148 |
+
print(f"Upload vers Gemini (mime: {mime_type})...")
|
149 |
gemini_file_obj = genai.upload_file(path=filepath, mime_type=mime_type)
|
150 |
+
uploaded_gemini_file = gemini_file_obj # Garder l'objet pour l'API
|
151 |
+
user_message_parts_for_gemini.append(uploaded_gemini_file) # Ajouter l'objet fichier pour Gemini
|
152 |
+
print(f"Fichier {filename} uploadé vers Gemini.")
|
153 |
+
|
154 |
+
# Optionnel: Supprimer le fichier local
|
155 |
# os.remove(filepath)
|
156 |
|
157 |
except Exception as e:
|
158 |
print(f"Erreur upload fichier : {e}")
|
159 |
+
# Ne pas bloquer, mais renvoyer une erreur partielle si nécessaire
|
160 |
+
return jsonify({'success': False, 'error': f"Erreur traitement fichier: {e}"}), 500
|
161 |
else:
|
162 |
+
return jsonify({'success': False, 'error': 'Type de fichier non autorisé.'}), 400
|
163 |
+
|
164 |
+
# --- Préparation du message utilisateur et de l'historique ---
|
165 |
+
# Ajouter le texte après le fichier pour Gemini
|
166 |
+
user_message_parts_for_gemini.append(prompt)
|
167 |
+
|
168 |
+
# Stocker le message utilisateur dans l'historique de session
|
169 |
+
# Stocker le texte brut et la référence au fichier séparément
|
170 |
+
user_history_entry = {
|
171 |
+
'role': 'user',
|
172 |
+
'text': f"[Fichier: {uploaded_filename}]\n\n{prompt}" if uploaded_filename else prompt, # Pour affichage
|
173 |
+
'raw_text': raw_user_text, # Texte brut pour Gemini
|
174 |
+
}
|
175 |
+
if uploaded_gemini_file:
|
176 |
+
# Ne stockez pas l'objet complet dans la session, juste une réf si nécessaire,
|
177 |
+
# ou reconstruisez l'historique sans la référence de fichier si non critique.
|
178 |
+
# Pour simplifier, on ne stocke pas la référence objet dans la session ici.
|
179 |
+
# On pourrait stocker gemini_file_obj.name si on a besoin de le réutiliser plus tard.
|
180 |
+
# user_history_entry['gemini_file_ref_name'] = uploaded_gemini_file.name
|
181 |
+
pass # L'objet est dans user_message_parts_for_gemini pour l'appel API actuel
|
182 |
+
|
183 |
+
if 'chat_history' not in session: session['chat_history'] = []
|
184 |
+
session['chat_history'].append(user_history_entry)
|
185 |
+
session.modified = True
|
186 |
+
|
187 |
+
|
188 |
+
# --- Web Search ---
|
189 |
+
final_prompt_text = prompt
|
190 |
+
if use_web_search and prompt: # Recherche uniquement si texte ET activé
|
191 |
+
print("Recherche web en cours pour:", prompt)
|
192 |
+
web_results = perform_web_search(prompt)
|
193 |
+
if web_results:
|
194 |
+
formatted_results = format_search_results(web_results)
|
195 |
+
# Mettre à jour le texte à envoyer à Gemini (le fichier est déjà dans les parts)
|
196 |
+
final_prompt_text = f"Question originale: {prompt}\n\n{formatted_results}\n\nBasé sur ces informations et ta connaissance générale, réponds à la question originale."
|
197 |
+
print("Prompt modifié avec résultats web.")
|
198 |
+
# Remplacer le texte dans la liste des parts
|
199 |
+
user_message_parts_for_gemini[-1] = final_prompt_text
|
200 |
+
else:
|
201 |
+
print("Pas de résultats web ou erreur.")
|
202 |
+
|
203 |
+
# --- Appel à Gemini ---
|
204 |
try:
|
205 |
+
# Préparer l'historique SANS le dernier message utilisateur (il est dans `contents`)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
206 |
gemini_history = prepare_gemini_history(session['chat_history'][:-1])
|
207 |
+
print(f"Historique Gemini: {len(gemini_history)} messages.")
|
208 |
|
209 |
+
# Construire le contenu pour generate_content
|
210 |
+
contents = gemini_history + [{'role': 'user', 'parts': user_message_parts_for_gemini}]
|
211 |
|
212 |
+
print("Appel à model.generate_content...")
|
213 |
+
response = model.generate_content(contents)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
214 |
|
215 |
+
# --- Traitement Réponse ---
|
216 |
+
response_text_raw = response.text
|
217 |
+
# Convertir Markdown en HTML pour un meilleur rendu
|
218 |
+
response_html = markdown.markdown(response_text_raw, extensions=['fenced_code', 'tables'])
|
219 |
|
220 |
+
print(f"Réponse Gemini reçue (premiers 500 chars): {response_text_raw[:500]}")
|
|
|
221 |
|
222 |
+
# Ajouter la réponse à l'historique de session
|
223 |
+
session['chat_history'].append({'role': 'assistant', 'text': response_html, 'raw_text': response_text_raw})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
224 |
session.modified = True
|
225 |
|
226 |
+
return jsonify({'success': True, 'message': response_html}) # Envoyer le HTML au client
|
227 |
+
|
228 |
except Exception as e:
|
229 |
+
print(f"Erreur lors de l'appel à Gemini : {e}")
|
230 |
+
# Retirer le dernier message utilisateur de l'historique en cas d'échec
|
231 |
+
session['chat_history'].pop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
232 |
session.modified = True
|
233 |
+
return jsonify({'success': False, 'error': f"Erreur communication IA: {e}"}), 500
|
234 |
|
|
|
235 |
|
|
|
236 |
@app.route('/clear', methods=['POST'])
|
237 |
def clear_chat():
|
238 |
+
"""Efface l'historique de la conversation."""
|
239 |
session.pop('chat_history', None)
|
240 |
+
session.pop('web_search', None) # Réinitialiser aussi le toggle web
|
|
|
|
|
241 |
print("Historique de chat effacé.")
|
242 |
+
flash("Conversation effacée.", "info") # Optionnel: message flash
|
243 |
return redirect(url_for('index'))
|
244 |
|
245 |
+
# --- Démarrage ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
246 |
if __name__ == '__main__':
|
247 |
+
app.run(debug=True, host='0.0.0.0', port=5001)
|
|