MAJ Phase 10 : Support KubeJS, bouton RAG UI et Modale d'upload admin
Build and Push Docker Image / build-and-push (push) Successful in 37s

This commit is contained in:
xavier committed 2026-08-29 07:38:55 +02:00
1 parent f17ef384f6
commit a2261ab8dd
3 files changed
+79 -5

No files matched your search

+40 -1
View File
@@ -3,6 +3,7 @@ from dotenv import load_dotenv
load_dotenv() # Force la lecture du fichier .env en local
import uuid
import json
import threading
from fasthtml.common import *
from starlette.staticfiles import StaticFiles
from starlette.responses import FileResponse, Response, HTMLResponse, RedirectResponse
@@ -10,13 +11,14 @@ from starlette.middleware.base import BaseHTTPMiddleware
from modules.theme import get_theme_hdrs
from modules.database import nettoyer_vieilles_discussions, HISTORY_FILE, load_history, save_history
from modules.ui_components import render_main_page, render_sidebar, render_login_page
from modules.ui_components import render_main_page, render_sidebar, render_login_page, render_rag_upload_modal
from modules.chat_handler import handle_chat_request
from modules.security import verifier_et_bloquer_vpn, journaliser_ip
from modules.auth_manager import IDENTIFIANTS_ADMIN, verifier_mot_de_passe
from modules.logger import log_event
from modules.billing_manager import charger_soldes, sauvegarder_soldes
from modules.rag_manager import auto_apprendre_rag
from modules.vector_rag import indexer_dossier_thematique
# Lancement du module d'auto-mise à jour en arrière-plan
import modules.model_updater
@@ -152,5 +154,42 @@ def post(prompt: str, reponse: str, theme: str):
auto_apprendre_rag(prompt, reponse, theme)
return Span("✅ Indexé", cls="msg-action-btn", style="background-color: #4caf50; color: white; border: none; cursor: default;")
# --- NOUVELLES ROUTES POUR L'UPLOAD MODAL DU RAG ---
@rt('/admin/rag_modal')
def get(session):
if session.get('user', '') != 'xavier':
return HTMLResponse("Accès refusé.")
return render_rag_upload_modal()
@rt('/api/upload_rag')
async def post(request, session):
if session.get('user', '') != 'xavier':
return Span("❌ Accès refusé", style="color: red;")
form = await request.form()
theme = form.get("theme")
fichiers = form.getlist("fichiers_rag")
if not theme or not fichiers:
return Span("❌ Thème ou fichiers manquants", style="color: red;")
dossier_cible = f"/DATA/AppData/MULTI-IA-AETHAS38/RAG/{theme}"
os.makedirs(dossier_cible, exist_ok=True)
fichiers_sauves = 0
for f in fichiers:
if f.filename:
chemin_fichier = os.path.join(dossier_cible, f.filename)
contenu = await f.read()
with open(chemin_fichier, "wb") as out:
out.write(contenu)
fichiers_sauves += 1
if fichiers_sauves > 0:
threading.Thread(target=indexer_dossier_thematique, args=(theme,)).start()
return Span(f"✅ {fichiers_sauves} fichier(s) sauvé(s). Indexation FAISS démarrée en arrière-plan !", style="color: #4caf50;")
else:
return Span("❌ Aucun fichier valide détecté.", style="color: red;")
if __name__ == '__main__':
serve(port=5001)
+38 -3
View File
@@ -17,7 +17,6 @@ def get_openrouter_stats():
def render_user_msg(text):
return Div(P(text, style="margin:0; font-weight:bold; white-space: pre-wrap;"), cls="user-bubble")
# --- LA FONCTION CORRIGÉE AVEC SES 10 ARGUMENTS ---
def render_ai_msg(text, intention, nom_fournisseur, nom_modele, couleur, execution_time=None, trace=None, prompt_associe="", thematique="general", msg_id=""):
time_badge = Span(f"⏱️ {execution_time}s", style="margin-right: 10px; color:#aaa; font-size:0.8em;") if execution_time else ""
@@ -33,7 +32,6 @@ def render_ai_msg(text, intention, nom_fournisseur, nom_modele, couleur, executi
time_badge, style="border-bottom: 1px solid #333; padding-bottom: 10px; margin-bottom: 10px; display: flex; flex-wrap: wrap; gap: 5px;"
)
# --- BOUTON FORCER RAG (HTMX) ---
bouton_forcer = ""
if msg_id and prompt_associe and thematique != "aucun":
safe_prompt = prompt_associe.replace('"', '\\"').replace('\n', ' ')
@@ -110,7 +108,6 @@ def render_main_page(chat_id, utilisateur_nom="xavier", role="Super Admin"):
.catch(() => { serverDown = true; console.log("Serveur hors ligne..."); });
}, 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;
@@ -155,6 +152,7 @@ def render_main_page(chat_id, utilisateur_nom="xavier", role="Super Admin"):
Div(
A("+ Nouvelle discussion", href="/new_chat", cls="new-chat-btn"),
A("💾 Exporter JSON", href="/export_json", target="_blank", cls="export-btn"),
Button("📚 Gestion RAG", hx_get="/admin/rag_modal", hx_target="body", hx_swap="beforeend", cls="export-btn", style="background-color: #2196f3; color: white; border-color: #2196f3; margin-top: 5px; cursor: pointer;") if role.lower() in ["super admin", "admin"] else "",
A("🚪 Déconnexion", href="/logout", cls="export-btn", style="background-color: #8b0000; color: white; border-color: #8b0000; margin-top: 5px;"),
cls="nav-buttons", style="display: flex; flex-direction: column; gap: 10px;"
), cls="sidebar-top"
@@ -253,3 +251,40 @@ def render_popup_paiement(cout_estime, prompt_sauvegarde, chat_id, multi_ia_stat
id="popup-paiement",
style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 9999;"
)
def render_rag_upload_modal():
"""Génère la fenêtre pop-up pour téléverser des documents dans le RAG."""
themes = [
"Minecraft_KubeJS", "Juridique_Loi_Francaise", "Serveur_Debian",
"Bureautique_Excel", "Regles_Aethas38", "Education_Nationale", "Connaissances_Generales"
]
options = [Option(t.replace("_", " "), value=t) for t in themes]
return Div(
Div(
H3("📚 Administration RAG (Base Vectorielle)", style="color: #2196f3; margin-top: 0;"),
P("Formats supportés : PDF, MD, TXT, JS, JSON, YML, CSV", style="color: #ccc; font-size: 0.9em;"),
Form(
Div(
Label("1. Choisir la thématique :", style="font-weight: bold; color: white;"),
Select(*options, name="theme", cls="input-field", style="margin-bottom: 15px;")
),
Div(
Label("2. Sélectionner les fichiers :", style="font-weight: bold; color: white;"),
Input(type="file", name="fichiers_rag", multiple=True, required=True, cls="input-field", style="margin-bottom: 15px; background: #222; padding: 10px; border: 1px dashed #2196f3; cursor: pointer;")
),
Div(
Button("Annuler", type="button", onclick="document.getElementById('rag-modal').remove();", cls="btn-submit", style="background-color: #555; width: 48%;"),
Button("Téléverser & Indexer", type="submit", cls="btn-submit", style="background-color: #2196f3; color: white; width: 48%;"),
style="display: flex; justify-content: space-between;"
),
hx_post="/api/upload_rag",
hx_target="#rag-upload-result",
enctype="multipart/form-data"
),
Div(id="rag-upload-result", style="margin-top: 15px; text-align: center; font-weight: bold;"),
style="background: #1a1a1a; padding: 25px; border-radius: 12px; border: 2px solid #2196f3; width: 500px; max-width: 90vw; box-shadow: 0 10px 30px rgba(0,0,0,0.8);"
),
id="rag-modal",
style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 9999;"
)
+1 -1
View File
@@ -46,7 +46,7 @@ def indexer_dossier_thematique(thematique: str):
reader = pypdf.PdfReader(chemin_complet)
for page in reader.pages:
texte += page.extract_text() or ""
elif fichier.lower().endswith(('.md', '.txt')):
elif fichier.lower().endswith(('.md', '.txt', '.yml', '.yaml', '.js', '.json', '.csv', '.toml', '.properties')):
with open(chemin_complet, 'r', encoding='utf-8') as f:
texte = f.read()