Chatm / app.py
Docfile's picture
Update app.py
dfc4deb verified
raw
history blame
10.9 kB
import os
import json
# Importer jsonify pour les réponses API
from flask import Flask, render_template, request, session, redirect, url_for, flash, jsonify
from dotenv import load_dotenv
import google.generativeai as genai
import requests
from werkzeug.utils import secure_filename
import mimetypes
import markdown # <-- Importer la bibliothèque Markdown
load_dotenv()
app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'une-clé-secrète-par-défaut-pour-dev')
UPLOAD_FOLDER = 'temp'
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg'}
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
# --- Configuration Gemini (inchangée) ---
try:
genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
safety_settings = [
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
# ... (autres catégories)
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
]
model = genai.GenerativeModel(
'gemini-1.5-flash',
safety_settings=safety_settings,
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"
)
print("Modèle Gemini chargé.")
except Exception as e:
print(f"Erreur lors de la configuration de Gemini : {e}")
model = None
# --- Fonctions Utilitaires (inchangées) ---
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def perform_web_search(query):
conn_key = os.getenv("SERPER_API_KEY")
if not conn_key:
print("Clé API SERPER manquante dans .env")
return None
search_url = "https://google.serper.dev/search"
headers = {'X-API-KEY': conn_key, 'Content-Type': 'application/json'}
payload = json.dumps({"q": query})
try:
response = requests.post(search_url, headers=headers, data=payload, timeout=10)
response.raise_for_status()
data = response.json()
print("Résultats de recherche obtenus.")
return data
except requests.exceptions.RequestException as e:
print(f"Erreur lors de la recherche web : {e}")
return None
except json.JSONDecodeError as e:
print(f"Erreur lors du décodage de la réponse JSON de Serper : {e}")
print(f"Réponse reçue : {response.text}")
return None
def format_search_results(data):
# (Fonction inchangée - assurez-vous qu'elle renvoie du texte formaté)
if not data: return "Aucun résultat de recherche trouvé."
result = "Résultats de recherche web :\n"
# ... (reste de la fonction inchangé) ...
if 'organic' in data and data['organic']:
result += "\n## Résultats principaux :\n"
for i, item in enumerate(data['organic'][:3], 1):
result += f"{i}. **{item.get('title', 'N/A')}**\n" # Format Markdown
result += f" {item.get('snippet', 'N/A')}\n"
result += f" [Lien]({item.get('link', '#')})\n\n" # Lien Markdown
# ... (reste de la fonction inchangé) ...
return result
def prepare_gemini_history(chat_history):
gemini_history = []
for message in chat_history:
role = 'user' if message['role'] == 'user' else 'model'
# Utiliser le 'raw_text' pour Gemini, pas le HTML rendu
text_part = message.get('raw_text', message.get('text', ''))
parts = [text_part]
if message.get('gemini_file_ref'): # Utiliser une clé différente pour la référence interne
parts.insert(0, message['gemini_file_ref'])
gemini_history.append({'role': role, 'parts': parts})
return gemini_history
# --- Routes Flask ---
@app.route('/', methods=['GET'])
def index():
"""Affiche la page principale du chat."""
if 'chat_history' not in session:
session['chat_history'] = []
# L'état 'web_search' est maintenant géré côté client initialement,
# mais on peut le pré-cocher depuis la session si on veut le persister.
web_search_initial_state = session.get('web_search', False)
# On ne passe que l'historique nécessaire à l'affichage initial
display_history = session['chat_history']
return render_template(
'index.html',
chat_history=display_history,
web_search_active=web_search_initial_state
)
@app.route('/api/chat', methods=['POST'])
def chat_api():
"""Gère les requêtes de chat AJAX et retourne du JSON."""
if not model:
return jsonify({'success': False, 'error': "Le modèle Gemini n'est pas configuré."}), 500
prompt = request.form.get('prompt', '').strip()
use_web_search = request.form.get('web_search') == 'true'
file = request.files.get('file')
uploaded_gemini_file = None # Référence à l'objet fichier uploadé à Gemini
uploaded_filename = None # Juste le nom pour référence
if not prompt and not file:
return jsonify({'success': False, 'error': 'Message ou fichier requis.'}), 400
# Mettre à jour l'état de la recherche web dans la session si on veut le persister
session['web_search'] = use_web_search
# --- Gestion de l'upload ---
user_message_parts_for_gemini = []
raw_user_text = prompt # Texte brut pour l'historique Gemini
if file and file.filename != '':
if allowed_file(file.filename):
try:
filename = secure_filename(file.filename)
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(filepath)
uploaded_filename = filename # Garder le nom
print(f"Fichier sauvegardé : {filepath}")
mime_type = mimetypes.guess_type(filepath)[0] or 'application/octet-stream'
print(f"Upload vers Gemini (mime: {mime_type})...")
gemini_file_obj = genai.upload_file(path=filepath, mime_type=mime_type)
uploaded_gemini_file = gemini_file_obj # Garder l'objet pour l'API
user_message_parts_for_gemini.append(uploaded_gemini_file) # Ajouter l'objet fichier pour Gemini
print(f"Fichier {filename} uploadé vers Gemini.")
# Optionnel: Supprimer le fichier local
# os.remove(filepath)
except Exception as e:
print(f"Erreur upload fichier : {e}")
# Ne pas bloquer, mais renvoyer une erreur partielle si nécessaire
return jsonify({'success': False, 'error': f"Erreur traitement fichier: {e}"}), 500
else:
return jsonify({'success': False, 'error': 'Type de fichier non autorisé.'}), 400
# --- Préparation du message utilisateur et de l'historique ---
# Ajouter le texte après le fichier pour Gemini
user_message_parts_for_gemini.append(prompt)
# Stocker le message utilisateur dans l'historique de session
# Stocker le texte brut et la référence au fichier séparément
user_history_entry = {
'role': 'user',
'text': f"[Fichier: {uploaded_filename}]\n\n{prompt}" if uploaded_filename else prompt, # Pour affichage
'raw_text': raw_user_text, # Texte brut pour Gemini
}
if uploaded_gemini_file:
# Ne stockez pas l'objet complet dans la session, juste une réf si nécessaire,
# ou reconstruisez l'historique sans la référence de fichier si non critique.
# Pour simplifier, on ne stocke pas la référence objet dans la session ici.
# On pourrait stocker gemini_file_obj.name si on a besoin de le réutiliser plus tard.
# user_history_entry['gemini_file_ref_name'] = uploaded_gemini_file.name
pass # L'objet est dans user_message_parts_for_gemini pour l'appel API actuel
if 'chat_history' not in session: session['chat_history'] = []
session['chat_history'].append(user_history_entry)
session.modified = True
# --- Web Search ---
final_prompt_text = prompt
if use_web_search and prompt: # Recherche uniquement si texte ET activé
print("Recherche web en cours pour:", prompt)
web_results = perform_web_search(prompt)
if web_results:
formatted_results = format_search_results(web_results)
# Mettre à jour le texte à envoyer à Gemini (le fichier est déjà dans les parts)
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."
print("Prompt modifié avec résultats web.")
# Remplacer le texte dans la liste des parts
user_message_parts_for_gemini[-1] = final_prompt_text
else:
print("Pas de résultats web ou erreur.")
# --- Appel à Gemini ---
try:
# Préparer l'historique SANS le dernier message utilisateur (il est dans `contents`)
gemini_history = prepare_gemini_history(session['chat_history'][:-1])
print(f"Historique Gemini: {len(gemini_history)} messages.")
# Construire le contenu pour generate_content
contents = gemini_history + [{'role': 'user', 'parts': user_message_parts_for_gemini}]
print("Appel à model.generate_content...")
response = model.generate_content(contents)
# --- Traitement Réponse ---
response_text_raw = response.text
# Convertir Markdown en HTML pour un meilleur rendu
response_html = markdown.markdown(response_text_raw, extensions=['fenced_code', 'tables'])
print(f"Réponse Gemini reçue (premiers 500 chars): {response_text_raw[:500]}")
# Ajouter la réponse à l'historique de session
session['chat_history'].append({'role': 'assistant', 'text': response_html, 'raw_text': response_text_raw})
session.modified = True
return jsonify({'success': True, 'message': response_html}) # Envoyer le HTML au client
except Exception as e:
print(f"Erreur lors de l'appel à Gemini : {e}")
# Retirer le dernier message utilisateur de l'historique en cas d'échec
session['chat_history'].pop()
session.modified = True
return jsonify({'success': False, 'error': f"Erreur communication IA: {e}"}), 500
@app.route('/clear', methods=['POST'])
def clear_chat():
"""Efface l'historique de la conversation."""
session.pop('chat_history', None)
session.pop('web_search', None) # Réinitialiser aussi le toggle web
print("Historique de chat effacé.")
flash("Conversation effacée.", "info") # Optionnel: message flash
return redirect(url_for('index'))
# --- Démarrage ---
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5001)