import os import json from datetime import date import uuid import time import re import psutil 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, render_popup_paiement from modules.logger import log_event from modules.billing_manager import verifier_et_valider_budget, obtenir_solde from modules.rag_manager import rechercher_contexte_rag, auto_apprendre_rag from modules.moderator import analyser_intention_illicite, appliquer_sanction_et_archiver def check_and_update_resource_quota(utilisateur_nom, role_utilisateur): quota_file = "/DATA/AppData/MULTI-IA-AETHAS38/Utilisateurs/quotas_ressources.json" today = str(date.today()) limites = {"super admin": 10, "admin": 10, "vip": 5, "famille": 5, "standard": 3} limite_max = limites.get(role_utilisateur.lower(), 3) try: if os.path.exists(quota_file): with open(quota_file, "r") as f: data = json.load(f) else: data = {} except: data = {} if data.get("date") != today: data = {"date": today, "users": {}} user_uses = data["users"].get(utilisateur_nom, 0) if user_uses >= limite_max: return False data["users"][utilisateur_nom] = user_uses + 1 os.makedirs(os.path.dirname(quota_file), exist_ok=True) with open(quota_file, "w") as f: json.dump(data, f) return True 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") mode_payant = form.get("mode_payant") == "on" mode_multi_ia = form.get("mode_multi_ia") == "on" mode_education = form.get("mode_education") == "on" # INTERCEPTION DU MODE EDU validation_acquise = form.get("validation_acquise") == "true" choix_utilisateur = form.get("choix_utilisateur", "aucun") if choix_utilisateur != "aucun": log_event("INFO", "BILLING", f"Décision utilisateur face au pop-up : {choix_utilisateur}") if choix_utilisateur == "gratuit_force": mode_payant = False session["current_chat_id"] = chat_id history = load_history() utilisateur_nom = session.get("user", "xavier") role_utilisateur = "Super Admin" if utilisateur_nom.lower() == "xavier" else ("VIP" if utilisateur_nom.lower() in ["céline", "anaëlle", "renan", "audric"] else "standard") ip_client = request.client.host if request.client else "Inconnue" cpu_usage = psutil.cpu_percent(interval=0.1) ram_usage = psutil.virtual_memory().percent if cpu_usage > 60.0 or ram_usage > 60.0: if not check_and_update_resource_quota(utilisateur_nom, role_utilisateur): return Div(H3("⚠️ Limite de requêtes atteintes", style="color:#ff9800;"), P(f"Serveur très sollicité (>60%). Quota épuisé."), style="background:#333; padding:15px; border-radius:10px; border: 1px solid #ff9800;") est_illicite, raison = analyser_intention_illicite(prompt) if est_illicite: sanction = appliquer_sanction_et_archiver(utilisateur_nom, prompt, raison, ip_client) return Div(H3("⚠️ Avertissement de Sécurité"), P(f"Sanction : {sanction}"), style="color:red; background:#300; padding:15px; border-radius:10px;") 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 (Edu: {mode_education} / Payant: {mode_payant})") sec_prompt = f"""Tu es l'Orchestrateur système. Analyse la requête et choisis le meilleur fournisseur IA...""" try: texte_routeur, _, _, _ = interroger_local_ollama(sec_prompt) decision = nettoyer_decision_router(texte_routeur) except: decision = {"intention": "Analyse", "fournisseur": "openrouter_chat", "theme_rag": "aucun"} intention = decision.get("intention", "Analyse") fournisseur = decision.get("fournisseur", "openrouter_chat").lower() thematique_rag = decision.get("theme_rag", "aucun").lower() if mode_payant or mode_multi_ia: fournisseur = "openrouter_code" intention += " (API Premium/Multi)" if file_names: fournisseur = "gemini" # --- LOGIQUE EDUCATION NATIONALE --- if mode_education: fournisseur = "openrouter_chat" thematique_rag = "Education_Nationale" intention = "Éducation Nationale " + ("(Payant)" if mode_payant else "(IA Gratuite)") # Marqueur secret pour ai_openrouter.py instruction_edu = "[SYSTÈME] RÈGLE ÉDUCATION NATIONALE : Agis comme un expert de l'Éducation Nationale française. Pédagogie stricte, respect du B.O.\n\n" prompt = instruction_edu + prompt contexte_rag = "" if thematique_rag != "aucun": contexte_rag = rechercher_contexte_rag(prompt, categorie=thematique_rag) urls = re.findall(r"https?://[^\s]+", prompt) if any(m in prompt.lower() for m in ["cherche", "recherche", "sur le web"]): 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 contexte_rag: full_prompt += f"\n\n[Contexte RAG :\n{contexte_rag}]" if web_text: full_prompt += f"\n\n[Contexte web :\n{web_text}]" if "openrouter" in fournisseur: modele_estime = "anthropic/claude-3.5-sonnet" if mode_payant else "cohere/north-mini-code:free" if mode_payant and not validation_acquise: from modules.billing_manager import estimer_cout_facture _, prix_estime, _ = estimer_cout_facture(full_prompt, modele_estime, rang_utilisateur=role_utilisateur, multi_ia=mode_multi_ia) if prix_estime > 0: return render_popup_paiement(prix_estime, prompt, chat_id, mode_multi_ia, mode_education) if mode_payant and role_utilisateur.lower() not in ["super admin", "admin", "vip", "famille"]: from modules.billing_manager import estimer_cout_facture _, prix_estime, _ = estimer_cout_facture(full_prompt, modele_estime, rang_utilisateur=role_utilisateur, multi_ia=mode_multi_ia) if prix_estime > obtenir_solde(utilisateur_nom): return Div(H3("❌ Solde Insuffisant", style="color:red;"), P("Rechargez votre compte."), style="background:#300; padding:15px;") reponse, couleur, fournisseur_nom, modele = executer_requete_ia(fournisseur, full_prompt, mode_payant=mode_payant, multi_ia=mode_multi_ia, chat_id=chat_id) if "openrouter" in fournisseur_nom.lower() and mode_payant: verifier_et_valider_budget(full_prompt, modele, utilisateur_nom=utilisateur_nom, rang_utilisateur=role_utilisateur, multi_ia=mode_multi_ia) if thematique_rag != "aucun" and reponse: auto_apprendre_rag(prompt, reponse, thematique_rag) 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.replace("[SYSTÈME] RÈGLE ÉDUCATION NATIONALE : Agis comme un expert de l'Éducation Nationale française. Pédagogie stricte, respect du B.O.\n\n", "") if file_names: display_msg += f"\n\n📎 **Fichiers joints :** {', '.join(file_names)}" 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) 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 : {e}") return Div(f"Erreur système : {e}", style="color:red; background:#300; padding:15px; border-radius:10px;")