Auto-updater, limites CPU 60% et facturation corrigés
Build and Push Docker Image / build-and-push (push) Successful in 1m55s

This commit is contained in:
xavier committed 2026-08-22 01:10:40 +02:00
1 parent 71d6f51ee0
commit b4ed2947d4
15 files changed
+7411 -120

No files matched your search

+8 -8
View File
@@ -32,7 +32,7 @@ Pour fonctionner de manière optimale, l'Orchestrateur Local nécessite au minim
### Phase 4 : Gestion des Utilisateurs & Sécurité Légale ### Phase 4 : Gestion des Utilisateurs & Sécurité Légale
- [ ] Mettre en place une bibliothèque logicielle autonome sur le serveur (création PDF, DOCX, etc.). - [ ] Mettre en place une bibliothèque logicielle autonome sur le serveur (création PDF, DOCX, etc.).
- [ ] Routage automatique (Gratuit vs Payant) sélectionné par l'utilisateur. - [x] Routage automatique (Gratuit vs Payant) sélectionné par l'utilisateur.
- [X] Système de connexion sécurisé avec configuration du premier lancement (DOB depuis 1900, Email, Clés API). L'installateur a obligatoirement le rôle "Dieu tout puissant" sur son se (Admin global = Super Admin). - [X] Système de connexion sécurisé avec configuration du premier lancement (DOB depuis 1900, Email, Clés API). L'installateur a obligatoirement le rôle "Dieu tout puissant" sur son se (Admin global = Super Admin).
- [x] Système de cryptage de grade militaire (Fernet) et de Hashing (Bcrypt) mis en place. - [x] Système de cryptage de grade militaire (Fernet) et de Hashing (Bcrypt) mis en place.
- [X] Sécurité Légère : Fail2Ban, protection Bruteforce et DDoS. **Blocage total des accès VPN sauf adresses locales (192.168.xxx.xxx & 10.xxx.xxx.xxx)**. - [X] Sécurité Légère : Fail2Ban, protection Bruteforce et DDoS. **Blocage total des accès VPN sauf adresses locales (192.168.xxx.xxx & 10.xxx.xxx.xxx)**.
@@ -41,8 +41,8 @@ Pour fonctionner de manière optimale, l'Orchestrateur Local nécessite au minim
### Phase 5 : Sécurité Financière & Facturation Exacte 💰 ### Phase 5 : Sécurité Financière & Facturation Exacte 💰
- [x] Pré-calcul local du nombre de tokens (via `tiktoken`) pour estimer le coût avant envoi (Module `billing_manager.py`). Bloquage automatique si coût > 0. - [x] Pré-calcul local du nombre de tokens (via `tiktoken`) pour estimer le coût avant envoi (Module `billing_manager.py`). Bloquage automatique si coût > 0.
- [x] Synchronisation dynamique du catalogue OpenRouter et calcul de facturation au token réel (`tokens_in` / `tokens_out` via `response.usage`). <!-- MAJ 20/08/2026 --> - [x] Synchronisation dynamique du catalogue OpenRouter et calcul de facturation au token réel (`tokens_in` / `tokens_out` via `response.usage`).
- [ ] Interface UI : Affichage du Pop-Up de validation du coût pour l'utilisateur. - [x] Interface UI : Affichage du Pop-Up OBLIGATOIRE de validation du coût pour TOUS les utilisateurs.
- [ ] Cryptage absolu de toutes les données sensibles et bancaires. - [ ] Cryptage absolu de toutes les données sensibles et bancaires.
- [ ] Module de conversion des devises en temps réel (Taux du jour) pour un affichage transparent. - [ ] Module de conversion des devises en temps réel (Taux du jour) pour un affichage transparent.
- [ ] **Utilisateurs Privilégiés (Zéro Marge) :** A définir par Admin/Super Admin. Autorisation de virements manuels pour la cagnotte. - [ ] **Utilisateurs Privilégiés (Zéro Marge) :** A définir par Admin/Super Admin. Autorisation de virements manuels pour la cagnotte.
@@ -50,11 +50,11 @@ Pour fonctionner de manière optimale, l'Orchestrateur Local nécessite au minim
- [ ] Export comptable Excel (TCD) par utilisateur via Pandas/Openpyxl et archivage PDF automatisé. - [ ] Export comptable Excel (TCD) par utilisateur via Pandas/Openpyxl et archivage PDF automatisé.
### Phase 6 : Nouvelles Capacités IA (Spécialisations & Modèles) 🤖 ### Phase 6 : Nouvelles Capacités IA (Spécialisations & Modèles) 🤖
*Note : L'infrastructure bascule sur un catalogue dynamique pour pallier les décommissionnements constants des fournisseurs tiers.* *Note : L'infrastructure bascule sur un catalogue dynamique via `modules/model_updater.py` pour pallier les décommissionnements constants des fournisseurs tiers (MAJ toutes les 2h).*
| Domaine de tâche | Modèle / Moteur | Fournisseur / Hébergement | Rôle principal | | Domaine de tâche | Modèle / Moteur | Fournisseur / Hébergement | Rôle principal |
| :--- | :--- | :--- | :--- | | :--- | :--- | :--- | :--- |
| **Orchestration & Tri** | Qwen 2.5 Coder (3B) | Local (GTX 1650 / Ollama) | Analyse l'intention, juge de sécurité et route vers la bonne IA <!-- MAJ 20/08/2026 --> | | **Orchestration & Tri** | Llama 3.2 3B Instruct Abliterated | Local (GTX 1650 / Ollama) | Analyse l'intention, juge de sécurité sans censure et route vers la bonne IA |
| **Bureautique & Code** | à définir par le Super Admin | Programmation, scripts, macros VBA, mathématiques | | **Bureautique & Code** | à définir par le Super Admin | Programmation, scripts, macros VBA, mathématiques |
| **Images HD / Fichiers** | à définir par le Super Admin | Analyse d'images/vidéos et documents joints | | **Images HD / Fichiers** | à définir par le Super Admin | Analyse d'images/vidéos et documents joints |
| **Génération Multimédia** | à définir par le Super Admin | Création d'images et vidéos (Text-to-Image / Text-to-Video) | | **Génération Multimédia** | à définir par le Super Admin | Création d'images et vidéos (Text-to-Image / Text-to-Video) |
@@ -67,18 +67,18 @@ Pour fonctionner de manière optimale, l'Orchestrateur Local nécessite au minim
- [ ] Création d'un Wiki détaillé multilingue pour l'installation personnelle. - [ ] Création d'un Wiki détaillé multilingue pour l'installation personnelle.
### Phase 9 : Modération Automatisée & Conformité Pénale 🚨 ### Phase 9 : Modération Automatisée & Conformité Pénale 🚨
<!-- MAJ 19/08/2026 : Intégration du Juge Qwen et gestion d'audit pénal -->
- [x] Système hiérarchique strict : `Super Admin` (Installateur), `Admin` (Modération/VIP), `VIP` (Grade de filiation sans privilèges d'administration) et Utilisateurs. - [x] Système hiérarchique strict : `Super Admin` (Installateur), `Admin` (Modération/VIP), `VIP` (Grade de filiation sans privilèges d'administration) et Utilisateurs.
- [ ] Validation obligatoire des CGV/CGU incluant une clause de "Ban sans remboursement et signalement aux autorités" en cas de contenu manifestement illicite. - [ ] Validation obligatoire des CGV/CGU incluant une clause de "Ban sans remboursement et signalement aux autorités" en cas de contenu manifestement illicite.
- [x] Modération préventive "Anti-Zèle" : Filtrage par IA Locale (Qwen) des intentions illégales bloquant la requête vers OpenRouter tout en tolérant la fiction et les demandes juridiques. - [x] Modération préventive "Anti-Zèle" : Filtrage par IA Locale des intentions illégales bloquant la requête vers OpenRouter tout en tolérant la fiction et les demandes juridiques.
- [x] Système d'Avertissements (Strikes) automatisé avec alerte E-mail aux Admins. - [x] Système d'Avertissements (Strikes) automatisé avec alerte E-mail aux Admins.
- [x] Protection anti-abus Admin : Si un Admin enfreint la loi, il est exclu de l'alerte ; seuls les autres Admins statuent. - [x] Protection anti-abus Admin : Si un Admin enfreint la loi, il est exclu de l'alerte ; seuls les autres Admins statuent.
- [x] Archivage légal sécurisé : Stockage pérenne des logs, preuves d'infractions et **décisions d'audit manuscrites des Admins** dans une arborescence dédiée. - [x] Archivage légal sécurisé : Stockage pérenne des logs, preuves d'infractions et **décisions d'audit manuscrites des Admins** dans une arborescence dédiée.
### Phase 10 : Base de Connaissances Intelligente (RAG) 📚 ### Phase 10 : Base de Connaissances Intelligente (RAG) 📚
- [x] Initialisation de l'arborescence dynamique (`modules/rag_manager.py`) sur le RAID (`/DATA/AppData/MULTI-IA-AETHAS38/RAG/`). - [x] Initialisation de l'arborescence dynamique (`modules/rag_manager.py`) sur le RAID (`/DATA/AppData/MULTI-IA-AETHAS38/RAG/`).
- [x] Évolution de l'Orchestrateur Local (Qwen) pour la sélection automatique du dossier sémantique pertinent ("minecraft", "droit", etc.). - [x] Évolution de l'Orchestrateur Local pour la sélection automatique du dossier sémantique pertinent ("minecraft", "droit", etc.).
- [x] Injection asynchrone des documents Markdown et Texte directement dans la fenêtre de contexte des LLMs. - [x] Injection asynchrone des documents Markdown et Texte directement dans la fenêtre de contexte des LLMs.
- [x] Auto-Apprentissage : Le système génère automatiquement des fichiers `.md` de son propre savoir après validation.
- [ ] Interface UI : Ajout d'une vue Administrateur pour téléverser de nouveaux documents RAG directement depuis le navigateur. - [ ] Interface UI : Ajout d'une vue Administrateur pour téléverser de nouveaux documents RAG directement depuis le navigateur.
## 📷 Vidéosurveillance & Détection Intelligente ## 📷 Vidéosurveillance & Détection Intelligente
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"cagnotte_commune_vip": 35.0 "cagnotte_commune_vip": 34.988855
} }
+57
View File
@@ -0,0 +1,57 @@
[
{
"timestamp": 1787262333.1032357,
"date": "2026-08-20 23:45:33",
"utilisateur": "xavier",
"rang": "Super Admin",
"type_operation": "DEBIT_REQUETE",
"modele": "anthropic/claude-3.5-sonnet",
"tokens": 87,
"cout_brut_api": 0.00261,
"prix_facture_client": 0.00261
},
{
"timestamp": 1787262606.6284242,
"date": "2026-08-20 23:50:06",
"utilisateur": "xavier",
"rang": "Super Admin",
"type_operation": "DEBIT_REQUETE",
"modele": "anthropic/claude-3.5-sonnet",
"tokens": 88,
"cout_brut_api": 0.00132,
"prix_facture_client": 0.00132
},
{
"timestamp": 1787263101.5169146,
"date": "2026-08-20 23:58:21",
"utilisateur": "xavier",
"rang": "Super Admin",
"type_operation": "DEBIT_REQUETE",
"modele": "anthropic/claude-3.5-sonnet",
"tokens": 88,
"cout_brut_api": 0.00264,
"prix_facture_client": 0.00264
},
{
"timestamp": 1787344221.9884093,
"date": "2026-08-21 22:30:21",
"utilisateur": "xavier",
"rang": "Super Admin",
"type_operation": "DEBIT_REQUETE",
"modele": "anthropic/claude-3.5-sonnet",
"tokens": 17,
"cout_brut_api": 0.000255,
"prix_facture_client": 0.000255
},
{
"timestamp": 1787348873.0218458,
"date": "2026-08-21 23:47:53",
"utilisateur": "xavier",
"rang": "Super Admin",
"type_operation": "DEBIT_REQUETE",
"modele": "anthropic/claude-3.5-sonnet",
"tokens": 288,
"cout_brut_api": 0.00432,
"prix_facture_client": 0.00432
}
]
+7038
View File
File diff suppressed because it is too large. Load diff
+22 -5
View File
@@ -11,6 +11,9 @@ from modules.chat_handler import handle_chat_request
from modules.security import verifier_et_bloquer_vpn, journaliser_ip from modules.security import verifier_et_bloquer_vpn, journaliser_ip
from modules.auth_manager import IDENTIFIANTS_ADMIN, verifier_mot_de_passe from modules.auth_manager import IDENTIFIANTS_ADMIN, verifier_mot_de_passe
# Lancement du module d'auto-mise à jour en arrière-plan
import modules.model_updater
# --- MIDDLEWARE DE SÉCURITÉ --- # --- MIDDLEWARE DE SÉCURITÉ ---
class SecurityMiddleware(BaseHTTPMiddleware): class SecurityMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next): async def dispatch(self, request, call_next):
@@ -20,6 +23,9 @@ class SecurityMiddleware(BaseHTTPMiddleware):
if not request.url.path.startswith("/Logo") and not request.url.path.startswith("/login"): if not request.url.path.startswith("/Logo") and not request.url.path.startswith("/login"):
if verifier_et_bloquer_vpn(ip, utilisateur): if verifier_et_bloquer_vpn(ip, utilisateur):
return HTMLResponse("<h1 style='color:red; text-align:center;'>Accès refusé</h1><p style='text-align:center;'>VPN ou Proxy strictement interdit.</p>", status_code=403) return HTMLResponse("<h1 style='color:red; text-align:center;'>Accès refusé</h1><p style='text-align:center;'>VPN ou Proxy strictement interdit.</p>", status_code=403)
# Exclusion stricte du spam de logs (Silence pour /ping et /favicon)
if request.url.path not in ["/ping", "/favicon.ico"]:
journaliser_ip(ip, f"Accès à {request.url.path}") journaliser_ip(ip, f"Accès à {request.url.path}")
return await call_next(request) return await call_next(request)
@@ -45,7 +51,7 @@ async def post(request, session):
if form.get("email") == IDENTIFIANTS_ADMIN["email"] and verifier_mot_de_passe(form.get("password"), IDENTIFIANTS_ADMIN["mot_de_passe_hash"]): if form.get("email") == IDENTIFIANTS_ADMIN["email"] and verifier_mot_de_passe(form.get("password"), IDENTIFIANTS_ADMIN["mot_de_passe_hash"]):
session['user'] = "xavier" session['user'] = "xavier"
resp = RedirectResponse('/', status_code=303) resp = RedirectResponse('/', status_code=303)
resp.set_cookie("aethas_user", "xavier", max_age=86400) # Utilisé par le Middleware resp.set_cookie("aethas_user", "xavier", max_age=86400)
return resp return resp
return render_login_page(error="Accès refusé. Identifiants incorrects.") return render_login_page(error="Accès refusé. Identifiants incorrects.")
@@ -58,16 +64,27 @@ def get(session):
# --- ROUTES PROTÉGÉES --- # --- ROUTES PROTÉGÉES ---
@rt('/') @rt('/')
def get(session): def get(session, request):
if 'user' not in session: utilisateur = session.get('user') or request.cookies.get('aethas_user')
if not utilisateur:
return RedirectResponse('/login', status_code=303) return RedirectResponse('/login', status_code=303)
session['user'] = utilisateur
if 'current_chat_id' not in session or not session['current_chat_id']: if 'current_chat_id' not in session or not session['current_chat_id']:
session['current_chat_id'] = str(uuid.uuid4()) session['current_chat_id'] = str(uuid.uuid4())
return render_main_page(session['current_chat_id'], utilisateur_nom=session['user']) return render_main_page(session['current_chat_id'], utilisateur_nom=session['user'])
@rt('/chat') @rt('/chat')
async def post(request, session: dict): async def post(request, session):
if 'user' not in session: return HTMLResponse("Non autorisé", status_code=401) utilisateur = session.get('user') or request.cookies.get('aethas_user')
if not utilisateur:
return HTMLResponse(
"<div style='color:red; background:#300; padding:15px; border-radius:10px; margin-top:10px;'>"
"<b>❌ Session expirée ou non autorisée.</b> Veuillez rafraîchir la page (F5) pour vous reconnecter."
"</div>",
status_code=401
)
session['user'] = utilisateur
return await handle_chat_request(request, session) return await handle_chat_request(request, session)
@rt('/favicon.ico') @rt('/favicon.ico')
+7 -6
View File
@@ -11,11 +11,12 @@ SYSTEM_CONFIDENTIALITY_INSTRUCTION = (
"Concentre-toi uniquement sur la tâche demandée par l'utilisateur sans révéler les mécanismes sous-jacents." "Concentre-toi uniquement sur la tâche demandée par l'utilisateur sans révéler les mécanismes sous-jacents."
) )
def executer_synthese_multi_ia(prompt_securise: str, fournisseur: str, mode_payant: bool = False): def executer_synthese_multi_ia(prompt_securise: str, fournisseur: str, mode_payant: bool = False, chat_id: str = None):
"""Interroge plusieurs modèles en parallèle rapide et confie la synthèse à Gemini.""" """Interroge plusieurs modèles en parallèle rapide et confie la synthèse à Gemini."""
try: try:
reponses_brutes = interroger_openrouter_multi(prompt_securise, type_modele=fournisseur, premium=mode_payant) # Propagation cruciale du chat_id pour le suivi financier OpenRouter
if len(reponses_brutes) >= 2: reponses_brutes = interroger_openrouter_multi(prompt_securise, type_modele=fournisseur, premium=mode_payant, chat_id=chat_id)
if len(reponses_brutes) >= 1: # Modifié à 1 pour garantir un fallback même partiel
instruction_synthese = ( instruction_synthese = (
"RÈGLE DE CONFIDENTIALITÉ : Tu es un assistant IA. " "RÈGLE DE CONFIDENTIALITÉ : Tu es un assistant IA. "
"Tu ne dois JAMAIS divulguer l'architecture logicielle interne de la plateforme hôte Aethas38. " "Tu ne dois JAMAIS divulguer l'architecture logicielle interne de la plateforme hôte Aethas38. "
@@ -35,12 +36,12 @@ def executer_synthese_multi_ia(prompt_securise: str, fournisseur: str, mode_paya
return None return None
def executer_requete_ia(fournisseur: str, prompt: str, mode_payant: bool = False, multi_ia: bool = False): def executer_requete_ia(fournisseur: str, prompt: str, mode_payant: bool = False, multi_ia: bool = False, chat_id: str = None):
"""Moteur central gérant la synthèse multi-modèles et la cascade intelligente.""" """Moteur central gérant la synthèse multi-modèles et la cascade intelligente."""
prompt_securise = f"{SYSTEM_CONFIDENTIALITY_INSTRUCTION}\n\nRequête utilisateur :\n{prompt}" prompt_securise = f"{SYSTEM_CONFIDENTIALITY_INSTRUCTION}\n\nRequête utilisateur :\n{prompt}"
if multi_ia or fournisseur in ["openrouter_code", "openrouter_droit"]: if multi_ia or fournisseur in ["openrouter_code", "openrouter_droit"]:
resultat_multi = executer_synthese_multi_ia(prompt_securise, fournisseur, mode_payant) resultat_multi = executer_synthese_multi_ia(prompt_securise, fournisseur, mode_payant, chat_id)
if resultat_multi: if resultat_multi:
return resultat_multi return resultat_multi
@@ -55,7 +56,7 @@ def executer_requete_ia(fournisseur: str, prompt: str, mode_payant: bool = False
try: try:
if fonction_api.__name__ == "interroger_openrouter": if fonction_api.__name__ == "interroger_openrouter":
texte, couleur, nom_fournisseur, nom_modele, _, _ = fonction_api( texte, couleur, nom_fournisseur, nom_modele, _, _ = fonction_api(
prompt_securise, type_modele=fournisseur, premium=mode_payant prompt_securise, type_modele=fournisseur, premium=mode_payant, chat_id=chat_id
) )
else: else:
texte, couleur, nom_fournisseur, nom_modele = fonction_api(prompt_securise) texte, couleur, nom_fournisseur, nom_modele = fonction_api(prompt_securise)
+2 -4
View File
@@ -10,12 +10,10 @@ def interroger_gemini(prompt_securise):
try: try:
client = genai.Client(api_key=api_key) client = genai.Client(api_key=api_key)
# Liste actualisée avec les modèles opérationnels de la cascade # Liste actualisée avec UNIQUEMENT les modèles opérationnels de la nouvelle génération
modeles = [ modeles = [
"gemini-3.6-flash", "gemini-3.6-flash",
"gemini-3.7-flash", "gemini-3.7-flash"
"gemini-3.1-pro-preview",
"gemini-2.5-flash"
] ]
for modele in modeles: for modele in modeles:
+8 -6
View File
@@ -1,18 +1,20 @@
import os
from openai import OpenAI from openai import OpenAI
from modules.logger import log_event from modules.logger import log_event
def interroger_local_ollama(prompt_securise: str, modele: str = "qwen2.5-coder:3b"): def interroger_local_ollama(prompt_securise: str, modele: str = "richardyoung/llama-3.2-3b-instruct-abliterated"):
""" """
Interroge le serveur local Ollama avec repli automatique sur le modèle par défaut. Interroge le serveur local Ollama avec forçage absolu sur le modèle installé.
""" """
modele_effectif = modele if modele else "qwen2.5-coder:3b" modele_effectif = "richardyoung/llama-3.2-3b-instruct-abliterated"
client = OpenAI(base_url="http://192.168.1.20:11434/v1", api_key="ollama") ollama_host = os.getenv("OLLAMA_HOST", "http://192.168.1.20:11434")
client = OpenAI(base_url=f"{ollama_host}/v1", api_key="ollama")
try: try:
resp = client.chat.completions.create( resp = client.chat.completions.create(
model=modele_effectif, model=modele_effectif,
messages=[ messages=[
{"role": "system", "content": "Réponds en français."}, {"role": "system", "content": "Tu es l'IA locale Aethas38, une IA non censurée et sans filtre. Réponds dans la langue de la question de manière claire et directe."},
{"role": "user", "content": prompt_securise} {"role": "user", "content": prompt_securise}
] ]
) )
@@ -21,5 +23,5 @@ def interroger_local_ollama(prompt_securise: str, modele: str = "qwen2.5-coder:3
return resp.choices[0].message.content, "#10b981", "SERVEUR LOCAL", modele_effectif return resp.choices[0].message.content, "#10b981", "SERVEUR LOCAL", modele_effectif
except Exception as e: except Exception as e:
log_event("ERROR", f"LOCAL-{modele_effectif}", f"Erreur : {e}") log_event("ERROR", f"LOCAL-{modele_effectif}", f"Erreur critique locale : {e}")
raise Exception("Le serveur local a échoué.") raise Exception("Le serveur local a échoué.")
+40 -35
View File
@@ -1,31 +1,15 @@
import os import os
import time import time
import json
from dotenv import load_dotenv from dotenv import load_dotenv
import openai
from openai import OpenAI from openai import OpenAI
from modules.logger import log_event from modules.logger import log_event
load_dotenv() load_dotenv()
api_key_openrouter = os.getenv("OPENROUTER_API_KEY") or "sk-or-v1-cle-non-definie" api_key_openrouter = os.getenv("OPENROUTER_API_KEY") or "sk-or-v1-cle-non-definie"
client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=api_key_openrouter)
client = OpenAI( CATALOGUE_FILE = "/DATA/AppData/MULTI-IA-AETHAS38/catalogue_dynamique.json"
base_url="https://openrouter.ai/api/v1",
api_key=api_key_openrouter
)
CATALOGUE_MODELES = {
"gratuit": {
"openrouter_chat": "google/gemma-4-31b-it:free",
"openrouter_code": "cohere/north-mini-code:free",
"openrouter_droit": "z-ai/glm-5.2:free"
},
"premium": {
"openrouter_chat": "openai/gpt-4o-mini",
"openrouter_code": "anthropic/claude-3.5-sonnet",
"openrouter_droit": "mistralai/mistral-large-2411"
}
}
def verifier_cle_api(): def verifier_cle_api():
cle = os.getenv("OPENROUTER_API_KEY") cle = os.getenv("OPENROUTER_API_KEY")
@@ -33,23 +17,42 @@ def verifier_cle_api():
raise Exception("Clé OPENROUTER_API_KEY absente dans le fichier .env.") raise Exception("Clé OPENROUTER_API_KEY absente dans le fichier .env.")
return cle return cle
def interroger_openrouter(prompt: str, type_modele: str = "openrouter_chat", premium: bool = False, max_retries: int = 2): def obtenir_modele_dynamique(premium: bool):
"""Charge le catalogue dynamiquement selon l'état Premium ou Gratuit."""
try:
if os.path.exists(CATALOGUE_FILE):
with open(CATALOGUE_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
if premium:
paid_models = data.get("paid_models", [])
claude = [m for m in paid_models if "claude-3.5-sonnet" in m.lower()]
return claude[0] if claude else (paid_models[0] if paid_models else "anthropic/claude-3.5-sonnet")
else:
free_models = data.get("free_models", [])
priorites = [m for m in free_models if "gemma" in m.lower() or "nemotron" in m.lower()]
return priorites[0] if priorites else (free_models[0] if free_models else "google/gemma-2-9b-it:free")
except Exception:
pass
# Fallbacks absolus si fichier introuvable
return "anthropic/claude-3.5-sonnet" if premium else "google/gemma-2-9b-it:free"
def interroger_openrouter(prompt: str, type_modele: str = "openrouter_chat", premium: bool = False, max_retries: int = 2, chat_id: str = None):
verifier_cle_api() verifier_cle_api()
gamme = "premium" if premium else "gratuit" nom_modele_api = obtenir_modele_dynamique(premium)
nom_modele_api = CATALOGUE_MODELES[gamme].get(type_modele, CATALOGUE_MODELES[gamme]["openrouter_chat"])
nom_affichage = f"OpenRouter ({nom_modele_api.split('/')[-1]})" nom_affichage = f"OpenRouter ({nom_modele_api.split('/')[-1]})"
couleur = "#d97757" if premium else "#4a90e2" couleur = "#d97757" if premium else "#4a90e2"
# Injection du Tracker Financier OpenRouter
extra_body_data = {"session_id": chat_id} if chat_id else {}
for tentative in range(max_retries): for tentative in range(max_retries):
try: try:
response = client.chat.completions.create( response = client.chat.completions.create(
model=nom_modele_api, model=nom_modele_api,
messages=[{"role": "user", "content": prompt}], messages=[{"role": "user", "content": prompt}],
extra_headers={ extra_headers={"HTTP-Referer": "https://aethas38.duckdns.org", "X-Title": "Aethas38 Multi-IA"},
"HTTP-Referer": "https://aethas38.duckdns.org", extra_body=extra_body_data,
"X-Title": "Aethas38 Multi-IA" timeout=25.0
},
timeout=20.0
) )
texte = response.choices[0].message.content texte = response.choices[0].message.content
usage = response.usage usage = response.usage
@@ -60,19 +63,23 @@ def interroger_openrouter(prompt: str, type_modele: str = "openrouter_chat", pre
except Exception as e: except Exception as e:
log_event("WARNING", "OPENROUTER", f"Tentative {tentative + 1} échouée pour {nom_modele_api} : {e}") log_event("WARNING", "OPENROUTER", f"Tentative {tentative + 1} échouée pour {nom_modele_api} : {e}")
if tentative == max_retries - 1: if tentative == max_retries - 1:
# Force la MAJ du catalogue si le modèle crashe
from modules.model_updater import trigger_update_now
trigger_update_now()
raise e raise e
time.sleep(1) time.sleep(1)
return None, None, None, None, 0, 0 return None, None, None, None, 0, 0
def interroger_openrouter_multi(prompt: str, type_modele: str = "openrouter_code", premium: bool = False) -> list: def interroger_openrouter_multi(prompt: str, type_modele: str = "openrouter_code", premium: bool = False, chat_id: str = None) -> list:
verifier_cle_api() verifier_cle_api()
extra_body_data = {"session_id": chat_id} if chat_id else {}
modeles = [obtenir_modele_dynamique(premium)]
if premium: if premium:
modeles = ["anthropic/claude-3.5-sonnet", "openai/gpt-4o-mini"] modeles.append("openai/gpt-4o-mini")
else: else:
# Modèles gratuits réactifs sans blocage de quota modeles.append("nvidia/nemotron-3-nano-30b-a3b:free")
modeles = ["cohere/north-mini-code:free", "google/gemma-4-31b-it:free"]
reponses = [] reponses = []
for modele in modeles: for modele in modeles:
@@ -80,10 +87,8 @@ def interroger_openrouter_multi(prompt: str, type_modele: str = "openrouter_code
res = client.chat.completions.create( res = client.chat.completions.create(
model=modele, model=modele,
messages=[{"role": "user", "content": prompt}], messages=[{"role": "user", "content": prompt}],
extra_headers={ extra_headers={"HTTP-Referer": "https://aethas38.duckdns.org", "X-Title": "Aethas38 Multi-IA"},
"HTTP-Referer": "https://aethas38.duckdns.org", extra_body=extra_body_data,
"X-Title": "Aethas38 Multi-IA"
},
timeout=15.0 timeout=15.0
) )
contenu = res.choices[0].message.content contenu = res.choices[0].message.content
+89 -30
View File
@@ -1,18 +1,54 @@
import os
import json
from datetime import date
import uuid import uuid
import time import time
import re import re
import psutil
from fasthtml.common import * from fasthtml.common import *
from modules.database import load_history, save_history from modules.database import load_history, save_history
from modules.scraper import rechercher_sur_google, extraire_contenu_urls from modules.scraper import rechercher_sur_google, extraire_contenu_urls
from modules.router import nettoyer_decision_router from modules.router import nettoyer_decision_router
from modules.ai_local import interroger_local_ollama from modules.ai_local import interroger_local_ollama
from modules.ai_engine import executer_requete_ia from modules.ai_engine import executer_requete_ia
from modules.ui_components import render_user_msg, render_ai_msg, render_sidebar from modules.ui_components import render_user_msg, render_ai_msg, render_sidebar, render_popup_paiement
from modules.logger import log_event from modules.logger import log_event
from modules.billing_manager import verifier_et_valider_budget from modules.billing_manager import verifier_et_valider_budget, obtenir_solde
from modules.rag_manager import rechercher_contexte_rag from modules.rag_manager import rechercher_contexte_rag, auto_apprendre_rag
from modules.moderator import analyser_intention_illicite, appliquer_sanction_et_archiver from modules.moderator import analyser_intention_illicite, appliquer_sanction_et_archiver
def check_and_update_resource_quota(utilisateur_nom, role_utilisateur):
"""Vérifie si l'utilisateur a dépassé son quota quotidien d'utilisation intensive du serveur (>60%)."""
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 Exception:
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 # Quota dépassé
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): async def handle_chat_request(request, session):
start_time = time.time() start_time = time.time()
try: try:
@@ -23,6 +59,13 @@ async def handle_chat_request(request, session):
mode_payant = form.get("mode_payant") == "on" mode_payant = form.get("mode_payant") == "on"
mode_multi_ia = form.get("mode_multi_ia") == "on" mode_multi_ia = form.get("mode_multi_ia") == "on"
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 # Le client a reculé devant le prix
session["current_chat_id"] = chat_id session["current_chat_id"] = chat_id
history = load_history() history = load_history()
@@ -31,16 +74,20 @@ async def handle_chat_request(request, session):
role_utilisateur = "Super Admin" if utilisateur_nom.lower() == "xavier" else ("VIP" if utilisateur_nom.lower() in ["céline", "anaëlle", "renan", "audric"] else "standard") 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" ip_client = request.client.host if request.client else "Inconnue"
# Modération préventive locale # Protection CPU/RAM (60%) combinée au Quota Utilisateur
est_illicite, raison = analyser_intention_illicite(prompt, "qwen2.5-coder:3b") 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):
log_event("WARNING", "SYSTEM", f"Surcharge (CPU: {cpu_usage}%, RAM: {ram_usage}%) + Quota intensif dépassé pour {utilisateur_nom}.")
return Div(H3("⚠️ Limite de requêtes intensives atteinte", style="color:#ff9800;"), P(f"Le serveur est actuellement très sollicité (>60%). En tant que '{role_utilisateur}', vous avez épuisé votre quota de passages en force pour aujourd'hui."), style="background:#333; padding:15px; border-radius:10px; border: 1px solid #ff9800;")
else:
log_event("INFO", "SYSTEM", f"Serveur en charge (>60%), mais quota intensif autorisé utilisé par {utilisateur_nom}.")
est_illicite, raison = analyser_intention_illicite(prompt)
if est_illicite: if est_illicite:
sanction = appliquer_sanction_et_archiver(utilisateur_nom, prompt, raison, ip_client) sanction = appliquer_sanction_et_archiver(utilisateur_nom, prompt, raison, ip_client)
return Div( return Div(H3("⚠️ Avertissement de Sécurité"), P("Votre requête enfreint les conditions d'utilisation de la plateforme."), P(f"Sanction : {sanction}"), style="color:red; background:#300; padding:15px; border-radius:10px;")
H3("⚠️ Avertissement de Sécurité"),
P("Votre requête enfreint les conditions d'utilisation de la plateforme."),
P(f"Sanction : {sanction}"),
style="color:red; background:#300; padding:15px; border-radius:10px;"
)
file_summary = [] file_summary = []
file_names = [] file_names = []
@@ -54,7 +101,6 @@ async def handle_chat_request(request, session):
log_mode = "PAYANT 👑" if mode_payant else "GRATUIT 🆓" log_mode = "PAYANT 👑" if mode_payant else "GRATUIT 🆓"
log_event("INFO", "SYSTEM", f"Requête reçue [{log_mode}]. Fichiers joints : {liste_fichiers_str}") log_event("INFO", "SYSTEM", f"Requête reçue [{log_mode}]. Fichiers joints : {liste_fichiers_str}")
# NOUVEAU PROMPT : On demande à Qwen de deviner le thème RAG
sec_prompt = f"""Tu es l'Orchestrateur système. Analyse la requête et choisis le meilleur fournisseur IA : 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. - "gemini" : (OBLIGATOIRE si fichiers joints), rédaction complexe, comparaisons.
- "openrouter_chat" : requêtes générales, texte rapide, traduction. - "openrouter_chat" : requêtes générales, texte rapide, traduction.
@@ -66,7 +112,7 @@ async def handle_chat_request(request, session):
Requête : {prompt}""" Requête : {prompt}"""
try: try:
texte_routeur, _, _, _ = interroger_local_ollama(sec_prompt, "qwen2.5-coder:3b") texte_routeur, _, _, _ = interroger_local_ollama(sec_prompt)
decision = nettoyer_decision_router(texte_routeur) decision = nettoyer_decision_router(texte_routeur)
except Exception: except Exception:
decision = {"intention": "Analyse de secours", "fournisseur": "openrouter_code" if "minecraft" in prompt.lower() else "openrouter_chat", "theme_rag": "aucun"} decision = {"intention": "Analyse de secours", "fournisseur": "openrouter_code" if "minecraft" in prompt.lower() else "openrouter_chat", "theme_rag": "aucun"}
@@ -75,14 +121,17 @@ async def handle_chat_request(request, session):
fournisseur = decision.get("fournisseur", "openrouter_chat").lower() fournisseur = decision.get("fournisseur", "openrouter_chat").lower()
thematique_rag = decision.get("theme_rag", "aucun").lower() thematique_rag = decision.get("theme_rag", "aucun").lower()
if mode_payant or mode_multi_ia:
fournisseur = "openrouter_code"
intention += " (Forcé API Premium/Multi via UI)"
if file_names: if file_names:
fournisseur = "gemini" fournisseur = "gemini"
intention += " (Forcé Gemini via Sécurité Fichiers)" intention += " (Forcé Gemini via Sécurité Fichiers)"
# Récupération dynamique du contexte RAG
contexte_rag = "" contexte_rag = ""
if thematique_rag != "aucun": if thematique_rag != "aucun":
contexte_rag = rechercher_contexte_rag(prompt, thematique=thematique_rag) contexte_rag = rechercher_contexte_rag(prompt, categorie=thematique_rag)
urls = re.findall(r"https?://[^\s]+", prompt) urls = re.findall(r"https?://[^\s]+", prompt)
mots_cles = ["cherche", "recherche", "sur internet", "sur le web", "sur le net", "trouve", "site"] mots_cles = ["cherche", "recherche", "sur internet", "sur le web", "sur le net", "trouve", "site"]
@@ -91,44 +140,54 @@ async def handle_chat_request(request, session):
web_text = extraire_contenu_urls(list(set(urls))) if urls else "" web_text = extraire_contenu_urls(list(set(urls))) if urls else ""
# Concaténation de tous les contextes
full_prompt = prompt + ("\n" + "\n".join(file_summary) if file_summary else "") full_prompt = prompt + ("\n" + "\n".join(file_summary) if file_summary else "")
if contexte_rag: if contexte_rag:
full_prompt += f"\n\n[Contexte Documentaire RAG (Base de connaissances interne) :\n{contexte_rag}]" full_prompt += f"\n\n[Contexte Documentaire RAG (Base de connaissances interne) :\n{contexte_rag}]"
if web_text: if web_text:
full_prompt += f"\n\n[Contexte web :\n{web_text}]" full_prompt += f"\n\n[Contexte web :\n{web_text}]"
# Validation financière
if "openrouter" in fournisseur: if "openrouter" in fournisseur:
modele_estime = "anthropic/claude-3.5-sonnet" if mode_payant else "cohere/north-mini-code:free" modele_estime = "anthropic/claude-3.5-sonnet" if mode_payant else "cohere/north-mini-code:free"
verifier_et_valider_budget(full_prompt, modele_estime, utilisateur_nom=utilisateur_nom, rang_utilisateur=role_utilisateur, multi_ia=mode_multi_ia)
# Exécution rapide if mode_payant and not validation_acquise:
reponse, couleur, fournisseur_nom, modele = executer_requete_ia( from modules.billing_manager import estimer_cout_facture
fournisseur, full_prompt, mode_payant=mode_payant, multi_ia=mode_multi_ia _, 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)
# Empêche un utilisateur classique avec un solde à zéro de consommer du Premium
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("Veuillez recharger votre compte."), style="background:#300; padding:15px; border-radius:10px;")
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: if chat_id not in history:
history[chat_id] = {"title": (prompt[:30] if prompt else "Discussion") + "...", "pinned": False, "timestamp": time.time(), "messages": []} history[chat_id] = {"title": (prompt[:30] if prompt else "Discussion") + "...", "pinned": False, "timestamp": time.time(), "messages": []}
execution_time = round(time.time() - start_time, 1) execution_time = round(time.time() - start_time, 1)
display_msg = prompt display_msg = prompt
if file_names: if file_names:
display_msg += f"\n\n📎 **Fichiers joints :** {', '.join(file_names)}" display_msg += f"\n\n📎 **Fichiers joints :** {', '.join(file_names)}\n> *🗑️ Note : Fichiers traités de manière éphémère en mémoire vive.*"
display_msg += "\n> *🗑️ Note : Fichiers traités de manière éphémère en mémoire vive.*"
history[chat_id]["messages"].append({"role": "user", "content": display_msg}) history[chat_id]["messages"].append({"role": "user", "content": display_msg})
history[chat_id]["messages"].append({ history[chat_id]["messages"].append({"role": "ai", "content": reponse, "intention": intention, "fournisseur": fournisseur_nom, "modele": modele, "couleur": couleur, "execution_time": execution_time})
"role": "ai", "content": reponse, "intention": intention,
"fournisseur": fournisseur_nom, "modele": modele,
"couleur": couleur, "execution_time": execution_time
})
save_history(history) save_history(history)
log_event("INFO", fournisseur_nom, f"Réponse générée en {execution_time}s.") 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() 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: except Exception as e:
if "SOLDE_INSUFFISANT" in str(e):
details = str(e).split("|")
return Div(H3("❌ Solde Insuffisant", style="color:red;"), P(f"Cette requête coûte {details[1]} $, mais il ne vous reste que {details[2]} $."), P("Veuillez recharger votre compte ou désactiver le mode Payant."), style="background:#300; padding:15px; border-radius:10px; border: 1px solid red;")
log_event("CRITICAL", "SYSTEM", f"Erreur dans handle_chat_request : {e}") log_event("CRITICAL", "SYSTEM", f"Erreur dans handle_chat_request : {e}")
return Div(f"Erreur système : {e}", style="color:red; background:#300; padding:15px; border-radius:10px;") return Div(f"Erreur système : {e}", style="color:red; background:#300; padding:15px; border-radius:10px;")
+24 -16
View File
@@ -1,28 +1,36 @@
import logging
from logging.handlers import TimedRotatingFileHandler
import os import os
from datetime import datetime from datetime import datetime
# Le fichier de destination pour les logs locaux LOG_DIR = "/DATA/AppData/MULTI-IA-AETHAS38"
LOG_FILE = "aethas_logs.txt" os.makedirs(LOG_DIR, exist_ok=True)
LOG_FILE = os.path.join(LOG_DIR, "aethas_logs.txt")
logger = logging.getLogger("Aethas38")
logger.setLevel(logging.INFO)
if not logger.handlers:
# Conformité LCEN : Conservation des logs de connexion sur 365 jours (1 an)
handler = TimedRotatingFileHandler(LOG_FILE, when="midnight", interval=1, backupCount=365, encoding="utf-8")
handler.suffix = "%Y-%m-%d"
formatter = logging.Formatter('[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
handler.setFormatter(formatter)
logger.addHandler(handler)
def log_event(niveau, source, message): def log_event(niveau, source, message):
""" msg_formate = f"[{source}] {message}"
Enregistre un événement dans le fichier de log local.
Déclenche l'envoi d'un e-mail si le niveau est ERROR ou CRITICAL.
"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_line = f"[{timestamp}] [{niveau}] [{source}] {message}\n"
# 1. Écriture sécurisée dans le fichier de log local if niveau.upper() == "INFO": logger.info(msg_formate)
try: elif niveau.upper() == "WARNING": logger.warning(msg_formate)
with open(LOG_FILE, "a", encoding="utf-8") as f: elif niveau.upper() == "ERROR": logger.error(msg_formate)
f.write(log_line) elif niveau.upper() == "CRITICAL": logger.critical(msg_formate)
except Exception as e:
print(f"ÉCHEC D'ÉCRITURE LOG : Impossible d'écrire dans {LOG_FILE} : {e}") print(f"[{datetime.now().strftime('%H:%M:%S')}] [{niveau}] {msg_formate}")
# 2. Déclenchement de l'alerte e-mail pour la phase de Debug (Import local pour éviter l'import circulaire)
if niveau in ["ERROR", "CRITICAL"]: if niveau in ["ERROR", "CRITICAL"]:
try: try:
from modules.debug_mailer import envoyer_alerte_debug from modules.debug_mailer import envoyer_alerte_debug
envoyer_alerte_debug(niveau, source, message) envoyer_alerte_debug(niveau, source, message)
except Exception as mail_error: except Exception as mail_error:
print(f"ÉCHEC D'ENVOI E-MAIL : Impossible d'avertir l'administrateur : {mail_error}") pass
+55
View File
@@ -0,0 +1,55 @@
import os
import json
import requests
import threading
import time
from modules.logger import log_event
CATALOGUE_FILE = "/DATA/AppData/MULTI-IA-AETHAS38/catalogue_dynamique.json"
def _do_update():
ollama_host = os.getenv("OLLAMA_HOST", "http://192.168.1.20:11434")
try:
# MAJ locale Abliterated
resp_local = requests.post(
f"{ollama_host}/api/pull",
json={"name": "richardyoung/llama-3.2-3b-instruct-abliterated"},
timeout=300
)
if resp_local.status_code == 200:
log_event("INFO", "UPDATER", "Modèle local Abliterated vérifié/mis à jour.")
# MAJ du catalogue OpenRouter (Gratuits ET Payants)
resp_or = requests.get("https://openrouter.ai/api/v1/models", timeout=30)
if resp_or.status_code == 200:
data = resp_or.json().get("data", [])
modeles_gratuits = []
modeles_payants = []
for m in data:
pricing = m.get("pricing", {})
# Gratuits purs
if pricing.get("prompt") == "0" and pricing.get("completion") == "0":
modeles_gratuits.append(m["id"])
# Ciblage des payants ultra-performants (Claude & OpenAI)
elif "claude-3.5-sonnet" in m["id"].lower() or "gpt-4o" in m["id"].lower():
modeles_payants.append(m["id"])
with open(CATALOGUE_FILE, "w", encoding="utf-8") as f:
json.dump({"free_models": modeles_gratuits, "paid_models": modeles_payants}, f)
log_event("INFO", "UPDATER", f"Catalogue dynamique mis à jour ({len(modeles_gratuits)} gratuits, {len(modeles_payants)} payants).")
except Exception as e:
log_event("ERROR", "UPDATER", f"Erreur lors de la MAJ auto : {e}")
def trigger_update_now():
"""Déclenchement forcé du scan en cas d'erreur de modèle."""
threading.Thread(target=_do_update, daemon=True).start()
def update_models_background():
_do_update()
while True:
time.sleep(7200) # Attente 2 heures
_do_update()
threading.Thread(target=update_models_background, daemon=True).start()
+5 -4
View File
@@ -23,9 +23,10 @@ def analyser_intention_illicite(prompt: str, ai_orchestrateur: str = "qwen2.5-co
""" """
modele_cible = ai_orchestrateur if ai_orchestrateur else "qwen2.5-coder:3b" modele_cible = ai_orchestrateur if ai_orchestrateur else "qwen2.5-coder:3b"
# NOUVEAU PROMPT : Immunité absolue pour les jeux vidéo et la fiction
prompt_juge = f"""Tu es le Juge de Sécurité de la plateforme logicielle Aethas38. prompt_juge = f"""Tu es le Juge de Sécurité de la plateforme logicielle Aethas38.
Ta mission est d'analyser la requête et de détecter UNIQUEMENT les intentions strictement illégales (pédopornographie, drogues, terrorisme, haine). Ta mission est d'analyser la requête et de détecter UNIQUEMENT les intentions strictement et réellement illégales (pédopornographie, vente de drogues réelles, terrorisme dans le monde réel, discours de haine).
RÈGLE ANTI-ZÈLE : Ne bloque QUE l'intention malveillante. Laisse passer les questions juridiques légitimes, la météo, l'aide à l'écriture de fiction ou les infos neutres. RÈGLE ANTI-ZÈLE ET IMMUNITÉ GAMING : Tu dois OBLIGATOIREMENT laisser passer et considérer comme LÉGAL tout ce qui relève des jeux vidéo (comme Minecraft, les mods type Draconic Evolution ou Applied Energistics, KubeJS, les serveurs), de la fiction, du roleplay, ou des questions juridiques. La construction de "réacteurs", de "bombes", ou de "pièges" dans un contexte de jeu vidéo est TOTALEMENT AUTORISÉE.
Renvoie STRICTEMENT un JSON valide : {{"illegal": true ou false, "raison": "Explication très courte ou vide"}} Renvoie STRICTEMENT un JSON valide : {{"illegal": true ou false, "raison": "Explication très courte ou vide"}}
Requête à analyser : {prompt}""" Requête à analyser : {prompt}"""
@@ -42,8 +43,8 @@ Requête à analyser : {prompt}"""
except Exception as e: except Exception as e:
log_event("WARNING", "MODERATOR", f"Échec de l'analyse IA (Fallback activé) : {e}") log_event("WARNING", "MODERATOR", f"Échec de l'analyse IA (Fallback activé) : {e}")
# Fallback classique par mots-clés si l'IA locale est indisponible # Fallback affiné pour ne pas bloquer les mots courants du jeu vidéo
mots_interdits = ["pédopornographie", "bombe", "terrorisme"] mots_interdits = ["pédopornographie", "attentat terroriste réel", "vente de drogue illicite"]
if any(mot in prompt.lower() for mot in mots_interdits): if any(mot in prompt.lower() for mot in mots_interdits):
return True, "Détection par filtre de mots-clés (Secours)" return True, "Détection par filtre de mots-clés (Secours)"
return False, "" return False, ""
+35 -4
View File
@@ -1,4 +1,5 @@
import os import os
from datetime import datetime
from modules.logger import log_event from modules.logger import log_event
# Définition du chemin racine sécurisé sur ton RAID # Définition du chemin racine sécurisé sur ton RAID
@@ -53,16 +54,46 @@ def extraire_contexte_rag(thematique):
def rechercher_contexte_rag(prompt, categorie="general"): def rechercher_contexte_rag(prompt, categorie="general"):
""" """
Fonction appelée par le chat_handler pour récupérer le contexte. Bouchon temporaire : empêche le plantage en attendant la vectorisation sémantique avancée.
Pour l'instant, c'est un bouchon qui empêche le crash du serveur.
""" """
try: try:
# À terme, nous ferons le lien intelligent ici avec extraire_contexte_rag()
# et une analyse sémantique. Pour l'instant on renvoie vide pour tester.
return "" return ""
except Exception as e: except Exception as e:
log_event("ERROR", "RAG_MANAGER", f"Erreur lors de la recherche RAG : {e}") log_event("ERROR", "RAG_MANAGER", f"Erreur lors de la recherche RAG : {e}")
return "" return ""
def auto_apprendre_rag(prompt, reponse, thematique_simplifiee):
"""
Sauvegarde automatiquement les réponses pertinentes pour enrichir la base de connaissances.
"""
# Mapping entre le mot-clé deviné par Qwen et le vrai nom de ton dossier
mapping = {
"minecraft": "Minecraft_KubeJS",
"droit": "Juridique_Loi_Francaise",
"serveur": "Serveur_Debian",
"excel": "Bureautique_Excel",
"regles": "Regles_Aethas38"
}
theme_reel = mapping.get(thematique_simplifiee.lower())
if not theme_reel:
return # Si Qwen n'a pas détecté de thème précis (ex: "aucun"), on n'enregistre rien pour ne pas polluer.
try:
chemin_theme = os.path.join(RAG_BASE_DIR, theme_reel)
os.makedirs(chemin_theme, exist_ok=True)
nom_fichier = f"apprentissage_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"
chemin_fichier = os.path.join(chemin_theme, nom_fichier)
contenu = f"## Question utilisateur\n{prompt}\n\n## Réponse de l'IA (Contexte validé)\n{reponse}\n"
with open(chemin_fichier, 'w', encoding='utf-8') as f:
f.write(contenu)
log_event("INFO", "RAG_MANAGER", f"Nouvelle connaissance acquise et sauvegardée dans {theme_reel} ({nom_fichier}).")
except Exception as e:
log_event("ERROR", "RAG_MANAGER", f"Erreur lors de l'auto-apprentissage : {e}")
# On s'assure que les dossiers existent dès l'import du module # On s'assure que les dossiers existent dès l'import du module
initialiser_arborescence_rag() initialiser_arborescence_rag()
+20 -1
View File
@@ -158,7 +158,6 @@ def render_main_page(chat_id, utilisateur_nom="xavier", role="Super Admin"):
) )
def render_admin_moderation_panel(utilisateur_cible): def render_admin_moderation_panel(utilisateur_cible):
"""Composant exclusif aux administrateurs pour statuer sur les infractions."""
return Div( return Div(
H3(f"⚖️ Tribunal d'administration - Dossier de : {utilisateur_cible}", style="color: #ff4444;"), H3(f"⚖️ Tribunal d'administration - Dossier de : {utilisateur_cible}", style="color: #ff4444;"),
P("Loi applicable : Aucun utilisateur n'est dispensé du respect de la législation en vigueur."), P("Loi applicable : Aucun utilisateur n'est dispensé du respect de la législation en vigueur."),
@@ -216,3 +215,23 @@ def render_login_page(error=None):
), ),
style="background-color: #0d0d0d; color: #fff; font-family: monospace; height: 100vh; display: flex; align-items: center; justify-content: center;" style="background-color: #0d0d0d; color: #fff; font-family: monospace; height: 100vh; display: flex; align-items: center; justify-content: center;"
) )
def render_popup_paiement(cout_estime, prompt_sauvegarde, chat_id, multi_ia_state=False):
multi_ia_val = "on" if multi_ia_state else "off"
return Div(
Div(
H3("⚠️ Validation Financière Requise", style="color: #ff9800; margin-top: 0;"),
P("Cette requête nécessite l'utilisation d'un modèle d'IA payant.", style="color: #ccc;"),
P(f"Coût estimé (TTC) : {cout_estime} $", style="font-size: 1.2em; font-weight: bold; color: #fff; background: #333; padding: 10px; border-radius: 5px; text-align: center;"),
Div(
Button("Annuler la requête", type="button", onclick="document.getElementById('popup-paiement').remove();", cls="btn-submit", style="background-color: #555; width: 30%; font-size: 0.8em;"),
Form(Input(type="hidden", name="prompt", value=prompt_sauvegarde), Input(type="hidden", name="chat_id", value=chat_id), Input(type="hidden", name="mode_payant", value="off"), Input(type="hidden", name="mode_multi_ia", value=multi_ia_val), Input(type="hidden", name="validation_acquise", value="true"), Input(type="hidden", name="choix_utilisateur", value="gratuit_force"), Button("Tenter en Gratuit ⚠️", type="submit", onclick="document.getElementById('popup-paiement').remove();", cls="btn-submit", style="background-color: #00e5ff; width: 100%; color: black; font-size: 0.8em;"), hx_post="/chat", hx_target="#chat-history", hx_swap="beforeend", hx_indicator="#global-loader", style="width: 30%;"),
Form(Input(type="hidden", name="prompt", value=prompt_sauvegarde), Input(type="hidden", name="chat_id", value=chat_id), Input(type="hidden", name="mode_payant", value="on"), Input(type="hidden", name="mode_multi_ia", value=multi_ia_val), Input(type="hidden", name="validation_acquise", value="true"), Input(type="hidden", name="choix_utilisateur", value="paiement_accepte"), Button("Payer l'API", type="submit", onclick="document.getElementById('popup-paiement').remove();", cls="btn-submit", style="background-color: #4caf50; width: 100%; font-size: 0.8em;"), hx_post="/chat", hx_target="#chat-history", hx_swap="beforeend", hx_indicator="#global-loader", style="width: 30%;"),
style="display: flex; justify-content: space-between; margin-top: 20px; gap: 10px;"
), style="background: #1a1a1a; padding: 25px; border-radius: 12px; border: 2px solid #ff9800; width: 500px; box-shadow: 0 10px 30px rgba(0,0,0,0.8);"
), id="popup-paiement", style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; z-index: 9999;"
)