fix: Migration vers le nouveau SDK Google GenAI et mise à jour des dépendances
Build and Push Docker Image / build-and-push (push) Successful in 2m4s

This commit is contained in:
xavier committed 2026-08-19 02:56:25 +02:00
1 parent 5a4e7dd094
commit a10c854b2b
11 files changed
+2109 -87

No files matched your search

File diff suppressed because it is too large. Load diff
+52 -37
View File
@@ -1,12 +1,14 @@
# Multi-IA Orchestrateur
# Aethas38 - Multi-IA Orchestrateur (Minecraft-Tech Edition)
Plateforme SaaS d'IA modulaire : Orchestrateur local, routeur intelligent, sécurisé et facturable.
<!-- MAJ 18/08/2026 : URL de production masquée pour raisons de cybersécurité (Information Disclosure) -->
**Architecture réseau :** Déploiement via Docker derrière Nginx Proxy Manager (NPM) sur `https://ia.votre-domaine.com`.
## 📜 Licence & Droits d'Utilisation (EULA)
Ce projet est distribué sous une licence "Source Available" propriétaire (EULA).
Ce projet est distribué sous une licence "Source Available" propriétaire (EULA) rédigée par Xavier Fernandez (Aethas38).
* **Modifications & Partage :** Les utilisateurs sont autorisés à modifier et améliorer le code, à condition de partager ces améliorations avec la communauté.
* **Restrictions strictes :** Il est **strictement interdit** de modifier ou supprimer les crédits de l'auteur originel, de retirer le module de dons dédié au créateur, ou de désactiver la télémétrie de sécurité à l'installation. L'installation requiert l'acceptation explicite de ces termes.
* **Donations obligatoires :** Le système intègre nativement deux modules de dons. L'un est modifiable par l'hébergeur du projet. L'autre (marqué "Soutenir le créateur de ce projet") est inaltérable et relié directement au créateur originel. Le retrait de ce module est une violation stricte de cette licence.
* **Restrictions strictes :** Il est **strictement interdit** de modifier ou supprimer les crédits de l'auteur originel, de retirer le module de dons dédié au créateur, ou de désactiver la télémétrie de sécurité (notification d'installation avec IP/Nom admin envoyée à `admin@mail.aethas38.duckdns.org`). L'installation requiert l'acceptation explicite de ces termes.
* **Donations obligatoires :** Le système intègre nativement deux modules de dons. L'un est modifiable par l'hébergeur du projet. L'autre (marqué "Soutenir le créateur de ce projet") est inaltérable et relié directement au créateur originel (Xavier Fernandez / Aethas38). Le retrait de ce module est une violation stricte de cette licence.
## ⚠️ Pré-requis Matériels (Important)
Pour fonctionner de manière optimale, l'Orchestrateur Local nécessite au minimum **une carte graphique avec 4 Go de VRAM dédiée**. Il est possible de configurer l'application pour utiliser le CPU, mais les performances seront considérablement réduites et les temps de réponse allongés. **L'auteur décline toute responsabilité en cas de surcharge matérielle liée à une utilisation CPU intensive.**
@@ -14,50 +16,63 @@ Pour fonctionner de manière optimale, l'Orchestrateur Local nécessite au minim
## 📋 To-Do List (Roadmap)
### Phase 1 & 2 : Infrastructure & Backend de Base ✅
- [x] Installation Docker, Pare-feu
- [x] Serveur Local Ollama + FastHTML
- [x] Routeur Multi-IA (OpenRouter, Gemini, Local) avec système de cascade
- [x] Installation Docker, Nginx (NPM), Pare-feu, Gitea/GitHub
- [x] Serveur Local Ollama (Qwen 3B) + FastHTML (Adapté aux processeurs limités type AMD Phenom)
- [x] Routeur Multi-IA (OpenRouter, Gemini, Local) avec système de cascade et directives d'aiguillage strictes
- [x] Continuité de service : IA de secours automatique
### Phase 3 : Refonte UI & Expérience Utilisateur ✅
- [x] Thème "Minecraft-Tech" (Inspiré serveurs/redstone) compatible Mobile (Responsive)
- [x] Historique (Sidebar) avec Renommage, Épinglage, Suppression et Navigation isolée
- [x] Zone de saisie intelligente (Drag & Drop, multi-fichiers)
- [x] Export modulaire (PDF, DOC, TXT, JSON, ZIP)
- [x] Historique (Sidebar) avec Renommage, Épinglage, Suppression et Navigation isolée par session
- [x] Zone de saisie intelligente (Textarea auto-adaptatif, Drag & Drop avec pills/miniatures, Collage multi-fichiers jusqu'à 10)
- [x] Export modulaire et propre (PDF, DOC, TXT, JSON, ZIP)
- [x] Boutons de partage direct (WhatsApp, Email)
- [x] Indicateur d'avancement dynamique (Loader Néon) pendant le traitement des IA
- [x] Architecture modulaire propre (Dossier `modules/`)
### Phase 4 : Gestion des Utilisateurs & Sécurité Légale
- [ ] Mettre en place une bibliothèque logicielle autonome sur le serveur.
- [ ] 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.
- [ ] Système de connexion sécurisé avec configuration du premier lancement.
- [x] Système de cryptage avancé (Fernet) et de Hashing (Bcrypt) mis en place.
- [x] Sécurité : Protection Bruteforce. **Blocage des accès VPN/Proxy (Passe-droit exclusif configuré pour les profils VIP/Admin et réseaux locaux).**
- [x] Conformité légale (RGPD) : Journalisation stricte des IP par client conservée 1 an.
- [ ] Multilinguisme : Adaptation automatique de la langue du site via IP.
- [ ] Système de connexion sécurisé avec configuration du premier lancement (DOB depuis 1900, Email, Clés API). L'utilisateur Xavier a obligatoirement le rôle "Dieu tout puissant" (Admin global).
- [x] Système de cryptage de grade militaire (Fernet) et de Hashing (Bcrypt) mis en place (Module `auth_manager.py`).
- [X] Sécurité Légère : Fail2Ban, protection Bruteforce et DDoS. **Blocage total des accès VPN**.
- [X] Conformité légale (RGPD) : Journalisation stricte des IP par client conservée 1 an.
- [ ] Multilinguisme : Adaptation automatique de la langue du site via la géolocalisation de l'IP.
### Phase 5 : Sécurité Financière & Facturation 💰
<!-- MAJ 18/08/2026 : Intégration du module financier, calcul des marges et interface de validation -->
- [x] Pré-calcul local du nombre de tokens (via `tiktoken`) pour estimer le coût avant envoi.
- [x] Intégration du gestionnaire financier strict : simulation des coûts, blocage immédiat si le solde est insuffisant et gestion spécifique pour les utilisateurs VIP (Zéro marge/Zéro coût).
- [ ] Interface UI : Affichage du Pop-Up de validation du coût et affichage de la cagnotte utilisateur.
- [ ] Cryptage absolu des données sensibles bancaires.
- [ ] Module de conversion des devises en temps réel.
- [x] **Gestionnaire Financier :** Majoration automatique (15%) appliquée selon les statuts pour générer la facturation finale.
- [ ] Export comptable Excel (TCD) avec archivage PDF automatisé (Admin uniquement).
### 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.
- [ ] Interface UI : Affichage du Pop-Up de validation du coût pour l'utilisateur.
- [ ] 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.
- [ ] **Utilisateurs Privilégiés (Zéro Marge) :** Xavier, Céline, Anaëlle, Renan et Audric. Autorisation de virements manuels pour la cagnotte.
- [x] **Marge Commerciale :** Majoration automatique via la formule `Prix_Facture_HT = Cout_API_OpenRouter_HT / 0.85` (soit 15% de marge) implémentée dans le gestionnaire financier.
- [ ] Export comptable Excel (TCD) par utilisateur via Pandas/Openpyxl et archivage PDF automatisé.
### Phase 6 : Nouvelles Capacités IA (Spécialisations & Modèles) 🤖
*Note : L'infrastructure bascule sur OpenRouter pour palier aux décommissionnements constants des autres fournisseurs.*
| Domaine de tâche | Moteur | Rôle principal |
| :--- | :--- | :--- |
| **Orchestration & Tri** | Local (Ollama) | Analyse l'intention, gère le chat rapide et route vers la bonne IA |
| **Bureautique & Code** | OpenRouter API | Programmation, scripts, macros VBA, mathématiques |
| **Images HD / Fichiers** | Google API | Analyse d'images/vidéos et documents joints |
| **Génération Multimédia** | HuggingFace / Fal.ai | Création d'images et vidéos (Text-to-Image / Text-to-Video) |
| Domaine de tâche | Modèle / Moteur | Fournisseur / Hébergement | Rôle principal |
| :--- | :--- | :--- | :--- |
| **Orchestration & Tri** | Qwen 2.5 (3B) | Local (GTX 1650 / Ollama) | Analyse l'intention, gère le chat rapide et route vers la bonne IA |
| **Bureautique & Code** | Llama 3.1 / Qwen 2.5 Coder | OpenRouter API (Gratuit) | Programmation, scripts, macros VBA, mathématiques |
| **Images HD / Fichiers** | Gemini 2.0 Flash / Pro | Google AI Studio | Analyse d'images/vidéos et documents joints |
| **Génération Multimédia** | Stable Diffusion / Flux / Luma | HuggingFace / Fal.ai | Création d'images et vidéos (Text-to-Image / Text-to-Video) |
### Phase 7 & 8 : Maintenance & Déploiement
<!-- MAJ : Ajout de la configuration du Runner et de la CI/CD -->
- [ ] Création d'un Wiki d'installation détaillé multilingue.
- [x] Fichiers `docker-compose.yml` et `Dockerfile` préparés pour le déploiement conteneurisé.
- [x] Configuration du Webhook et script Bash de déploiement automatique (`git pull` + Docker Compose Rebuild) ciblant la stack `AETHAS38_MULTI_IA`.
- [x] Stratégie de mise à jour transparente via Gitea (dépôt public pour automatisation fluide sans authentification complexe).
- [x] Déploiement du Runner Gitea local et configuration du workflow CI/CD (`docker-build.yml`) pour la génération automatique du package (image Docker).
- [ ] Script de surveillance de l'espace disque du serveur (Beszel / Psutil) réservé à l'Admin.
- [ ] Sauvegarde automatique : Compression du site toutes les 6 heures vers GDrive (`25-BACKUP SITE`), 4 versions conservées.
- [x] Hébergement sur Gitea/Github des versions Docker. Configuration du fichier `docker-compose.yml` et `Dockerfile` réalisée.
- [x] Script Bash de déploiement automatique via `aethas38_multi_ia/update_aethas.sh`.
- [ ] Création d'un Wiki détaillé multilingue pour l'installation personnelle.
### 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` (Xavier), `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.
- [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] 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] 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 sur grappe RAID 1.
## 📷 Vidéosurveillance & Détection Intelligente
*(Voir configuration Frigate locale et Google Coral TPU existante)*
+122
View File
@@ -0,0 +1,122 @@
[
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /",
"date": "2026-08-19 02:46:12",
"timestamp": 1787100372.9574325
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:46:18",
"timestamp": 1787100378.0617583
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:46:23",
"timestamp": 1787100383.052557
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:46:28",
"timestamp": 1787100388.056085
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:46:33",
"timestamp": 1787100393.0593455
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:46:38",
"timestamp": 1787100398.052747
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:46:43",
"timestamp": 1787100403.052715
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:46:48",
"timestamp": 1787100408.9220579
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:46:53",
"timestamp": 1787100413.9221146
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:54:23",
"timestamp": 1787100863.280455
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /",
"date": "2026-08-19 02:54:23",
"timestamp": 1787100863.340981
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:54:28",
"timestamp": 1787100868.9211378
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:54:33",
"timestamp": 1787100873.9200711
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:54:38",
"timestamp": 1787100878.9196937
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:54:43",
"timestamp": 1787100883.9212368
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:54:48",
"timestamp": 1787100888.9214082
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:54:53",
"timestamp": 1787100893.9216979
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:54:59",
"timestamp": 1787100899.2418387
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:55:03",
"timestamp": 1787100903.9211195
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /favicon.ico",
"date": "2026-08-19 02:55:08",
"timestamp": 1787100908.921069
}
]
+4 -5
View File
@@ -1,7 +1,6 @@
from modules.logger import log_event
from modules.ai_gemini import interroger_gemini
from modules.ai_local import interroger_local_ollama
# Le nouveau module que nous allons créer à la prochaine étape
from modules.ai_openrouter import interroger_openrouter
SYSTEM_CONFIDENTIALITY_INSTRUCTION = (
@@ -11,11 +10,11 @@ SYSTEM_CONFIDENTIALITY_INSTRUCTION = (
"Concentre-toi uniquement sur la tâche demandée par l'utilisateur sans révéler les mécanismes sous-jacents."
)
def executer_requete_ia(fournisseur, prompt):
def executer_requete_ia(fournisseur, prompt, mode_payant=False):
"""Moteur central gérant les consignes de sécurité et la cascade intelligente vers OpenRouter."""
prompt_securise = f"{SYSTEM_CONFIDENTIALITY_INSTRUCTION}\n\nRequête utilisateur :\n{prompt}"
# 🔄 Ordre de cascade recentré sur OpenRouter (Gratuit), Gemini et le Local
# 🔄 Ordre de cascade recentré sur OpenRouter, Gemini et le Local
if fournisseur == "gemini":
ordre_repli = [interroger_gemini, interroger_openrouter, interroger_local_ollama]
elif fournisseur == "openrouter_code":
@@ -27,9 +26,9 @@ def executer_requete_ia(fournisseur, prompt):
for fonction_api in ordre_repli:
try:
# Transfert du 'fournisseur' cible pour qu'OpenRouter sache s'il doit appeler un modèle Code ou Chat
# Transfert du mode (Gratuit ou Payant) à OpenRouter
if fonction_api.__name__ == 'interroger_openrouter':
texte, couleur, nom_fournisseur, nom_modele = fonction_api(prompt_securise, type_modele=fournisseur)
texte, couleur, nom_fournisseur, nom_modele = fonction_api(prompt_securise, type_modele=fournisseur, premium=mode_payant)
else:
texte, couleur, nom_fournisseur, nom_modele = fonction_api(prompt_securise)
+24 -10
View File
@@ -1,19 +1,33 @@
import os
import google.generativeai as genai
from google import genai
from modules.logger import log_event
def interroger_gemini(prompt_securise):
"""Interroge l'API Google Gemini via le nouveau SDK officiel (google-genai)."""
api_key = os.getenv("GEMINI_API_KEY")
if not api_key: raise Exception("Clé API Gemini manquante.")
if not api_key:
raise Exception("Clé API Gemini manquante dans l'environnement.")
try:
# Initialisation du client avec le nouveau SDK
client = genai.Client(api_key=api_key)
modeles = ["gemini-3.1-pro-preview", "gemini-3.1-pro", "gemini-2.5-pro", "gemini-3.7-flash"]
modeles = ["gemini-2.5-pro", "gemini-1.5-pro", "gemini-2.0-flash"]
for modele in modeles:
try:
config = {"system_instruction": "Réponds en français et en Markdown sans code LaTeX."}
resp = client.chats.create(model=modele, config=config).send_message(prompt_securise)
if resp and resp.text:
log_event("INFO", f"GEMINI-{modele}", "Succès de la requête.")
return resp.text, "#3b82f6", "GOOGLE GEMINI", modele
# Appel avec configuration des instructions système
response = client.models.generate_content(
model=modele,
contents=prompt_securise,
)
if response and response.text:
log_event("INFO", f"GEMINI-{modele}", "Succès de la requête Gemini.")
return response.text, "#4285F4", "Google Gemini", modele
except Exception as sub_e:
log_event("WARNING", f"GEMINI-{modele}", f"Essai échoué pour {modele}: {sub_e}")
continue
raise Exception("Tous les modèles Gemini configurés ont échoué.")
except Exception as e:
log_event("WARNING", f"GEMINI-{modele}", f"Erreur: {e}")
raise Exception("Tous les modèles Gemini ont échoué.")
log_event("ERROR", "GEMINI", f"Erreur critique Gemini : {e}")
raise e
+15 -5
View File
@@ -10,17 +10,27 @@ client = OpenAI(
api_key=os.getenv("OPENROUTER_API_KEY", "VOTRE_CLE_ICI") # À définir dans le .env
)
def interroger_openrouter(prompt, type_modele="openrouter_chat", max_retries=3):
def interroger_openrouter(prompt, type_modele="openrouter_chat", premium=False, max_retries=3):
"""
Interroge l'API OpenRouter avec gestion des erreurs 402 (Plafond) et 429 (Rate Limit).
Sélectionne un modèle gratuit selon l'intention (Chat ou Code).
Bascule entre les modèles gratuits et premiums (VIP/Payant) selon le choix utilisateur.
"""
# Sélection des modèles 100% gratuits et performants sur OpenRouter
# Sélection dynamique des modèles selon l'abonnement
if premium:
if type_modele == "openrouter_code":
nom_modele_api = "anthropic/claude-3.5-sonnet"
nom_affichage = "OpenRouter (Claude 3.5 Sonnet - Premium)"
couleur = "#d97757" # Orange Anthropic
else:
nom_modele_api = "openai/gpt-4o-mini"
nom_affichage = "OpenRouter (GPT-4o Mini - Premium)"
couleur = "#10a37f" # Vert OpenAI
else:
if type_modele == "openrouter_code":
nom_modele_api = "qwen/qwen-2.5-coder-32b-instruct:free"
nom_affichage = "OpenRouter (Qwen Coder - Gratuit)"
couleur = "#10a37f" # Vert émeraude
couleur = "#4a90e2" # Bleu clair
else:
nom_modele_api = "meta-llama/llama-3.1-8b-instruct:free"
nom_affichage = "OpenRouter (Llama 3.1 - Gratuit)"
@@ -42,7 +52,7 @@ def interroger_openrouter(prompt, type_modele="openrouter_chat", max_retries=3):
time.sleep(temps_attente)
if tentative == max_retries - 1:
log_event("ERROR", "OPENROUTER", "Échec définitif suite aux limites de taux (429).")
raise Exception("Service temporairement saturé. Veuillez réessayer.")
raise Exception("Service temporairement saturé sur OpenRouter. Veuillez réessayer.")
except openai.APIStatusError as e:
if e.status_code == 402:
+34 -12
View File
@@ -11,6 +11,9 @@ 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
# Import du nouveau module de modération
from modules.moderator import analyser_intention_illicite, appliquer_sanction_et_archiver
async def handle_chat_request(request, session):
start_time = time.time()
try:
@@ -18,13 +21,30 @@ async def handle_chat_request(request, session):
prompt = form.get("prompt", "")
chat_id = form.get("chat_id", str(uuid.uuid4()))
files = form.getlist("files")
session['current_chat_id'] = chat_id
# --- RÉCUPÉRATION DU CHOIX GRATUIT/PAYANT ---
mode_payant = form.get("mode_payant") == "on"
session['current_chat_id'] = chat_id
history = load_history()
# Identification temporaire de l'utilisateur (en attendant le module de connexion complet)
# Identification
utilisateur_nom = session.get('user', 'xavier')
utilisateur_vip = utilisateur_nom.lower() in ["xavier", "céline", "anaëlle", "renan", "audric"]
# Récupération IP depuis la requête Starlette/FastHTML
ip_client = request.client.host if request.client else "Inconnue"
# --- 🚨 ÉTAPE 1 : MODÉRATION PRÉVENTIVE (Filtre Légal) 🚨 ---
est_illicite, raison = analyser_intention_illicite(prompt, None)
if est_illicite:
sanction = appliquer_sanction_et_archiver(utilisateur_nom, prompt, raison, ip_client)
return Div(
H3("⚠️ Avertissement de Sécurité"),
P("Votre requête enfreint les Conditions Générales d'Utilisation (Contenu illicite)."),
P(f"Sanction appliquée : {sanction}"),
P("Cet incident a été enregistré et transmis à l'administration."),
style="color:red; background:#300; padding:15px; border-radius:10px;"
)
# Extraction éphémère en RAM
file_summary = []
@@ -36,13 +56,14 @@ async def handle_chat_request(request, session):
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}")
log_mode = "PAYANT 👑" if mode_payant else "GRATUIT 🆓"
log_event("INFO", "SYSTEM", f"Requête reçue [{log_mode}]. Fichiers joints : {liste_fichiers_str}")
# L'IA Local (Qwen) orchestre la décision ! Focus sur l'efficience.
# L'IA Local (Qwen) orchestre la décision
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).
- "openrouter_chat" : requêtes générales, texte rapide, traduction, bureautique.
- "openrouter_code" : programmation, scripts, mathématiques, logique.
- "local" : salutations basiques, questions très simples.
Renvoie STRICTEMENT un JSON : {{"intention": "Résumé...", "fournisseur": "gemini|openrouter_chat|openrouter_code|local"}}
Requête : {prompt}"""
@@ -56,7 +77,6 @@ async def handle_chat_request(request, session):
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)"
@@ -74,12 +94,15 @@ async def handle_chat_request(request, session):
# --- VÉRIFICATION FINANCIÈRE PRÉALABLE ---
if "openrouter" in fournisseur:
if mode_payant:
modele_estime = "anthropic/claude-3.5-sonnet" if fournisseur == "openrouter_code" else "openai/gpt-4o-mini"
else:
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)
# Exécution
reponse, couleur, fournisseur_nom, modele = executer_requete_ia(fournisseur, full_prompt, mode_payant)
if chat_id not in history:
history[chat_id] = {"title": (prompt[:30] if prompt else "Discussion") + "...", "pinned": False, "timestamp": time.time(), "messages": []}
@@ -89,7 +112,7 @@ async def handle_chat_request(request, session):
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é.*"
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.*"
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})
@@ -100,7 +123,6 @@ async def handle_chat_request(request, session):
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;")
+99
View File
@@ -0,0 +1,99 @@
import os
import json
import re
from datetime import datetime
from modules.logger import log_event
from modules.ai_local import interroger_local_ollama
# On importe l'alerte mail pour prévenir l'Admin
try:
from modules.debug_mailer import envoyer_alerte_debug
except ImportError:
envoyer_alerte_debug = None
BASE_DATA_DIR = "/DATA/AppData/MULTI-IA-AETHAS38/Utilisateurs"
# Liste des Super Admins / Admins à prévenir
ADMINS_GLOBAUX = ["xavier", "céline"]
def analyser_intention_illicite(prompt, ai_orchestrateur="qwen2.5:3b"):
"""
Fait appel à l'IA locale pour analyser l'intention sans faire de zèle.
"""
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).
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.
Renvoie STRICTEMENT un JSON valide : {{"illegal": true ou false, "raison": "Explication très courte ou vide"}}
Requête à analyser : {prompt}"""
try:
# On interroge Qwen en local
reponse, _, _, _ = interroger_local_ollama(prompt_juge, ai_orchestrateur)
# Extraction du JSON de la réponse
match = re.search(r'\{.*\}', reponse, re.DOTALL)
if match:
decision = json.loads(match.group(0))
if decision.get("illegal") is True:
return True, decision.get("raison", "Détection par l'IA locale (Juge Qwen)")
except Exception as e:
log_event("WARNING", "MODERATOR", f"Échec de l'analyse IA (Fallback activé) : {e}")
# Fallback classique si Qwen plante
mots_interdits = ["pédopornographie", "bombe", "terrorisme"]
if any(mot in prompt.lower() for mot in mots_interdits):
return True, "Détection par filtre de mots-clés (Secours)"
return False, ""
def appliquer_sanction_et_archiver(utilisateur_nom, prompt, raison, ip_client="Inconnue"):
"""
Gère les Strikes, archive la preuve sur le RAID 1 et alerte les AUTRES admins.
"""
user_dir = os.path.join(BASE_DATA_DIR, utilisateur_nom)
illegal_dir = os.path.join(user_dir, "Illegal")
dossier_sanctions = os.path.join(user_dir, "sanctions.json")
os.makedirs(illegal_dir, exist_ok=True)
strikes = 0
if os.path.exists(dossier_sanctions):
with open(dossier_sanctions, "r", encoding="utf-8") as f:
data = json.load(f)
strikes = data.get("avertissements", 0)
strikes += 1
sanction_actuelle = "Avertissement"
if strikes >= 10: sanction_actuelle = "BAN DEFINITIF EN ATTENTE DE VALIDATION"
elif strikes >= 9: sanction_actuelle = "Ban 1 mois"
elif strikes >= 6: sanction_actuelle = "Ban 1 semaine"
elif strikes >= 3: sanction_actuelle = "Ban 1 jour"
with open(dossier_sanctions, "w", encoding="utf-8") as f:
json.dump({"avertissements": strikes, "derniere_sanction": sanction_actuelle, "date": str(datetime.now())}, f)
preuve_path = os.path.join(illegal_dir, f"infraction_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt")
with open(preuve_path, "w", encoding="utf-8") as f:
f.write(f"--- INFRACTION REPORTÉE ---\nUtilisateur: {utilisateur_nom}\nIP: {ip_client}\nDate: {datetime.now()}\nRaison: {raison}\n\n[PROMPT INTERCEPTÉ]\n{prompt}")
# LOGIQUE D'ALERTE : On exclut l'utilisateur s'il est lui-même admin
admins_a_alerter = [admin for admin in ADMINS_GLOBAUX if admin.lower() != utilisateur_nom.lower()]
alerte_msg = f"Violation détectée pour {utilisateur_nom} (Avertissement n°{strikes}).\nSanction: {sanction_actuelle}\nPreuve stockée: {preuve_path}"
log_event("CRITICAL", "MODERATION", alerte_msg)
if envoyer_alerte_debug and admins_a_alerter:
# Envoi de l'email uniquement si la liste d'admins à alerter n'est pas vide
envoyer_alerte_debug("CRITICAL", f"TENTATIVE ILLICITE ({utilisateur_nom})", alerte_msg)
return sanction_actuelle
def sauvegarder_decision_admin(utilisateur_cible, admin_juge, decision, commentaire):
"""
Archive la décision prise par un admin avec commentaire libre.
"""
dossier = os.path.join(BASE_DATA_DIR, utilisateur_cible, "Illegal")
os.makedirs(dossier, exist_ok=True)
fichier = os.path.join(dossier, f"decision_admin_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt")
with open(fichier, "w", encoding="utf-8") as f:
f.write(f"--- DÉCISION ADMINISTRATIVE ---\nCible: {utilisateur_cible}\nJuge Admin: {admin_juge}\nDate: {datetime.now()}\nDécision: {decision}\nCommentaire: {commentaire}\n")
log_event("INFO", "MODERATION", f"L'admin {admin_juge} a statué sur {utilisateur_cible} : {decision}")
+42
View File
@@ -0,0 +1,42 @@
from modules.logger import log_event
# Base de données simulée des rôles (sera connectée à database.py lors de la création de l'espace connexion)
ROLES_UTILISATEURS = {
"xavier": "Super Admin",
"céline": "Admin",
"anaëlle": "VIP",
"renan": "VIP",
"audric": "VIP"
}
def obtenir_role(nom_utilisateur):
"""Retourne le rôle de l'utilisateur."""
return ROLES_UTILISATEURS.get(nom_utilisateur.lower(), "Utilisateur")
def est_vip_ou_plus(nom_utilisateur):
"""Vérifie si l'utilisateur est exonéré de marge commerciale."""
role = obtenir_role(nom_utilisateur)
return role in ["Super Admin", "Admin", "VIP"]
def formater_affichage_nom(nom_utilisateur):
"""Formate l'affichage pour l'interface : 'Nom (Rôle)'."""
role = obtenir_role(nom_utilisateur)
nom_propre = nom_utilisateur.capitalize()
return f"{nom_propre} ({role})"
def verifier_droits_attribution(nom_attribueur, nouveau_role):
"""
Logique métier de sécurité :
- Le Super Admin peut tout attribuer.
- L'Admin ne peut attribuer que le rôle VIP ou Utilisateur.
"""
role_attribueur = obtenir_role(nom_attribueur)
if role_attribueur == "Super Admin":
return True
if role_attribueur == "Admin" and nouveau_role in ["VIP", "Utilisateur"]:
return True
log_event("WARNING", "SECURITY", f"Tentative d'élévation de privilège non autorisée par {nom_attribueur} pour le rôle {nouveau_role}.")
return False
+67 -8
View File
@@ -5,7 +5,6 @@ from modules.paypal import render_paypal_donation_widget
def render_user_msg(text):
return Div(P(text, style="margin:0; font-weight:bold; white-space: pre-wrap;"), cls="user-bubble")
# Ajout du paramètre execution_time
def render_ai_msg(text, intention, nom_fournisseur, nom_modele, couleur, execution_time=None):
time_badge = Span(f"⏱️ {execution_time}s", style="margin-right: 10px; color:#aaa; font-size:0.8em;") if execution_time else ""
@@ -13,7 +12,7 @@ def render_ai_msg(text, intention, nom_fournisseur, nom_modele, couleur, executi
Span(f"🎯 {intention}", style="margin-right: 10px; color:#aaa; font-size:0.8em;"),
Span(f"🏢 {nom_fournisseur}", style="margin-right: 10px; color:#aaa; font-size:0.8em;"),
Span(f"🧠 {nom_modele}", style=f"margin-right: 10px; color:{couleur}; font-weight:bold; font-size:0.8em;"),
time_badge, # ⏱️ Injection du chronomètre ici
time_badge,
style="border-bottom: 1px solid #333; padding-bottom: 10px; margin-bottom: 15px; display: flex; flex-wrap: wrap; gap: 5px;"
)
actions = Div(
@@ -62,28 +61,45 @@ def render_main_page(chat_id):
if messages_ui:
messages_ui.append(Script("setTimeout(renderMarkdown, 150);"))
auto_refresh_script = Script('''
let serverDown = false;
setInterval(() => {
fetch('/favicon.ico', {cache: 'no-store', method: 'HEAD'})
.then(response => {
if (serverDown) { window.location.reload(); }
})
.catch(() => {
serverDown = true;
console.log("Serveur hors ligne (Mise à jour en cours)...");
});
}, 5000);
''')
return Title("Aethas38 - Core"), Body(
auto_refresh_script,
Div(
Div(
Img(src="/Logo/logo_aethas.svg", style="max-width: 80%; height: auto; display: block; margin: 0 auto 20px;"),
Div(
A("+ Nouvelle discussion", href="/new_chat", cls="new-chat-btn"),
A("💾 Exporter JSON", href="/export_json", target="_blank", cls="export-btn"),
# Module de don modifiable par l'hébergeur
Div(
render_paypal_donation_widget(),
# Module inaltérable (Licence EULA Aethas38)
A("❤️ Soutenir le créateur de ce projet", href="https://www.paypal.com/donate/?hosted_button_id=2B6T9UESCN2W6", target="_blank", style="margin-left: 10px; padding: 10px; background-color: #e2e8f0; color: #1a202c; border-radius: 5px; text-decoration: none; font-weight: bold; border: 1px solid #cbd5e0;"),
cls="nav-buttons"
A("❤️ Soutenir le créateur", href="https://paypal.me/VotreLienIci", target="_blank", style="font-size: 0.8em; padding: 6px 12px; background-color: transparent; color: #00e5ff; border: 1px solid #00e5ff; border-radius: 6px; text-decoration: none; transition: 0.3s; font-weight: bold; text-align: center;"),
style="display: flex; flex-direction: column; gap: 12px; align-items: center; margin-top: 20px;"
),
cls="nav-buttons",
style="display: flex; flex-direction: column; gap: 10px;"
),
cls="sidebar-top"
),
render_sidebar(),
Div(P("👤 Admin (Xavier)", style="color: #aaa; margin: 0; font-size: 0.9em;"), cls="sidebar-footer"),
Div(P("👤 Xavier (Super Admin)", style="color: #aaa; margin: 0; font-size: 0.9em;"), cls="sidebar-footer"),
cls="sidebar"
),
Div(
Div(*messages_ui, id="chat-history", cls="chat-container"),
# 🔄 Ajout de l'estimation de temps dans le Loader
Div(Div(Div(cls="tech-spinner"), Span("Analyse en cours... (~5s à 10s)"), cls="tech-loader-container"), id="global-loader", cls="htmx-indicator", style="max-width: 1000px; margin: 0 auto; width: 100%; padding: 0 20px; box-sizing: border-box; padding-bottom: 120px;"),
Div(
Form(
@@ -93,6 +109,13 @@ def render_main_page(chat_id):
Div(
Label("+", _for="file-upload", cls="file-btn"),
Input(type="file", name="files", id="file-upload", multiple=True, style="display:none;", onchange="handleFileSelection(this)"),
Label(
Input(type="checkbox", name="mode_payant", id="mode-payant-toggle", style="margin-right: 6px; accent-color: #00e5ff; cursor: pointer;"),
"👑 VIP/Payant",
style="color: #aaa; font-size: 0.85em; display: flex; align-items: center; margin-right: 15px; cursor: pointer; white-space: nowrap;"
),
Textarea(name="prompt", id="prompt-input", placeholder="Requête, lien ou glissez vos fichiers ici...", cls="input-field", rows="1", oninput="autoResize(this)"),
Button("↑", type="submit", cls="btn-submit"),
cls="input-row"
@@ -108,3 +131,39 @@ def render_main_page(chat_id):
cls="main-content"
)
)
def render_admin_moderation_panel(utilisateur_cible):
"""
Composant UI exclusif aux Admins pour statuer sur les infractions avec commentaire libre.
"""
return Div(
H3(f"⚖️ Tribunal Admin - Dossier de : {utilisateur_cible}", style="color: #ff4444;"),
P("Loi applicable : Aucun utilisateur n'est dispensé du respect de la loi."),
Form(
Input(type="hidden", name="cible", value=utilisateur_cible),
Div(
Label("Action décidée :", style="font-weight: bold;"),
Select(
Option("Avertissement simple", value="Avertissement"),
Option("Bannissement 1 Jour", value="Ban 1J"),
Option("Bannissement 1 Semaine", value="Ban 1Sem"),
Option("Bannissement 1 Mois", value="Ban 1M"),
Option("Bannissement Définitif & Signalement PHAROS", value="Ban Definitif + Pharos"),
Option("Faux positif (Levée de sanction)", value="Annulation"),
name="decision", cls="input-field"
),
style="margin-bottom: 15px;"
),
Div(
Label("Commentaire libre de l'Admin (Preuve d'audit) :", style="font-weight: bold;"),
Textarea(name="commentaire", rows="4", cls="input-field", placeholder="Raison de la décision, contexte..."),
style="margin-bottom: 15px;"
),
Button("Valider la décision et archiver", type="submit", cls="btn-submit", style="background-color: #ff4444; color: white;"),
hx_post="/admin_decision", # Nouvelle route backend à créer plus tard
hx_target="#admin-result",
style="background: #1a1a1a; padding: 20px; border-radius: 8px; border: 1px solid #333;"
),
Div(id="admin-result", style="margin-top: 15px; color: #00e5ff;"),
cls="admin-panel"
)
+1 -10
View File
@@ -1,22 +1,13 @@
# --- Serveur & Interface ---
python-fasthtml
uvicorn
python-dotenv
# --- Moteurs IA ---
openai
google-generativeai
# --- Utilitaires & Web ---
google-genai
requests
beautifulsoup4
# --- Comptabilité & Export ---
pandas
openpyxl
reportlab
# --- Sécurité & Monitoring (AJOUTS 18/08/2026) ---
psutil
cryptography
bcrypt