|
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() |
|
|
|
|
|
UPLOAD_FOLDER = Path('uploads') |
|
TEMP_FOLDER = Path('temp') |
|
UPLOAD_FOLDER.mkdir(exist_ok=True) |
|
TEMP_FOLDER.mkdir(exist_ok=True) |
|
|
|
|
|
genai.configure(api_key=os.getenv("GOOGLE_API_KEY")) |
|
|
|
|
|
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"}, |
|
] |
|
|
|
|
|
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 |
|
""" |
|
|
|
|
|
model = genai.GenerativeModel('gemini-pro', |
|
safety_settings=safety_settings) |
|
|
|
|
|
conversations = {} |
|
chat_sessions = {} |
|
|
|
|
|
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 = [] |
|
|
|
|
|
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") |
|
|
|
|
|
if 'organic' in data: |
|
formatted_results.append("### Résultats de recherche:") |
|
for item in data['organic'][:3]: |
|
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/<conversation_id>', 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 |
|
|
|
|
|
if not conversation_id or conversation_id not in conversations: |
|
conversation_id = create_new_conversation() |
|
|
|
|
|
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}" |
|
|
|
|
|
response = chat_sessions[conversation_id].send_message(message) |
|
|
|
|
|
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: |
|
|
|
filename = secure_filename(file.filename) |
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') |
|
safe_filename = f"{timestamp}_{filename}" |
|
|
|
|
|
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/<filename>') |
|
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) |