Public Access
Ajout du module multimédia (Text-to-Image via HuggingFace et Cloudflare) et sécurisation du routeur Multi-IA
Build and Push Docker Image / build-and-push (push) Successful in 38s
Build and Push Docker Image / build-and-push (push) Successful in 38s
This commit is contained in:
1 parent
c82e12f49a
commit
519a42107f
8 files changed
+277
-75
No files matched your search
@@ -22,12 +22,13 @@ Pour fonctionner de manière optimale, l'Orchestrateur Local nécessite au minim
|
|||||||
- [x] Continuité de service : IA de secours automatique et Timeout à 300s.
|
- [x] Continuité de service : IA de secours automatique et Timeout à 300s.
|
||||||
|
|
||||||
### Phase 3 : Refonte UI & Expérience Utilisateur ✅
|
### Phase 3 : Refonte UI & Expérience Utilisateur ✅
|
||||||
- [x] Thème "Minecraft-Tech" (Inspiré serveurs/redstone) compatible Mobile (Responsive)
|
- [x] Thème "Minecraft-Tech" (Inspiré serveurs/redstone) compatible Mobile (Responsive / Contraste OLED élevé)
|
||||||
- [x] Historique (Sidebar) avec Renommage, Épinglage, Suppression et Navigation isolée par session
|
- [x] Historique (Sidebar) avec Renommage, Épinglage, Suppression et Navigation isolée par session
|
||||||
- [x] Zone de saisie intelligente (Drag & Drop, Collage multi-fichiers)
|
- [x] Zone de saisie intelligente (Drag & Drop, Collage multi-fichiers)
|
||||||
- [x] Export modulaire et propre (PDF, DOC, TXT, JSON, ZIP)
|
- [x] Export modulaire et propre (PDF, DOC, TXT, JSON, ZIP)
|
||||||
- [x] Boutons de partage direct (WhatsApp, Email) et Déconnexion.
|
- [x] Boutons de partage direct (WhatsApp, Email) et Déconnexion.
|
||||||
- [x] Indicateur d'avancement dynamique (Loader Néon) pendant le traitement des IA.
|
- [x] Indicateur d'avancement dynamique (Loader Néon) pendant le traitement des IA.
|
||||||
|
- [x] Transparence du Routage : Affichage visuel (Badges) du parcours multi-IA (Succès, Échecs, Erreurs 404/503) pour chaque requête.
|
||||||
|
|
||||||
### 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.).
|
||||||
@@ -49,6 +50,7 @@ Pour fonctionner de manière optimale, l'Orchestrateur Local nécessite au minim
|
|||||||
|
|
||||||
### Phase 6 : Nouvelles Capacités IA (Spécialisations & Modèles) 🤖
|
### Phase 6 : Nouvelles Capacités IA (Spécialisations & Modèles) 🤖
|
||||||
*L'infrastructure bascule sur un catalogue dynamique mis à jour toutes les 2h pour pallier les décommissionnements.*
|
*L'infrastructure bascule sur un catalogue dynamique mis à jour toutes les 2h pour pallier les décommissionnements.*
|
||||||
|
*Filtrage actif des modèles OpenRouter restreints (ex: Politiques de confidentialité strictes ignorées).*
|
||||||
|
|
||||||
| 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 |
|
||||||
| :--- | :--- | :--- | :--- |
|
| :--- | :--- | :--- | :--- |
|
||||||
@@ -61,6 +63,8 @@ Pour fonctionner de manière optimale, l'Orchestrateur Local nécessite au minim
|
|||||||
- [ ] File d'attente asynchrone (Queue List) et Timer en temps réel pour gérer la charge serveur.
|
- [ ] File d'attente asynchrone (Queue List) et Timer en temps réel pour gérer la charge serveur.
|
||||||
- [ ] Sauvegarde automatique : Compression du site toutes les 6 heures vers un GDrive.
|
- [ ] Sauvegarde automatique : Compression du site toutes les 6 heures vers un GDrive.
|
||||||
- [x] Hébergement Gitea Docker. Injection des clés via `env_file`. Volumes persistants configurés.
|
- [x] Hébergement Gitea Docker. Injection des clés via `env_file`. Volumes persistants configurés.
|
||||||
|
- [x] Persistance absolue et sécurité : Fichiers d'historique, logs, facturation et dossiers RAG isolés sur volume RAID mdadm externe.
|
||||||
|
- [x] Synchronisation temporelle : Conteneurs Docker synchronisés sur l'horloge système du serveur Debian hôte (NTP via montage en lecture seule).
|
||||||
|
|
||||||
### Phase 9 : Modération Automatisée & Conformité Pénale 🚨
|
### Phase 9 : Modération Automatisée & Conformité Pénale 🚨
|
||||||
- [x] Système hiérarchique : `Super Admin`, `Admin`, `VIP` (Famille) et `Standard`. Quotas de ressources (60% CPU/RAM) applicables selon le rang.
|
- [x] Système hiérarchique : `Super Admin`, `Admin`, `VIP` (Famille) et `Standard`. Quotas de ressources (60% CPU/RAM) applicables selon le rang.
|
||||||
@@ -69,6 +73,6 @@ Pour fonctionner de manière optimale, l'Orchestrateur Local nécessite au minim
|
|||||||
|
|
||||||
### Phase 10 : Base de Connaissances Intelligente (RAG) 📚
|
### Phase 10 : Base de Connaissances Intelligente (RAG) 📚
|
||||||
- [x] Initialisation de l'arborescence dynamique (`RAG/`) sur le stockage RAID.
|
- [x] Initialisation de l'arborescence dynamique (`RAG/`) sur le stockage RAID.
|
||||||
- [x] Injection asynchrone des documents Markdown et Texte directement dans la fenêtre de contexte des LLMs.
|
- [x] Injection dynamique 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.
|
- [x] Auto-Apprentissage Infaillible : Le système génère automatiquement des fichiers `.md` de son propre savoir après validation. Inclusion d'un dossier "Connaissances_Generales" pour capter 100% des requêtes.
|
||||||
- [ ] Interface UI : Ajout d'une vue Administrateur pour téléverser de nouveaux documents RAG.
|
- [ ] Interface UI : Ajout d'une vue Administrateur pour téléverser de nouveaux documents RAG.
|
||||||
@@ -11860,5 +11860,65 @@
|
|||||||
"action": "Acc\u00e8s \u00e0 /",
|
"action": "Acc\u00e8s \u00e0 /",
|
||||||
"date": "2026-08-23 15:08:53",
|
"date": "2026-08-23 15:08:53",
|
||||||
"timestamp": 1787490533.299349
|
"timestamp": 1787490533.299349
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"action": "Acc\u00e8s \u00e0 /",
|
||||||
|
"date": "2026-08-23 19:50:11",
|
||||||
|
"timestamp": 1787507411.7223423
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"action": "Acc\u00e8s \u00e0 /",
|
||||||
|
"date": "2026-08-23 20:27:43",
|
||||||
|
"timestamp": 1787509663.9815109
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"action": "Acc\u00e8s \u00e0 /",
|
||||||
|
"date": "2026-08-24 18:25:30",
|
||||||
|
"timestamp": 1787588730.6111038
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"action": "Acc\u00e8s \u00e0 /",
|
||||||
|
"date": "2026-08-24 18:54:02",
|
||||||
|
"timestamp": 1787590442.176149
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"action": "Acc\u00e8s \u00e0 /",
|
||||||
|
"date": "2026-08-24 18:54:18",
|
||||||
|
"timestamp": 1787590458.311077
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"action": "Acc\u00e8s \u00e0 /",
|
||||||
|
"date": "2026-08-24 19:08:18",
|
||||||
|
"timestamp": 1787591298.5431492
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"action": "Acc\u00e8s \u00e0 /chat",
|
||||||
|
"date": "2026-08-24 19:14:32",
|
||||||
|
"timestamp": 1787591672.8711567
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"action": "Acc\u00e8s \u00e0 /Media/img_cf_1787591691.png",
|
||||||
|
"date": "2026-08-24 19:14:51",
|
||||||
|
"timestamp": 1787591691.4614098
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"action": "Acc\u00e8s \u00e0 /",
|
||||||
|
"date": "2026-08-24 19:21:20",
|
||||||
|
"timestamp": 1787592080.4595678
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"action": "Acc\u00e8s \u00e0 /Media/img_cf_1787591691.png",
|
||||||
|
"date": "2026-08-24 19:21:22",
|
||||||
|
"timestamp": 1787592082.4804988
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -44,7 +44,10 @@ app, rt = fast_app(
|
|||||||
middleware=[Middleware(SecurityMiddleware)]
|
middleware=[Middleware(SecurityMiddleware)]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
dossier_media = "/DATA/AppData/MULTI-IA-AETHAS38/RAG/Media"
|
||||||
|
os.makedirs(dossier_media, exist_ok=True)
|
||||||
app.mount("/Logo", StaticFiles(directory="Logo"), name="Logo")
|
app.mount("/Logo", StaticFiles(directory="Logo"), name="Logo")
|
||||||
|
app.mount("/Media", StaticFiles(directory="/DATA/AppData/MULTI-IA-AETHAS38/RAG/Media"), name="Media")
|
||||||
|
|
||||||
# --- ROUTES D'AUTHENTIFICATION ---
|
# --- ROUTES D'AUTHENTIFICATION ---
|
||||||
@rt('/login')
|
@rt('/login')
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
import concurrent.futures
|
||||||
|
from modules.logger import log_event
|
||||||
|
|
||||||
|
def generer_image_cloudflare(prompt: str) -> bytes:
|
||||||
|
"""Appel à l'API Cloudflare Workers AI"""
|
||||||
|
account_id = os.getenv("CLOUDFLARE_ACCOUNT_ID")
|
||||||
|
api_token = os.getenv("CLOUDFLARE_API_TOKEN")
|
||||||
|
if not account_id or not api_token: return None
|
||||||
|
|
||||||
|
url = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/run/@cf/stabilityai/stable-diffusion-xl-base-1.0"
|
||||||
|
headers = {"Authorization": f"Bearer {api_token}"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.post(url, headers=headers, json={"prompt": prompt}, timeout=120)
|
||||||
|
if resp.status_code == 200: return resp.content
|
||||||
|
except Exception as e:
|
||||||
|
log_event("WARNING", "MEDIA", f"Échec Cloudflare : {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def generer_image_huggingface(prompt: str) -> bytes:
|
||||||
|
"""Appel à l'API HuggingFace"""
|
||||||
|
api_key = os.getenv("HUGGINGFACE_API_KEY")
|
||||||
|
if not api_key: return None
|
||||||
|
|
||||||
|
url = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
|
||||||
|
headers = {"Authorization": f"Bearer {api_key}"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.post(url, headers=headers, json={"inputs": prompt}, timeout=120)
|
||||||
|
if resp.status_code == 200: return resp.content
|
||||||
|
except Exception as e:
|
||||||
|
log_event("WARNING", "MEDIA", f"Échec HF : {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def generer_les_deux_images(prompt: str, fichiers: list) -> tuple:
|
||||||
|
trace = []
|
||||||
|
if fichiers:
|
||||||
|
trace.append("🟢 Mode Image-to-Image (Désactivé en multi-fournisseur)")
|
||||||
|
return f"*(BETA)* Le mode Image-to-Image nécessite une API spécifique. \n\nFichiers : {', '.join(fichiers)}", "#ff9800", "Système", "En dev", trace
|
||||||
|
|
||||||
|
trace.append("🟢 Lancement Text-to-Image (HuggingFace + Cloudflare)")
|
||||||
|
|
||||||
|
# Exécution des deux API en parallèle pour gagner du temps
|
||||||
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||||
|
future_hf = executor.submit(generer_image_huggingface, prompt)
|
||||||
|
future_cf = executor.submit(generer_image_cloudflare, prompt)
|
||||||
|
img_hf = future_hf.result()
|
||||||
|
img_cf = future_cf.result()
|
||||||
|
|
||||||
|
dossier_media = "/DATA/AppData/MULTI-IA-AETHAS38/RAG/Media"
|
||||||
|
os.makedirs(dossier_media, exist_ok=True)
|
||||||
|
|
||||||
|
reponse_md = f"**Prompt analysé :** {prompt}\n\n"
|
||||||
|
fournisseurs_actifs = []
|
||||||
|
|
||||||
|
# Traitement Cloudflare
|
||||||
|
if img_cf:
|
||||||
|
nom_cf = f"img_cf_{int(time.time())}.png"
|
||||||
|
with open(os.path.join(dossier_media, nom_cf), 'wb') as f: f.write(img_cf)
|
||||||
|
reponse_md += f"### ☁️ Cloudflare Workers AI (SDXL)\n\n\n---\n\n"
|
||||||
|
fournisseurs_actifs.append("Cloudflare")
|
||||||
|
trace.append("🟢 Cloudflare : Succès")
|
||||||
|
else:
|
||||||
|
trace.append("🔴 Cloudflare : Échec")
|
||||||
|
|
||||||
|
# Traitement HuggingFace
|
||||||
|
if img_hf:
|
||||||
|
nom_hf = f"img_hf_{int(time.time())}.png"
|
||||||
|
with open(os.path.join(dossier_media, nom_hf), 'wb') as f: f.write(img_hf)
|
||||||
|
reponse_md += f"### 🤗 HuggingFace (SDXL)\n\n\n"
|
||||||
|
fournisseurs_actifs.append("HuggingFace")
|
||||||
|
trace.append("🟢 HF : Succès")
|
||||||
|
else:
|
||||||
|
trace.append("🔴 HF : Échec")
|
||||||
|
|
||||||
|
# Si les deux ont échoué
|
||||||
|
if not img_hf and not img_cf:
|
||||||
|
return "*Échec critique : HuggingFace et Cloudflare ont échoué (ou clés manquantes).* \nVérifiez les logs du serveur.", "#ff4444", "Système", "Erreur", trace
|
||||||
|
|
||||||
|
return reponse_md, "#00e5ff", " + ".join(fournisseurs_actifs), "Stable Diffusion XL", trace
|
||||||
|
|
||||||
|
def router_media(prompt: str, type_media: str, premium: bool, fichiers: list) -> tuple:
|
||||||
|
trace = [f"🟢 Routage Média : {type_media.upper()}"]
|
||||||
|
if type_media == "image":
|
||||||
|
return generer_les_deux_images(prompt, fichiers)
|
||||||
|
elif type_media == "video":
|
||||||
|
return "*La génération vidéo nécessite une API payante (Luma, Kling).* \n\nPrompt : " + prompt, "#ff4444", "Système", "En attente API", trace
|
||||||
|
return "Type de média non reconnu.", "#333", "Erreur", "Aucun", trace
|
||||||
@@ -83,11 +83,23 @@ def interroger_openrouter_multi(prompt: str, type_modele: str = "openrouter_code
|
|||||||
verifier_cle_api()
|
verifier_cle_api()
|
||||||
extra_body_data = {"session_id": chat_id} if chat_id else {}
|
extra_body_data = {"session_id": chat_id} if chat_id else {}
|
||||||
|
|
||||||
modeles = [obtenir_modele_dynamique(premium, prompt)]
|
# --- LA LISTE BLANCHE DES 5 MEILLEURS MODÈLES ---
|
||||||
if premium:
|
if premium:
|
||||||
modeles.append("openai/gpt-4o-mini")
|
modeles = [
|
||||||
|
"anthropic/claude-3.5-sonnet", # Le meilleur en code/logique
|
||||||
|
"openai/gpt-4o", # Ultra polyvalent
|
||||||
|
"google/gemini-1.5-pro", # Excellent contexte
|
||||||
|
"mistralai/mistral-large-2407", # Top pour le français
|
||||||
|
"meta-llama/llama-3.1-405b-instruct" # Le titan OpenSource
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
modeles.append("nvidia/nemotron-3-nano-30b-a3b:free")
|
modeles = [
|
||||||
|
"google/gemma-2-9b-it:free",
|
||||||
|
"meta-llama/llama-3.1-8b-instruct:free",
|
||||||
|
"microsoft/phi-3-mini-128k-instruct:free",
|
||||||
|
"mistralai/mistral-nemo:free",
|
||||||
|
"qwen/qwen-2-7b-instruct:free"
|
||||||
|
]
|
||||||
|
|
||||||
reponses = []
|
reponses = []
|
||||||
for modele in modeles:
|
for modele in modeles:
|
||||||
|
|||||||
+61
-52
@@ -16,6 +16,7 @@ from modules.logger import log_event
|
|||||||
from modules.billing_manager import verifier_et_valider_budget, obtenir_solde
|
from modules.billing_manager import verifier_et_valider_budget, obtenir_solde
|
||||||
from modules.rag_manager import rechercher_contexte_rag, auto_apprendre_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
|
||||||
|
from modules.ai_media import router_media # <--- IMPORT DU NOUVEAU MODULE
|
||||||
|
|
||||||
def check_and_update_resource_quota(utilisateur_nom, role_utilisateur):
|
def check_and_update_resource_quota(utilisateur_nom, role_utilisateur):
|
||||||
quota_file = "/DATA/AppData/MULTI-IA-AETHAS38/Utilisateurs/quotas_ressources.json"
|
quota_file = "/DATA/AppData/MULTI-IA-AETHAS38/Utilisateurs/quotas_ressources.json"
|
||||||
@@ -45,7 +46,9 @@ 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"
|
||||||
mode_education = form.get("mode_education") == "on" # INTERCEPTION DU MODE EDU
|
mode_education = form.get("mode_education") == "on"
|
||||||
|
media_mode = form.get("media_mode", "texte")
|
||||||
|
|
||||||
validation_acquise = form.get("validation_acquise") == "true"
|
validation_acquise = form.get("validation_acquise") == "true"
|
||||||
choix_utilisateur = form.get("choix_utilisateur", "aucun")
|
choix_utilisateur = form.get("choix_utilisateur", "aucun")
|
||||||
|
|
||||||
@@ -81,69 +84,75 @@ async def handle_chat_request(request, session):
|
|||||||
file_summary.append(f"\n--- Fichier : {f.filename} ---\n{content.decode('utf-8', errors='ignore')}")
|
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"
|
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})")
|
log_event("INFO", "SYSTEM", f"Requête reçue (Mode: {media_mode.upper()} / Payant: {mode_payant})")
|
||||||
|
|
||||||
sec_prompt = f"""Tu es l'Orchestrateur système. Analyse la requête et choisis le meilleur fournisseur IA..."""
|
# --- AIGUILLAGE MULTIMÉDIA ---
|
||||||
try:
|
if media_mode in ["image", "video"]:
|
||||||
texte_routeur, _, _, _ = interroger_local_ollama(sec_prompt)
|
reponse, couleur, fournisseur_nom, modele, trace_route = router_media(prompt, type_media=media_mode, premium=mode_payant, fichiers=file_names)
|
||||||
decision = nettoyer_decision_router(texte_routeur)
|
intention = f"Génération {media_mode.capitalize()}"
|
||||||
except:
|
thematique_rag = "aucun"
|
||||||
decision = {"intention": "Analyse", "fournisseur": "openrouter_chat", "theme_rag": "aucun"}
|
|
||||||
|
|
||||||
intention = decision.get("intention", "Analyse")
|
# --- AIGUILLAGE TEXTE (CLASSIQUE) ---
|
||||||
fournisseur = decision.get("fournisseur", "openrouter_chat").lower()
|
else:
|
||||||
thematique_rag = decision.get("theme_rag", "aucun").lower()
|
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"}
|
||||||
|
|
||||||
if mode_payant or mode_multi_ia:
|
intention = decision.get("intention", "Analyse")
|
||||||
fournisseur = "openrouter_code"
|
fournisseur = decision.get("fournisseur", "openrouter_chat").lower()
|
||||||
intention += " (API Premium/Multi)"
|
thematique_rag = decision.get("theme_rag", "aucun").lower()
|
||||||
|
|
||||||
if file_names:
|
if mode_payant or mode_multi_ia:
|
||||||
fournisseur = "gemini"
|
fournisseur = "openrouter_code"
|
||||||
|
intention += " (API Premium/Multi)"
|
||||||
|
|
||||||
# --- LOGIQUE EDUCATION NATIONALE ---
|
if file_names:
|
||||||
if mode_education:
|
fournisseur = "gemini"
|
||||||
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 mode_education:
|
||||||
if thematique_rag != "aucun":
|
fournisseur = "openrouter_chat"
|
||||||
contexte_rag = rechercher_contexte_rag(prompt, categorie=thematique_rag)
|
thematique_rag = "Education_Nationale"
|
||||||
|
intention = "Éducation Nationale " + ("(Payant)" if mode_payant else "(IA Gratuite)")
|
||||||
|
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
|
||||||
|
|
||||||
urls = re.findall(r"https?://[^\s]+", prompt)
|
contexte_rag = ""
|
||||||
if any(m in prompt.lower() for m in ["cherche", "recherche", "sur le web"]):
|
if thematique_rag != "aucun":
|
||||||
urls.extend(rechercher_sur_google(prompt))
|
contexte_rag = rechercher_contexte_rag(prompt, categorie=thematique_rag)
|
||||||
web_text = extraire_contenu_urls(list(set(urls))) if urls else ""
|
|
||||||
|
|
||||||
full_prompt = prompt + ("\n" + "\n".join(file_summary) if file_summary else "")
|
urls = re.findall(r"https?://[^\s]+", prompt)
|
||||||
if contexte_rag: full_prompt += f"\n\n[Contexte RAG :\n{contexte_rag}]"
|
if any(m in prompt.lower() for m in ["cherche", "recherche", "sur le web"]):
|
||||||
if web_text: full_prompt += f"\n\n[Contexte web :\n{web_text}]"
|
urls.extend(rechercher_sur_google(prompt))
|
||||||
|
web_text = extraire_contenu_urls(list(set(urls))) if urls else ""
|
||||||
|
|
||||||
if "openrouter" in fournisseur:
|
full_prompt = prompt + ("\n" + "\n".join(file_summary) if file_summary else "")
|
||||||
modele_estime = "anthropic/claude-3.5-sonnet" if mode_payant else "cohere/north-mini-code:free"
|
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 mode_payant and not validation_acquise:
|
if "openrouter" in fournisseur:
|
||||||
from modules.billing_manager import estimer_cout_facture
|
modele_estime = "anthropic/claude-3.5-sonnet" if mode_payant else "cohere/north-mini-code:free"
|
||||||
_, 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"]:
|
if mode_payant and not validation_acquise:
|
||||||
from modules.billing_manager import estimer_cout_facture
|
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)
|
_, 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):
|
if prix_estime > 0:
|
||||||
return Div(H3("❌ Solde Insuffisant", style="color:red;"), P("Rechargez votre compte."), style="background:#300; padding:15px;")
|
return render_popup_paiement(prix_estime, prompt, chat_id, mode_multi_ia, mode_education)
|
||||||
|
|
||||||
reponse, couleur, fournisseur_nom, modele, trace_route = executer_requete_ia(
|
if mode_payant and role_utilisateur.lower() not in ["super admin", "admin", "vip", "famille"]:
|
||||||
fournisseur, full_prompt, mode_payant=mode_payant, multi_ia=mode_multi_ia, chat_id=chat_id
|
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;")
|
||||||
|
|
||||||
if "openrouter" in fournisseur_nom.lower() and mode_payant:
|
reponse, couleur, fournisseur_nom, modele, trace_route = executer_requete_ia(
|
||||||
verifier_et_valider_budget(full_prompt, modele, utilisateur_nom=utilisateur_nom, rang_utilisateur=role_utilisateur, multi_ia=mode_multi_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:
|
if thematique_rag != "aucun" and reponse:
|
||||||
auto_apprendre_rag(prompt, reponse, thematique_rag)
|
auto_apprendre_rag(prompt, reponse, thematique_rag)
|
||||||
@@ -164,7 +173,7 @@ async def handle_chat_request(request, session):
|
|||||||
"modele": modele,
|
"modele": modele,
|
||||||
"couleur": couleur,
|
"couleur": couleur,
|
||||||
"execution_time": execution_time,
|
"execution_time": execution_time,
|
||||||
"trace": trace_route # <--- Ajout de la traçabilité
|
"trace": trace_route
|
||||||
})
|
})
|
||||||
save_history(history)
|
save_history(history)
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ def _do_update():
|
|||||||
if resp_local.status_code == 200:
|
if resp_local.status_code == 200:
|
||||||
log_event("INFO", "UPDATER", "Modèle local Abliterated vérifié/mis à jour.")
|
log_event("INFO", "UPDATER", "Modèle local Abliterated vérifié/mis à jour.")
|
||||||
|
|
||||||
# MAJ du catalogue OpenRouter (Gratuits ET Payants)
|
# MAJ DU CATALOGUE COMPLET OPENROUTER
|
||||||
resp_or = requests.get("https://openrouter.ai/api/v1/models", timeout=30)
|
resp_or = requests.get("https://openrouter.ai/api/v1/models", timeout=30)
|
||||||
if resp_or.status_code == 200:
|
if resp_or.status_code == 200:
|
||||||
data = resp_or.json().get("data", [])
|
data = resp_or.json().get("data", [])
|
||||||
@@ -28,23 +28,23 @@ def _do_update():
|
|||||||
|
|
||||||
for m in data:
|
for m in data:
|
||||||
pricing = m.get("pricing", {})
|
pricing = m.get("pricing", {})
|
||||||
# Gratuits purs
|
|
||||||
|
# S'il est 100% gratuit
|
||||||
if pricing.get("prompt") == "0" and pricing.get("completion") == "0":
|
if pricing.get("prompt") == "0" and pricing.get("completion") == "0":
|
||||||
if "nvidia/nemotron" not in m["id"].lower():
|
if "nvidia/nemotron" not in m["id"].lower(): # On garde juste cette exclusion sécurité
|
||||||
modeles_gratuits.append(m["id"])
|
modeles_gratuits.append(m["id"])
|
||||||
# Ciblage des payants ultra-performants (Claude & OpenAI)
|
else:
|
||||||
elif "claude-3.5-sonnet" in m["id"].lower() or "gpt-4o" in m["id"].lower():
|
# On intègre TOUS les modèles payants (Texte, Vision, 3D, etc.)
|
||||||
modeles_payants.append(m["id"])
|
modeles_payants.append(m["id"])
|
||||||
|
|
||||||
with open(CATALOGUE_FILE, "w", encoding="utf-8") as f:
|
with open(CATALOGUE_FILE, "w", encoding="utf-8") as f:
|
||||||
json.dump({"free_models": modeles_gratuits, "paid_models": modeles_payants}, 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).")
|
log_event("INFO", "UPDATER", f"Catalogue Global mis à jour : {len(modeles_gratuits)} gratuits, {len(modeles_payants)} payants détectés.")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log_event("ERROR", "UPDATER", f"Erreur lors de la MAJ auto : {e}")
|
log_event("ERROR", "UPDATER", f"Erreur lors de la MAJ auto : {e}")
|
||||||
|
|
||||||
def trigger_update_now():
|
def trigger_update_now():
|
||||||
"""Déclenchement forcé du scan en cas d'erreur de modèle."""
|
|
||||||
threading.Thread(target=_do_update, daemon=True).start()
|
threading.Thread(target=_do_update, daemon=True).start()
|
||||||
|
|
||||||
def update_models_background():
|
def update_models_background():
|
||||||
|
|||||||
+33
-10
@@ -20,7 +20,6 @@ def render_user_msg(text):
|
|||||||
def render_ai_msg(text, intention, nom_fournisseur, nom_modele, couleur, execution_time=None, trace=None):
|
def render_ai_msg(text, intention, nom_fournisseur, nom_modele, couleur, execution_time=None, trace=None):
|
||||||
time_badge = Span(f"⏱️ {execution_time}s", style="margin-right: 10px; color:#aaa; font-size:0.8em;") if execution_time else ""
|
time_badge = Span(f"⏱️ {execution_time}s", style="margin-right: 10px; color:#aaa; font-size:0.8em;") if execution_time else ""
|
||||||
|
|
||||||
# Construction de la trace visuelle si elle existe
|
|
||||||
trace_html = ""
|
trace_html = ""
|
||||||
if trace and isinstance(trace, list):
|
if trace and isinstance(trace, list):
|
||||||
badges = [Span(t, style="font-size:0.75em; background:#222; padding:2px 6px; border-radius:4px; border:1px solid #444;") for t in trace]
|
badges = [Span(t, style="font-size:0.75em; background:#222; padding:2px 6px; border-radius:4px; border:1px solid #444;") for t in trace]
|
||||||
@@ -43,8 +42,11 @@ def render_ai_msg(text, intention, nom_fournisseur, nom_modele, couleur, executi
|
|||||||
cls="msg-actions"
|
cls="msg-actions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Si le texte contient une image générée (détection basique Markdown ou HTML)
|
||||||
|
# L'affichage natif s'occupera du rendu via marked.js
|
||||||
|
|
||||||
return Div(
|
return Div(
|
||||||
trace_html, # Affichage du chemin de routage
|
trace_html,
|
||||||
encart_info,
|
encart_info,
|
||||||
Div(text, cls="markdown-raw", style="display:none;"),
|
Div(text, cls="markdown-raw", style="display:none;"),
|
||||||
Div(cls="markdown-rendered", style="line-height: 1.6;"),
|
Div(cls="markdown-rendered", style="line-height: 1.6;"),
|
||||||
@@ -77,7 +79,7 @@ def render_main_page(chat_id, utilisateur_nom="xavier", role="Super Admin"):
|
|||||||
msg.get('modele', ''),
|
msg.get('modele', ''),
|
||||||
msg.get('couleur', '#333'),
|
msg.get('couleur', '#333'),
|
||||||
msg.get('execution_time', ''),
|
msg.get('execution_time', ''),
|
||||||
msg.get('trace', []) # <--- C'est ici qu'on transmet la trace aux anciens messages !
|
msg.get('trace', [])
|
||||||
))
|
))
|
||||||
if messages_ui: messages_ui.append(Script("setTimeout(renderMarkdown, 150);"))
|
if messages_ui: messages_ui.append(Script("setTimeout(renderMarkdown, 150);"))
|
||||||
|
|
||||||
@@ -88,6 +90,23 @@ def render_main_page(chat_id, utilisateur_nom="xavier", role="Super Admin"):
|
|||||||
.then(response => { if (serverDown && response.ok) { window.location.reload(); } })
|
.then(response => { if (serverDown && response.ok) { window.location.reload(); } })
|
||||||
.catch(() => { serverDown = true; console.log("Serveur hors ligne..."); });
|
.catch(() => { serverDown = true; console.log("Serveur hors ligne..."); });
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
|
// Script pour gérer l'avertissement de gratuité sur les médias
|
||||||
|
function checkMediaPaymentWarning() {
|
||||||
|
const mediaMode = document.querySelector('input[name="media_mode"]:checked').value;
|
||||||
|
const isPayant = document.getElementById('mode-payant-toggle').checked;
|
||||||
|
const warningDiv = document.getElementById('media-warning');
|
||||||
|
|
||||||
|
if (mediaMode === 'video' && !isPayant) {
|
||||||
|
warningDiv.style.display = 'block';
|
||||||
|
warningDiv.innerHTML = '⚠️ La génération vidéo gratuite est très limitée ou inexistante. Cochez "Payant" pour de meilleurs résultats.';
|
||||||
|
} else if (mediaMode === 'image' && !isPayant) {
|
||||||
|
warningDiv.style.display = 'block';
|
||||||
|
warningDiv.innerHTML = 'ℹ️ Mode image gratuit : Qualité standard (HuggingFace). Pour la HD, cochez "Payant".';
|
||||||
|
} else {
|
||||||
|
warningDiv.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
''')
|
''')
|
||||||
|
|
||||||
solde_perso = obtenir_solde(utilisateur_nom)
|
solde_perso = obtenir_solde(utilisateur_nom)
|
||||||
@@ -131,6 +150,15 @@ def render_main_page(chat_id, utilisateur_nom="xavier", role="Super Admin"):
|
|||||||
Form(
|
Form(
|
||||||
Input(type="hidden", name="chat_id", value=chat_id),
|
Input(type="hidden", name="chat_id", value=chat_id),
|
||||||
Div(
|
Div(
|
||||||
|
# --- NOUVEAU SÉLECTEUR DE MODE MÉDIA ---
|
||||||
|
Div(
|
||||||
|
Label(Input(type="radio", name="media_mode", value="texte", checked=True, onchange="checkMediaPaymentWarning()"), "📝 Texte", cls="media-btn"),
|
||||||
|
Label(Input(type="radio", name="media_mode", value="image", onchange="checkMediaPaymentWarning()"), "🎨 Image", cls="media-btn"),
|
||||||
|
Label(Input(type="radio", name="media_mode", value="video", onchange="checkMediaPaymentWarning()"), "🎬 Vidéo", cls="media-btn"),
|
||||||
|
style="display: flex; gap: 15px; justify-content: center; margin-bottom: 10px;"
|
||||||
|
),
|
||||||
|
Div(id="media-warning", style="display:none; color: #ff9800; font-size: 0.8em; text-align: center; margin-bottom: 10px;"),
|
||||||
|
|
||||||
Div(id="file-preview-list"),
|
Div(id="file-preview-list"),
|
||||||
Div(
|
Div(
|
||||||
Label("+", _for="file-upload", cls="file-btn"),
|
Label("+", _for="file-upload", cls="file-btn"),
|
||||||
@@ -140,11 +168,11 @@ def render_main_page(chat_id, utilisateur_nom="xavier", role="Super Admin"):
|
|||||||
),
|
),
|
||||||
Div(
|
Div(
|
||||||
Label(Input(type="checkbox", name="mode_education", id="mode-education-toggle", style="margin-right: 6px; accent-color: #8bc34a; cursor: pointer;"), "🎓 Éducation", style="color: #aaa; font-size: 0.85em; display: flex; align-items: center; cursor: pointer; white-space: nowrap;"),
|
Label(Input(type="checkbox", name="mode_education", id="mode-education-toggle", style="margin-right: 6px; accent-color: #8bc34a; cursor: pointer;"), "🎓 Éducation", style="color: #aaa; font-size: 0.85em; display: flex; align-items: center; cursor: pointer; white-space: nowrap;"),
|
||||||
Label(Input(type="checkbox", name="mode_payant", id="mode-payant-toggle", style="margin-right: 6px; accent-color: #00e5ff; cursor: pointer;"), "👑 Payant", style="color: #aaa; font-size: 0.85em; display: flex; align-items: center; cursor: pointer; white-space: nowrap;"),
|
Label(Input(type="checkbox", name="mode_payant", id="mode-payant-toggle", style="margin-right: 6px; accent-color: #00e5ff; cursor: pointer;", onchange="checkMediaPaymentWarning()"), "👑 Payant", style="color: #aaa; font-size: 0.85em; display: flex; align-items: center; cursor: pointer; white-space: nowrap;"),
|
||||||
Label(Input(type="checkbox", name="mode_multi_ia", id="mode-multi-toggle", style="margin-right: 6px; accent-color: #d97757; cursor: pointer;"), "⚡ Multi-IA", style="color: #aaa; font-size: 0.85em; display: flex; align-items: center; cursor: pointer; white-space: nowrap;"),
|
Label(Input(type="checkbox", name="mode_multi_ia", id="mode-multi-toggle", style="margin-right: 6px; accent-color: #d97757; cursor: pointer;"), "⚡ Multi-IA", style="color: #aaa; font-size: 0.85em; display: flex; align-items: center; cursor: pointer; white-space: nowrap;"),
|
||||||
cls="options-row"
|
cls="options-row"
|
||||||
), cls="input-container-box"
|
), cls="input-container-box"
|
||||||
), hx_post="/chat", hx_target="#chat-history", hx_swap="beforeend", hx_indicator="#global-loader", hx_on__after_request="this.reset(); document.getElementById('file-preview-list').innerHTML=''; document.getElementById('prompt-input').style.height='36px';", enctype="multipart/form-data", style="width: 100%; display: flex; justify-content: center;"
|
), hx_post="/chat", hx_target="#chat-history", hx_swap="beforeend", hx_indicator="#global-loader", hx_on__after_request="this.reset(); document.getElementById('file-preview-list').innerHTML=''; document.getElementById('prompt-input').style.height='36px'; checkMediaPaymentWarning();", enctype="multipart/form-data", style="width: 100%; display: flex; justify-content: center;"
|
||||||
), cls="input-area"
|
), cls="input-area"
|
||||||
), cls="main-content"
|
), cls="main-content"
|
||||||
)
|
)
|
||||||
@@ -178,10 +206,6 @@ def render_login_page(error=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def render_popup_paiement(cout_estime, prompt_sauvegarde, chat_id, multi_ia_state=False, edu_state=False):
|
def render_popup_paiement(cout_estime, prompt_sauvegarde, chat_id, multi_ia_state=False, edu_state=False):
|
||||||
"""
|
|
||||||
Modale de rejet financier.
|
|
||||||
Version sans système de paiement (en attendant la structure juridique).
|
|
||||||
"""
|
|
||||||
edu_val = "on" if edu_state else "off"
|
edu_val = "on" if edu_state else "off"
|
||||||
|
|
||||||
return Div(
|
return Div(
|
||||||
@@ -193,7 +217,6 @@ def render_popup_paiement(cout_estime, prompt_sauvegarde, chat_id, multi_ia_stat
|
|||||||
Div(
|
Div(
|
||||||
Button("Annuler la requête", type="button", onclick="document.getElementById('popup-paiement').remove();", cls="btn-submit", style="background-color: #555; width: 48%; font-size: 0.85em; border-radius: 8px;"),
|
Button("Annuler la requête", type="button", onclick="document.getElementById('popup-paiement').remove();", cls="btn-submit", style="background-color: #555; width: 48%; font-size: 0.85em; border-radius: 8px;"),
|
||||||
|
|
||||||
# Bouton de secours pour forcer un modèle gratuit local (Ollama)
|
|
||||||
Form(
|
Form(
|
||||||
Input(type="hidden", name="prompt", value=prompt_sauvegarde),
|
Input(type="hidden", name="prompt", value=prompt_sauvegarde),
|
||||||
Input(type="hidden", name="chat_id", value=chat_id),
|
Input(type="hidden", name="chat_id", value=chat_id),
|
||||||
|
|||||||
Reference in new issue
Block a user