File size: 21,750 Bytes
c253059
da7ef42
c253059
 
da7ef42
 
c253059
c54f02a
c23cd74
 
da7ef42
c23cd74
 
 
 
 
 
 
 
 
 
 
da7ef42
c23cd74
 
 
 
 
 
da7ef42
f97a643
c23cd74
 
 
 
da7ef42
c23cd74
 
da7ef42
c23cd74
 
 
 
da7ef42
 
c23cd74
 
 
 
 
 
 
 
 
 
c253059
c23cd74
 
 
 
 
 
 
da7ef42
 
c23cd74
 
 
 
 
 
 
 
 
 
 
a2eb89f
c23cd74
 
6979c79
 
c23cd74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da7ef42
c23cd74
 
 
 
 
da7ef42
c23cd74
 
da7ef42
c23cd74
da7ef42
 
 
c23cd74
da7ef42
 
c23cd74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da7ef42
c23cd74
 
 
da7ef42
 
 
c23cd74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da7ef42
c23cd74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c253059
c23cd74
 
c96425b
f97a643
 
c23cd74
 
f97a643
c23cd74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c96425b
c253059
 
c23cd74
 
 
 
 
 
 
 
 
c253059
 
c23cd74
f97a643
a2eb89f
c23cd74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a2eb89f
c23cd74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c253059
 
c23cd74
 
 
 
c253059
 
 
c23cd74
 
 
 
 
 
 
 
c253059
c23cd74
 
 
c253059
 
c23cd74
 
 
c253059
f97a643
c23cd74
 
 
 
da7ef42
c23cd74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da7ef42
c23cd74
 
 
 
 
 
 
 
 
 
 
 
 
 
c253059
 
 
c23cd74
 
 
 
 
 
 
 
 
f97a643
 
c23cd74
 
 
f97a643
c23cd74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c253059
da7ef42
c23cd74
 
c253059
c23cd74
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
from flask import Flask, render_template, request, jsonify, session
import google.generativeai as genai
import os
from dotenv import load_dotenv
import http.client
import json
from werkzeug.utils import secure_filename
import uuid
from PIL import Image # Required for checking image files if needed, or directly by genai
import traceback # For detailed error logging

# --- Configuration ---
load_dotenv() # Charge les variables depuis le fichier .env

# Check for essential environment variables
GEMINI_API_KEY = os.getenv("GOOGLE_API_KEY")
SERPER_API_KEY = "9b90a274d9e704ff5b21c0367f9ae1161779b573" # Clé pour la recherche web (optionnelle)
FLASK_SECRET_KEY = "jdjdjdjdj" # Clé secrète pour les sessions Flask

# Validation critique des clés au démarrage

# Configure Flask App
app = Flask(__name__)
app.secret_key = FLASK_SECRET_KEY
app.config['UPLOAD_FOLDER'] = 'temp_uploads' # Dossier pour les uploads temporaires
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024  # Limite la taille des uploads à 100 Mo

# Assurer l'existence du dossier d'upload
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)

# Configure Google AI
try:
    genai.configure(api_key=GEMINI_API_KEY)
except Exception as e:
    raise RuntimeError(f"Échec de la configuration de l'API Google AI: {e}. Vérifiez votre GEMINI_API_KEY.")


