import uuid import time import re from fasthtml.common import * from modules.database import load_history, save_history from modules.scraper import rechercher_sur_google, extraire_contenu_urls from modules.router import nettoyer_decision_router from modules.ai_local import interroger_local_ollama from modules.ai_engine import executer_requete_ia from modules.ui_components import render_user_msg, render_ai_msg, render_sidebar from modules.logger import log_event from modules.billing_manager import verifier_et_valider_budget async def handle_chat_request(request, session): start_time = time.time() try: form = await request.form() prompt = form.get("prompt", "") chat_id = form.get("chat_id", str(uuid.uuid4())) files = form.getlist("files") session['current_chat_id'] = chat_id history = load_history() # Identification temporaire de l'utilisateur (en attendant le module de connexion complet) utilisateur_nom = session.get('user', 'xavier') utilisateur_vip = utilisateur_nom.lower() in ["xavier", "céline", "anaëlle", "renan", "audric"] # Extraction éphémère en RAM file_summary = [] file_names = [] for f in files[:10]: if f and f.filename: file_names.append(f.filename) content = await f.read() file_summary.append(f"\n--- Fichier: {f.filename} ---\n{content.decode('utf-8', errors='ignore')}") liste_fichiers_str = ', '.join(file_names) if file_names else 'Aucun fichier' log_event("INFO", "SYSTEM", f"Requête reçue. Fichiers joints : {liste_fichiers_str}") # L'IA Local (Qwen) orchestre la décision ! Focus sur l'efficience. sec_prompt = f"""Tu es l'Orchestrateur système. Analyse la requête et choisis le meilleur fournisseur IA : - "gemini" : (OBLIGATOIRE si fichiers joints), rédaction complexe, comparaisons ou recherches web. - "openrouter_chat" : requêtes générales, texte rapide, traduction, bureautique (Modèles gratuits). - "openrouter_code" : programmation, scripts, mathématiques, logique (Modèles gratuits). - "local" : salutations basiques, questions très simples. Renvoie STRICTEMENT un JSON : {{"intention": "Résumé...", "fournisseur": "gemini|openrouter_chat|openrouter_code|local"}} Requête : {prompt}""" try: texte_routeur, _, _, _ = interroger_local_ollama(sec_prompt, "qwen2.5:3b") decision = nettoyer_decision_router(texte_routeur) except Exception: decision = {"intention": "Analyse de secours", "fournisseur": "gemini"} intention = decision.get('intention', 'Analyse') fournisseur = decision.get('fournisseur', 'openrouter_chat').lower() # Sécurité Absolue Fichiers -> Gemini if file_names: fournisseur = "gemini" intention += " (Forcé Gemini via Sécurité Fichiers)" urls = re.findall(r'https?://[^\s]+', prompt) mots_cles = ["cherche", "recherche", "sur internet", "sur le web", "sur le net", "va sur", "trouve", "site", "donne", "historique", "version"] if any(m in prompt.lower() for m in mots_cles): urls.extend(rechercher_sur_google(prompt)) web_text = extraire_contenu_urls(list(set(urls))) if urls else "" full_prompt = prompt + ("\n" + "\n".join(file_summary) if file_summary else "") if web_text: full_prompt += f"\n\n[Contexte web extrait :\n{web_text}\nUtilise ces informations pour répondre.]" # --- VÉRIFICATION FINANCIÈRE PRÉALABLE --- if "openrouter" in fournisseur: modele_estime = "qwen/qwen-2.5-coder-32b-instruct:free" if fournisseur == "openrouter_code" else "meta-llama/llama-3.1-8b-instruct:free" # Déclenche une exception bloquante si le solde est insuffisant ou nécessite une validation verifier_et_valider_budget(full_prompt, modele_estime, utilisateur_vip) # Délégation de l'exécution au Moteur d'IA (Cascade) reponse, couleur, fournisseur_nom, modele = executer_requete_ia(fournisseur, full_prompt) if chat_id not in history: history[chat_id] = {"title": (prompt[:30] if prompt else "Discussion") + "...", "pinned": False, "timestamp": time.time(), "messages": []} execution_time = round(time.time() - start_time, 1) display_msg = prompt if file_names: display_msg += f"\n\n📎 **Fichiers joints :** {', '.join(file_names)}" display_msg += "\n> *🗑️ Note : Fichiers traités de manière éphémère en mémoire vive et détruits instantanément après analyse conformément aux règles de confidentialité.*" history[chat_id]['messages'].append({"role": "user", "content": display_msg}) history[chat_id]['messages'].append({"role": "ai", "content": reponse, "intention": intention, "fournisseur": fournisseur_nom, "modele": modele, "couleur": couleur, "execution_time": execution_time}) save_history(history) log_event("INFO", fournisseur_nom, f"Réponse générée en {execution_time}s.") return render_user_msg(display_msg), render_ai_msg(reponse, intention, fournisseur_nom, modele, couleur, execution_time), Script("renderMarkdown();"), render_sidebar() except Exception as e: log_event("CRITICAL", "SYSTEM", f"Erreur critique dans handle_chat_request: {e}") # Capture l'erreur de paiement pour avertir l'utilisateur dans l'UI msg_erreur = str(e) if "VALIDATION_PAIEMENT_REQUISE" in msg_erreur or "SOLDE_INSUFFISANT" in msg_erreur: return Div(f"⚠️ Alerte Financière : {msg_erreur.split('|')[0]}", style="color:orange; background:#331a00; padding:15px; border-radius:10px;") return Div(f"Erreur système : {e}", style="color:red; background:#300; padding:15px; border-radius:10px;")