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 tempfile from datetime import datetime import uuid from pathlib import Path app = Flask(__name__) app.secret_key = os.urandom(24) load_dotenv() # Configuration des dossiers UPLOAD_FOLDER = Path('uploads') TEMP_FOLDER = Path('temp') UPLOAD_FOLDER.mkdir(exist_ok=True) TEMP_FOLDER.mkdir(exist_ok=True) # Configuration de Google AI genai.configure(api_key=os.getenv("GOOGLE_API_KEY")) # Paramètres de sécurité safety_settings = [ {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"}, {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"}, {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"}, {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"}, ] # Prompt système SYSTEM_PROMPT = """ Tu es Mariam, une assistante IA créée par Youssouf. Tu es serviable, polie et tu t'exprimes en français. Tu dois toujours : - Répondre en français - Être précise et concise dans tes réponses - Utiliser un ton professionnel mais amical - Proposer des solutions pratiques aux problèmes - Demander des clarifications si nécessaire """ # Initialisation du modèle Gemini model = genai.GenerativeModel('gemini-pro', safety_settings=safety_settings) # Stockage des conversations conversations = {} chat_sessions = {} # Extensions de fichiers autorisées ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'pdf', 'txt', 'doc', 'docx'} 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 def perform_web_search(query): """Effectue une recherche web via l'API Serper""" conn = http.client.HTTPSConnection("google.serper.dev") payload = json.dumps({"q": query}) headers = { 'X-API-KEY': os.getenv('SERPER_API_KEY'), 'Content-Type': 'application/json' } try: conn.request("POST", "/search", payload, headers) res = conn.getresponse() data = json.loads(res.read().decode("utf-8")) return data except Exception as e: print(f"Erreur de recherche web: {e}") return None finally: conn.close() def format_search_results(data): """Formate les résultats de recherche""" if not data: return "Aucun résultat trouvé" formatted_results = [] # Traitement du Knowledge Graph if 'knowledgeGraph' in data: kg = data['knowledgeGraph'] formatted_results.append(f"### {kg.get('title', '')}") if 'description' in kg: formatted_results.append(f"{kg['description']}\n") # Traitement des résultats organiques if 'organic' in data: formatted_results.append("### Résultats de recherche:") for item in data['organic'][:3]: # Limite aux 3 premiers résultats formatted_results.append(f"- **{item['title']}**") formatted_results.append(f" {item['snippet']}\n") return "\n".join(formatted_results) def create_new_conversation(): """Crée une nouvelle conversation""" conversation_id = str(uuid.uuid4()) conversations[conversation_id] = { 'id': conversation_id, 'created_at': datetime.now().isoformat(), 'messages': [] } chat_sessions[conversation_id] = model.start_chat(history=[]) return conversation_id @app.route('/') def home(): """Page d'accueil""" if 'user_id' not in session: session['user_id'] = str(uuid.uuid4()) return render_template('index.html') @app.route('/conversations', methods=['GET']) def get_conversations(): """Récupère la liste des conversations""" user_conversations = [ { 'id': conv_id, 'created_at': conv['created_at'], 'preview': conv['messages'][0]['content'][:50] + '...' if conv['messages'] else 'Nouvelle conversation' } for conv_id, conv in conversations.items() ] return jsonify(user_conversations) @app.route('/conversation', methods=['POST']) def create_conversation(): """Crée une nouvelle conversation""" conversation_id = create_new_conversation() return jsonify({ 'id': conversation_id, 'created_at': conversations[conversation_id]['created_at'] }) @app.route('/conversation/', methods=['GET']) def get_conversation(conversation_id): """Récupère une conversation spécifique""" if conversation_id not in conversations: return jsonify({'error': 'Conversation non trouvée'}), 404 return jsonify(conversations[conversation_id]) @app.route('/send_message', methods=['POST']) def send_message(): """Traite l'envoi d'un message""" try: data = request.json message = data.get('message') conversation_id = data.get('conversation_id') web_search = data.get('web_search', False) if not message: return jsonify({'error': 'Message manquant'}), 400 # Crée une nouvelle conversation si nécessaire if not conversation_id or conversation_id not in conversations: conversation_id = create_new_conversation() # Effectue une recherche web si activée search_results = "" if web_search: results = perform_web_search(message) if results: search_results = format_search_results(results) message = f"{message}\n\nContexte de recherche web:\n{search_results}" # Envoie le message au modèle response = chat_sessions[conversation_id].send_message(message) # Enregistre les messages conversations[conversation_id]['messages'].append({ 'role': 'user', 'content': message, 'timestamp': datetime.now().isoformat() }) conversations[conversation_id]['messages'].append({ 'role': 'assistant', 'content': response.text, 'timestamp': datetime.now().isoformat() }) return jsonify({ 'conversation_id': conversation_id, 'response': response.text }) except Exception as e: print(f"Erreur lors de l'envoi du message: {e}") return jsonify({'error': str(e)}), 500 @app.route('/upload', methods=['POST']) def upload_file(): """Gère l'upload de fichiers""" if 'file' not in request.files: return jsonify({'error': 'Aucun fichier fourni'}), 400 file = request.files['file'] if file.filename == '': return jsonify({'error': 'Nom de fichier vide'}), 400 if file and allowed_file(file.filename): try: # Sécurise le nom du fichier filename = secure_filename(file.filename) timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') safe_filename = f"{timestamp}_{filename}" # Sauvegarde le fichier filepath = UPLOAD_FOLDER / safe_filename file.save(filepath) return jsonify({ 'success': True, 'filename': safe_filename, 'message': 'Fichier uploadé avec succès' }) except Exception as e: return jsonify({'error': f'Erreur lors de l\'upload: {str(e)}'}), 500 return jsonify({'error': 'Type de fichier non autorisé'}), 400 @app.route('/clear_chat', methods=['POST']) def clear_chat(): """Efface une conversation""" data = request.json conversation_id = data.get('conversation_id') if conversation_id: if conversation_id in conversations: del conversations[conversation_id] if conversation_id in chat_sessions: del chat_sessions[conversation_id] return jsonify({'success': True}) @app.route('/download/') def download_file(filename): """Télécharge un fichier""" try: return send_from_directory(UPLOAD_FOLDER, filename, as_attachment=True) except Exception as e: return jsonify({'error': f'Erreur lors du téléchargement: {str(e)}'}), 500 @app.errorhandler(404) def not_found_error(error): """Gère les erreurs 404""" return jsonify({'error': 'Page non trouvée'}), 404 @app.errorhandler(500) def internal_error(error): """Gère les erreurs 500""" return jsonify({'error': 'Erreur interne du serveur'}), 500 if __name__ == '__main__': app.run(debug=True)