# Paramètres de Sécurité Gemini (Ajustez si nécessaire)
safety_settings = [
    {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
    {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
    {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
    {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
]

# Configuration de la Génération Gemini
generation_config = {
    "temperature": 0.7,
    "top_p": 0.95,
    "top_k": 64,
    "max_output_tokens": 8192,
    "response_mime_type": "text/plain", # Demande une réponse texte simple
}

# Prompt Système (Instructions pour l'IA)
SYSTEM_PROMPT = """
Vous êtes Mariam, une assistante IA conversationnelle polyvalente conçue par Youssouf.
Votre objectif est d'aider les utilisateurs de manière informative, créative et engageante.
Répondez en français. Soyez concise mais complète.
Si l'utilisateur télécharge des fichiers, prenez-les en compte dans votre réponse si pertinent pour la question posée.
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.
Formattez vos réponses en Markdown lorsque cela améliore la lisibilité (listes, blocs de code, gras, italique, etc.).
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.
"""

# Initialisation du Modèle Gemini
try:
    model = genai.GenerativeModel(
        model_name="gemini-1.5-flash-latest", # Utilisation de la dernière version flash
        safety_settings=safety_settings,
        generation_config=generation_config,
        system_instruction=SYSTEM_PROMPT
    )
    print("Modèle Gemini initialisé avec succès.")
except Exception as e:
     raise RuntimeError(f"Échec de l'initialisation du modèle Gemini: {e}")


# Stockage en mémoire des sessions de chat (Pour la production, envisagez une solution persistante comme Redis ou une base de données)
chat_sessions = {}

# Extensions de fichiers autorisées pour l'upload
ALLOWED_EXTENSIONS = {
    'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'webp', # Texte et Images
    'heic', 'heif', # Formats d'image Apple
    'mp3', 'wav', 'ogg', 'flac', # Audio
    'mp4', 'mov', 'avi', 'mkv', 'webm' # Vidéo
}

def allowed_file(filename):
    """Vérifie si l'extension du fichier est autorisée."""
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

# --- Fonctions Utilitaires ---

def get_chat_session(session_id):
    """Récupère ou crée une session de chat Gemini pour l'ID de session Flask donné."""
    if session_id not in chat_sessions:
        print(f"Création d'une nouvelle session de chat Gemini pour Flask session ID: {session_id}")
        # L'historique est géré dynamiquement lors de l'envoi des messages
        chat_sessions[session_id] = model.start_chat(history=[])
    return chat_sessions[session_id]

def perform_web_search(query):
    """Effectue une recherche web via l'API Serper.dev."""
    if not SERPER_API_KEY:
        print("Recherche web ignorée : SERPER_API_KEY non configurée.")
        return None # Retourne None si la clé n'est pas dispo

    conn = http.client.HTTPSConnection("google.serper.dev")
    # Payload pour la recherche en français
    payload = json.dumps({"q": query, "gl": "fr", "hl": "fr"})
    headers = {
        'X-API-KEY': SERPER_API_KEY,
        'Content-Type': 'application/json'
    }
    try:
        print(f"Serper: Envoi de la requête pour '{query}'...")
        conn.request("POST", "/search", payload, headers)
        res = conn.getresponse()
        data_bytes = res.read()
        data_str = data_bytes.decode("utf-8")
        print(f"Serper: Réponse reçue - Statut: {res.status}")

        if res.status == 200:
            print("Serper: Recherche réussie.")
            return json.loads(data_str)
        else:
            print(f"Serper: Erreur API - Statut {res.status}, Réponse: {data_str}")
            return {"error": f"L'API de recherche a échoué avec le statut {res.status}"}
    except http.client.HTTPException as http_err:
        print(f"Erreur HTTP lors de la recherche web: {http_err}")
        return {"error": f"Erreur de connexion HTTP: {str(http_err)}"}
    except json.JSONDecodeError as json_err:
         print(f"Erreur de décodage JSON de la réponse Serper: {json_err}")
         print(f"Réponse brute reçue: {data_str}")
         return {"error": "Impossible de lire la réponse de l'API de recherche."}
    except Exception as e:
        print(f"Erreur inattendue lors de la recherche web: {e}")
        traceback.print_exc()
        return {"error": f"Exception lors de la recherche web: {str(e)}"}
    finally:
        conn.close()

def format_search_results_for_prompt(data):
    """Formate les résultats de recherche de manière concise pour les injecter dans le prompt de l'IA."""
    if not data or data.get("searchParameters", {}).get("q") is None:
         # Si data est vide ou ne semble pas être une réponse valide
        print("Formatage recherche: Données invalides ou vides reçues.")
        return "La recherche web n'a pas retourné de résultats exploitables."
    if "error" in data:
        print(f"Formatage recherche: Erreur détectée dans les données - {data['error']}")
        return f"Note : La recherche web a rencontré une erreur ({data['error']})."

    results = []
    query = data.get("searchParameters", {}).get("q", "Terme inconnu")
    results.append(f"Résultats de recherche web pour '{query}':")

    # Boîte de réponse / Knowledge Graph (si présents)
    if 'answerBox' in data:
        ab = data['answerBox']
        answer = ab.get('answer') or ab.get('snippet') or ab.get('title')
        if answer:
             results.append(f"- Réponse directe trouvée : {answer}")
    elif 'knowledgeGraph' in data:
        kg = data['knowledgeGraph']
        description = kg.get('description')
        if description:
            results.append(f"- Info (Knowledge Graph) : {kg.get('title', '')} - {description}")

    # Résultats Organiques (naturels)
    if 'organic' in data and data['organic']:
        results.append("- Résultats principaux :")
        for i, item in enumerate(data['organic'][:3], 1): # Limite aux 3 premiers
            title = item.get('title', 'Sans titre')
            snippet = item.get('snippet', 'Pas de description')
            link = item.get('link', '#')
            results.append(f"  {i}. {title}: {snippet} (Source: {link})")
    elif not results: # Si ni answerbox/KG ni organic n'ont donné qqch
        return f"Aucun résultat de recherche web pertinent trouvé pour '{query}'."


    print(f"Formatage recherche: {len(results)-1} éléments formatés pour le prompt.")
    return "\n".join(results)


# --- Routes Flask ---

@app.route('/')
def home():
    """Affiche l'interface de chat principale."""
    # Initialise la session Flask si elle n'existe pas
    if 'session_id' not in session:
        session_id = str(uuid.uuid4())
        session['session_id'] = session_id
        session['messages'] = [] # Historique pour l'affichage client
        session['uploaded_files_gemini'] = [] # Stocke les objets File retournés par genai.upload_file
        session.modified = True # Marque la session comme modifiée
        print(f"Nouvelle session Flask créée: {session_id}")
    else:
        # Assure que les clés nécessaires existent même si la session existe déjà
        if 'messages' not in session: session['messages'] = []
        if 'uploaded_files_gemini' not in session: session['uploaded_files_gemini'] = []


    # Récupère les messages de la session pour les passer au template
    messages_for_template = session.get('messages', [])
    return render_template('index.html', messages=messages_for_template)

@app.route('/send_message', methods=['POST'])
def send_message():
    """Gère l'envoi d'un message à l'IA et retourne sa réponse."""
    # Vérifie si la session utilisateur est valide
    if 'session_id' not in session:
        print("Erreur send_message: ID de session manquant.")
        return jsonify({'error': 'Session expirée ou invalide. Veuillez rafraîchir la page.'}), 400

    session_id = session['session_id']
    print(f"\n--- Requête /send_message reçue (Session: {session_id}) ---")

    try:
        data = request.json
        user_message_text = data.get('message', '').strip()
        web_search_enabled = data.get('web_search', False)

        # Vérifie si le message est vide (sauf si des fichiers sont attachés)
        uploaded_files = session.get('uploaded_files_gemini', [])
        if not user_message_text and not uploaded_files:
            print("Avertissement send_message: Message vide et aucun fichier attaché.")
            return jsonify({'error': 'Veuillez entrer un message ou joindre un fichier.'}), 400

        # Récupère ou crée la session de chat Gemini associée
        chat_gemini = get_chat_session(session_id)

        # --- Préparation du contenu pour Gemini ---
        prompt_parts = [] # Liste pour contenir texte, fichiers, résultats web

        # 1. Ajouter les fichiers uploadés (depuis la session)
        #    On utilise directement les objets File retournés par genai.upload_file
        if uploaded_files:
            prompt_parts.extend(uploaded_files) # Ajoute les objets File à la liste
            file_names = [f.display_name for f in uploaded_files] # Noms pour log
            print(f"Ajout au prompt des fichiers Gemini: {', '.join(file_names)}")
            # Vider la liste des fichiers après les avoir ajoutés au prompt de CE message
            # Permet d'associer les fichiers au message en cours uniquement
            session['uploaded_files_gemini'] = []
            session.modified = True
            print("Liste des fichiers uploadés vidée pour le prochain message.")


        # 2. Ajouter les résultats de recherche web (si activé et clé API dispo)
        search_prompt_text = "" # Texte à ajouter au prompt textuel
        if web_search_enabled and SERPER_API_KEY:
            print(f"Recherche web activée pour: '{user_message_text}'")
            web_results_data = perform_web_search(user_message_text)
            if web_results_data:
                formatted_results = format_search_results_for_prompt(web_results_data)
                search_prompt_text = f"\n\n--- Informations issues de la recherche web ---\n{formatted_results}\n--- Fin des informations de recherche ---\n"
                print("Résultats de recherche formatés ajoutés au contexte.")
            else:
                 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"
                 print("Aucun résultat de recherche web ou échec.")


        # 3. Ajouter le message texte de l'utilisateur (incluant le contexte de recherche si existant)
        final_user_text = user_message_text + search_prompt_text
        prompt_parts.append(final_user_text) # Ajoute le texte (potentiellement enrichi)
        print(f"Texte final envoyé à Gemini:\n{final_user_text[:500]}...") # Log tronqué

        # --- Envoi à l'API Gemini ---
        print(f"Envoi de {len(prompt_parts)} partie(s) à l'API Gemini...")
        ai_response_text = ""
        try:
            # Envoie la liste complète des parties (fichiers + texte)
            response = chat_gemini.send_message(prompt_parts)
            ai_response_text = response.text # Récupère le texte de la réponse
            print("Réponse reçue de Gemini.")

            # Vérifier si la réponse a été bloquée par les filtres de sécurité
            if response.prompt_feedback.block_reason:
                 block_reason = response.prompt_feedback.block_reason
                 print(f"AVERTISSEMENT: Réponse bloquée par Gemini. Raison: {block_reason}")
                 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."

        except Exception as e:
            print(f"ERREUR lors de l'appel à Gemini API: {e}")
            traceback.print_exc() # Log détaillé de l'erreur serveur
            # Essaye de donner une erreur plus spécifique si possible
            error_details = str(e)
            ai_response_text = f"❌ Désolé, une erreur est survenue lors de la communication avec l'IA. Détails techniques: {error_details}"


        # --- Mise à jour de l'historique de session Flask (pour l'affichage) ---
        # Stocke le message *original* de l'utilisateur et la réponse de l'IA
        current_messages = session.get('messages', [])
        # !! Ne pas stocker le prompt enrichi dans l'historique affiché !!
        current_messages.append({'role': 'user', 'content': user_message_text}) # Message original
        current_messages.append({'role': 'assistant', 'content': ai_response_text}) # Réponse IA
        session['messages'] = current_messages
        session.modified = True
        print("Historique de session Flask mis à jour.")

        # --- Retourner la réponse de l'IA au client ---
        print(f"--- Fin Requête /send_message (Session: {session_id}) ---")
        return jsonify({'response': ai_response_text})

    except Exception as e:
        print(f"ERREUR Inattendue dans /send_message: {e}")
        traceback.print_exc()
        return jsonify({'error': f'Erreur interne du serveur: {str(e)}'}), 500


@app.route('/upload', methods=['POST'])
def upload_file():
    """Gère l'upload de fichiers, les enregistre temporairement et les prépare pour Gemini."""
    if 'session_id' not in session:
        print("Erreur upload: ID de session manquant.")
        return jsonify({'error': 'Session expirée ou invalide. Veuillez rafraîchir la page.'}), 400

    session_id = session['session_id']
    print(f"\n--- Requête /upload reçue (Session: {session_id}) ---")

    if 'file' not in request.files:
        print("Erreur upload: 'file' manquant dans request.files.")
        return jsonify({'error': 'Aucun fichier trouvé dans la requête.'}), 400

    file = request.files['file']
    if file.filename == '':
        print("Erreur upload: Nom de fichier vide.")
        return jsonify({'error': 'Aucun fichier sélectionné.'}), 400

    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        # Crée un chemin de sauvegarde temporaire unique (évite écrasement si même nom)
        temp_filename = f"{session_id}_{uuid.uuid4().hex}_{filename}"
        save_path = os.path.join(app.config['UPLOAD_FOLDER'], temp_filename)

        try:
            print(f"Sauvegarde du fichier '{filename}' vers '{save_path}'...")
            file.save(save_path)
            print(f"Fichier sauvegardé localement.")

            # Uploader le fichier vers Google AI Studio (API Gemini)
            print(f"Upload de '{filename}' vers Google AI Studio...")
            # display_name est optionnel mais utile pour le débogage ou l'affichage
            gemini_file_object = genai.upload_file(path=save_path, display_name=filename)
            print(f"Fichier uploadé vers Gemini. Référence obtenue: {gemini_file_object.name}") # L'objet a un attribut 'name'

            # Stocker l'objet File retourné par Gemini dans la session Flask
            # C'est cet objet qui sera utilisé directement dans send_message
            current_uploads = session.get('uploaded_files_gemini', [])
            current_uploads.append(gemini_file_object)
            session['uploaded_files_gemini'] = current_uploads
            session.modified = True
            print(f"Objet Fichier Gemini ajouté à la session Flask (Total: {len(current_uploads)}).")

            # Supprimer le fichier local temporaire après l'upload réussi vers Gemini
            try:
                os.remove(save_path)
                print(f"Fichier local temporaire '{save_path}' supprimé.")
            except OSError as e:
                print(f"AVERTISSEMENT: Échec de la suppression du fichier local '{save_path}': {e}")

            print(f"--- Fin Requête /upload (Session: {session_id}) - Succès ---")
            return jsonify({'success': True, 'filename': filename}) # Renvoie le nom original pour l'UI

        except Exception as e:
            print(f"ERREUR lors de l'upload ou du traitement Gemini: {e}")
            traceback.print_exc()
            # Nettoyer le fichier local s'il existe encore en cas d'erreur
            if os.path.exists(save_path):
                try:
                    os.remove(save_path)
                    print(f"Nettoyage: Fichier local '{save_path}' supprimé après erreur.")
                except OSError as rm_err:
                    print(f"Erreur lors du nettoyage du fichier '{save_path}': {rm_err}")
            print(f"--- Fin Requête /upload (Session: {session_id}) - Échec ---")
            return jsonify({'error': f'Échec de l\'upload ou du traitement du fichier: {str(e)}'}), 500
    else:
        print(f"Erreur upload: Type de fichier non autorisé - '{file.filename}'")
        return jsonify({'error': 'Type de fichier non autorisé.'}), 400

@app.route('/clear_chat', methods=['POST'])
def clear_chat():
    """Efface l'historique de chat et les fichiers uploadés pour la session en cours."""
    if 'session_id' not in session:
        print("Avertissement clear_chat: Pas de session à effacer.")
        return jsonify({'success': True}) # Pas d'erreur si pas de session

    session_id = session['session_id']
    print(f"\n--- Requête /clear_chat reçue (Session: {session_id}) ---")

    # 1. Effacer la session de chat Gemini en mémoire (si elle existe)
    if session_id in chat_sessions:
        del chat_sessions[session_id]
        print(f"Session de chat Gemini pour {session_id} effacée de la mémoire.")

    # 2. Effacer les données pertinentes de la session Flask
    session['messages'] = []
    files_to_delete = session.get('uploaded_files_gemini', [])
    session['uploaded_files_gemini'] = []
    session.modified = True
    print("Historique des messages et références de fichiers effacés de la session Flask.")

    # 3. (Optionnel mais recommandé) Supprimer les fichiers uploadés correspondants depuis Google AI Studio
    #    Cela nécessite de stocker les 'name' (IDs) des fichiers Gemini et d'appeler genai.delete_file()
    #    Pour l'instant, on efface juste la référence dans la session Flask.
    #    L'API delete_file est importante pour gérer l'espace de stockage Gemini.
    if files_to_delete:
         print(f"Tentative de suppression de {len(files_to_delete)} fichier(s) sur Google AI Studio...")
         for file_obj in files_to_delete:
             try:
                 print(f"  Suppression de {file_obj.display_name} (ID: {file_obj.name})...")
                 genai.delete_file(file_obj.name)
                 print(f"  Fichier {file_obj.name} supprimé de Google AI.")
             except Exception as e:
                 print(f"  ERREUR lors de la suppression du fichier {file_obj.name} de Google AI: {e}")
                 # Continuer même si un fichier ne peut être supprimé


    print(f"--- Fin Requête /clear_chat (Session: {session_id}) ---")
    return jsonify({'success': True})


# --- Démarrage de l'Application ---
if __name__ == '__main__':
    # Rappel des dépendances:
    # pip install Flask python-dotenv google-generativeai Pillow werkzeug requests gunicorn
    print("\n" + "="*40)
    print(" Démarrage du serveur Flask Assistant IA ")
    print("="*40)
    print("Vérifiez que votre fichier .env contient :")
    print("  - GEMINI_API_KEY=VOTRE_CLE_GEMINI")
    print("  - FLASK_SECRET_KEY=VOTRE_CLE_SECRET_FLASK")
    print("  - SERPER_API_KEY=VOTRE_CLE_SERPER (Optionnel, pour recherche web)")
    print("-"*40)
    # Utiliser host='0.0.0.0' pour rendre accessible sur le réseau local
    # debug=True active le rechargement automatique et le débogueur (NE PAS UTILISER EN PRODUCTION)
    # Utiliser Gunicorn ou un autre serveur WSGI pour la production
    app.run(debug=True, host='0.0.0.0', port=5000)