Public Access
Amélioration contraste mobile, traçabilité des IA et filtrage catalogue
Build and Push Docker Image / build-and-push (push) Successful in 35s
Build and Push Docker Image / build-and-push (push) Successful in 35s
This commit is contained in:
1 parent
3e140d7d6f
commit
9fdb2bd73e
7 files changed
+115
-19
No files matched your search
@@ -11824,5 +11824,41 @@
|
||||
"action": "Acc\u00e8s \u00e0 /",
|
||||
"date": "2026-08-23 11:36:33",
|
||||
"timestamp": 1787477793.7782843
|
||||
},
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"action": "Acc\u00e8s \u00e0 /",
|
||||
"date": "2026-08-23 14:50:01",
|
||||
"timestamp": 1787489401.7572532
|
||||
},
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"action": "Acc\u00e8s \u00e0 /chat",
|
||||
"date": "2026-08-23 14:53:10",
|
||||
"timestamp": 1787489590.9684572
|
||||
},
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"action": "Acc\u00e8s \u00e0 /",
|
||||
"date": "2026-08-23 15:04:55",
|
||||
"timestamp": 1787490295.170784
|
||||
},
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"action": "Acc\u00e8s \u00e0 /",
|
||||
"date": "2026-08-23 15:05:06",
|
||||
"timestamp": 1787490306.0473568
|
||||
},
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"action": "Acc\u00e8s \u00e0 /",
|
||||
"date": "2026-08-23 15:05:37",
|
||||
"timestamp": 1787490337.0444038
|
||||
},
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"action": "Acc\u00e8s \u00e0 /",
|
||||
"date": "2026-08-23 15:08:53",
|
||||
"timestamp": 1787490533.299349
|
||||
}
|
||||
]
|
||||
@@ -37,11 +37,12 @@ def executer_synthese_multi_ia(prompt_securise: str, fournisseur: str, mode_paya
|
||||
|
||||
def executer_requete_ia(fournisseur: str, prompt: str, mode_payant: bool = False, multi_ia: bool = False, chat_id: str = None):
|
||||
prompt_securise = f"{SYSTEM_CONFIDENTIALITY_INSTRUCTION}\n\nRequête utilisateur :\n{prompt}"
|
||||
trace_tentatives = []
|
||||
|
||||
if multi_ia or fournisseur in ["openrouter_code", "openrouter_droit"]:
|
||||
resultat_multi = executer_synthese_multi_ia(prompt_securise, fournisseur, mode_payant, chat_id)
|
||||
if resultat_multi:
|
||||
return resultat_multi
|
||||
return resultat_multi[0], resultat_multi[1], resultat_multi[2], resultat_multi[3], ["🟢 Multi-IA / Synthèse Gemini réussie"]
|
||||
|
||||
if fournisseur == "gemini":
|
||||
ordre_repli = [interroger_gemini, interroger_openrouter, interroger_local_ollama]
|
||||
@@ -51,6 +52,7 @@ def executer_requete_ia(fournisseur: str, prompt: str, mode_payant: bool = False
|
||||
ordre_repli = [interroger_local_ollama, interroger_openrouter, interroger_gemini]
|
||||
|
||||
for fonction_api in ordre_repli:
|
||||
nom_tente = fonction_api.__name__.replace("interroger_", "")
|
||||
try:
|
||||
if fonction_api.__name__ == "interroger_openrouter":
|
||||
texte, couleur, nom_fournisseur, nom_modele, _, _ = fonction_api(
|
||||
@@ -60,9 +62,12 @@ def executer_requete_ia(fournisseur: str, prompt: str, mode_payant: bool = False
|
||||
texte, couleur, nom_fournisseur, nom_modele = fonction_api(prompt_securise)
|
||||
|
||||
if texte:
|
||||
return texte, couleur, nom_fournisseur, nom_modele
|
||||
trace_tentatives.append(f"🟢 {nom_tente.upper()} ({nom_modele}) : Succès")
|
||||
return texte, couleur, nom_fournisseur, nom_modele, trace_tentatives
|
||||
except Exception as e:
|
||||
msg_err = str(e).split("-")[0].strip()[:50] # On raccourcit l'erreur pour l'affichage
|
||||
trace_tentatives.append(f"🔴 {nom_tente.upper()} : Échec ({msg_err})")
|
||||
log_event("WARNING", "ORCHESTRATEUR", f"Basculement suite à l'échec de {fonction_api.__name__} : {e}")
|
||||
continue
|
||||
|
||||
raise Exception("Tous les fournisseurs d'IA sont indisponibles.")
|
||||
raise Exception(f"Tous les fournisseurs d'IA ont échoué. Trace : {' | '.join(trace_tentatives)}")
|
||||
+13
-2
@@ -138,7 +138,9 @@ async def handle_chat_request(request, session):
|
||||
if prix_estime > obtenir_solde(utilisateur_nom):
|
||||
return Div(H3("❌ Solde Insuffisant", style="color:red;"), P("Rechargez votre compte."), style="background:#300; padding:15px;")
|
||||
|
||||
reponse, couleur, fournisseur_nom, modele = executer_requete_ia(fournisseur, full_prompt, mode_payant=mode_payant, multi_ia=mode_multi_ia, chat_id=chat_id)
|
||||
reponse, couleur, fournisseur_nom, modele, trace_route = 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)
|
||||
@@ -154,7 +156,16 @@ async def handle_chat_request(request, session):
|
||||
if file_names: display_msg += f"\n\n📎 **Fichiers joints :** {', '.join(file_names)}"
|
||||
|
||||
history[chat_id]["messages"].append({"role": "user", "content": display_msg})
|
||||
history[chat_id]["messages"].append({"role": "ai", "content": reponse, "intention": intention, "fournisseur": fournisseur_nom, "modele": modele, "couleur": couleur, "execution_time": execution_time})
|
||||
history[chat_id]["messages"].append({
|
||||
"role": "ai",
|
||||
"content": reponse,
|
||||
"intention": intention,
|
||||
"fournisseur": fournisseur_nom,
|
||||
"modele": modele,
|
||||
"couleur": couleur,
|
||||
"execution_time": execution_time,
|
||||
"trace": trace_route # <--- Ajout de la traçabilité
|
||||
})
|
||||
save_history(history)
|
||||
|
||||
return render_user_msg(display_msg), render_ai_msg(reponse, intention, fournisseur_nom, modele, couleur, execution_time), Script("renderMarkdown();"), render_sidebar()
|
||||
|
||||
@@ -30,7 +30,8 @@ def _do_update():
|
||||
pricing = m.get("pricing", {})
|
||||
# Gratuits purs
|
||||
if pricing.get("prompt") == "0" and pricing.get("completion") == "0":
|
||||
modeles_gratuits.append(m["id"])
|
||||
if "nvidia/nemotron" not in m["id"].lower():
|
||||
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"])
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
def nettoyer_decision_router(raw_text):
|
||||
try:
|
||||
clean = re.sub(r'```(?:json)?\s*', '', raw_text)
|
||||
clean = clean.replace('```', '').strip()
|
||||
start = clean.find('{')
|
||||
end = clean.rfind('}') + 1
|
||||
if start != -1 and end != 0:
|
||||
return json.loads(clean[start:end])
|
||||
except Exception:
|
||||
pass
|
||||
return {"intention": "Analyse", "fournisseur": "gemini"}
|
||||
+10
-8
@@ -11,7 +11,7 @@ def get_theme_hdrs():
|
||||
Link(rel="stylesheet", href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css"),
|
||||
Link(rel="icon", type="image/svg+xml", href=f"data:image/svg+xml;utf8,{svg_favicon}"),
|
||||
Style("""
|
||||
:root { --bg-dark: #121212; --bg-panel: #1e1e24; --accent-blue: #00e5ff; --accent-purple: #b300ff; --text-main: #e0e0e0; }
|
||||
:root { --bg-dark: #121212; --bg-panel: #1e1e24; --accent-blue: #00e5ff; --accent-purple: #b300ff; --text-main: #f8fafc; }
|
||||
body { background-color: var(--bg-dark); color: var(--text-main); font-family: 'Segoe UI', Tahoma, sans-serif; margin: 0; display: flex; height: 100vh; overflow: hidden; box-sizing: border-box; }
|
||||
|
||||
/* --- SIDEBAR CLASSIQUE --- */
|
||||
@@ -30,13 +30,17 @@ def get_theme_hdrs():
|
||||
.history-btn { background: none; border: none; color: #888; cursor: pointer; padding: 2px 5px; font-size: 1.1em; }
|
||||
.history-btn:hover { color: var(--accent-blue); }
|
||||
|
||||
/* --- CONTENU CENTRAL --- */
|
||||
/* --- CONTENU CENTRAL & CONTRASTE MOBILE ACCRU --- */
|
||||
.main-content { flex: 1; display: flex; flex-direction: column; height: 100vh; position: relative; box-sizing: border-box; }
|
||||
.chat-container { flex: 1; padding: 20px; max-width: 1000px; margin: 0 auto; width: 100%; overflow-y: auto; display: flex; flex-direction: column; gap: 20px; padding-bottom: 160px; box-sizing: border-box; }
|
||||
.user-bubble { background-color: #2a2a35; padding: 15px 20px; border-radius: 15px 15px 0 15px; align-self: flex-end; max-width: 80%; border: 1px solid #444; margin-left: auto; word-break: break-word; overflow-wrap: break-word; }
|
||||
.ai-bubble { background-color: var(--bg-panel); padding: 15px 20px; border-radius: 15px 15px 15px 0; align-self: flex-start; max-width: 90%; width: 100%; border: 1px solid #333; word-break: break-word; overflow-wrap: break-word; }
|
||||
.msg-actions { display: flex; gap: 10px; margin-top: 15px; border-top: 1px dashed #333; padding-top: 10px; justify-content: flex-end; flex-wrap: wrap; }
|
||||
.msg-action-btn { background: transparent; border: 1px solid #444; color: #888; border-radius: 15px; padding: 5px 12px; cursor: pointer; font-size: 0.85em; transition: 0.2s; }
|
||||
.user-bubble { background-color: #2a2a35; color: #ffffff; padding: 15px 20px; border-radius: 15px 15px 0 15px; align-self: flex-end; max-width: 80%; border: 1px solid #555; margin-left: auto; word-break: break-word; overflow-wrap: break-word; }
|
||||
|
||||
/* BULLE IA : Contraste maximal forcé pour le confort de lecture mobile */
|
||||
.ai-bubble { background-color: var(--bg-panel); color: #ffffff !important; padding: 15px 20px; border-radius: 15px 15px 15px 0; align-self: flex-start; max-width: 90%; width: 100%; border: 1px solid #444; word-break: break-word; overflow-wrap: break-word; }
|
||||
.markdown-rendered, .markdown-rendered p, .markdown-rendered li, .markdown-rendered span, .markdown-rendered strong { color: #ffffff !important; }
|
||||
|
||||
.msg-actions { display: flex; gap: 10px; margin-top: 15px; border-top: 1px dashed #444; padding-top: 10px; justify-content: flex-end; flex-wrap: wrap; }
|
||||
.msg-action-btn { background: transparent; border: 1px solid #555; color: #ccc; border-radius: 15px; padding: 5px 12px; cursor: pointer; font-size: 0.85em; transition: 0.2s; }
|
||||
.msg-action-btn:hover { background: #2a2a35; color: var(--accent-blue); border-color: var(--accent-blue); }
|
||||
.code-header { background: #16161a; padding: 8px 15px; display: flex; justify-content: space-between; align-items: center; border-radius: 8px 8px 0 0; font-family: monospace; font-size: 0.85em; border: 1px solid #333; border-bottom: none; }
|
||||
.code-actions-group { display: flex; gap: 8px; }
|
||||
@@ -85,7 +89,6 @@ def get_theme_hdrs():
|
||||
Script(r"""
|
||||
let aethasFiles = new DataTransfer();
|
||||
|
||||
// JS pour le bouton Hamburger
|
||||
window.toggleSidebar = function() {
|
||||
let sidebar = document.querySelector('.sidebar');
|
||||
if (sidebar) {
|
||||
@@ -250,7 +253,6 @@ def get_theme_hdrs():
|
||||
});
|
||||
});
|
||||
|
||||
// ... (Fonctions de téléchargement ZIP, PDF, DOC, TXT, JSON et Partage identiques et préservées)
|
||||
function downloadCodeZip(btn, lang) {
|
||||
let pre = btn.closest('.code-header').nextElementSibling;
|
||||
let code = pre.querySelector('code').innerText;
|
||||
|
||||
@@ -17,14 +17,22 @@ 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")
|
||||
|
||||
def render_ai_msg(text, intention, nom_fournisseur, nom_modele, couleur, execution_time=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 ""
|
||||
|
||||
# Construction de la trace visuelle si elle existe
|
||||
trace_html = ""
|
||||
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]
|
||||
trace_html = Div(Span("Route : ", style="color:#888; font-size:0.75em;"), *badges, style="display:flex; gap:5px; align-items:center; margin-bottom:8px; flex-wrap:wrap;")
|
||||
|
||||
encart_info = Div(
|
||||
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, style="border-bottom: 1px solid #333; padding-bottom: 10px; margin-bottom: 15px; display: flex; flex-wrap: wrap; gap: 5px;"
|
||||
time_badge, style="border-bottom: 1px solid #333; padding-bottom: 10px; margin-bottom: 10px; display: flex; flex-wrap: wrap; gap: 5px;"
|
||||
)
|
||||
|
||||
actions = Div(
|
||||
Button("📄 PDF", onclick="downloadResponsePDF(this)", cls="msg-action-btn", title="Télécharger en PDF"),
|
||||
Button("📘 DOC", onclick="downloadResponseDOC(this)", cls="msg-action-btn", title="Télécharger en Word"),
|
||||
@@ -34,7 +42,16 @@ def render_ai_msg(text, intention, nom_fournisseur, nom_modele, couleur, executi
|
||||
Button("📧 Email", onclick="shareEmail(this)", cls="msg-action-btn", title="Envoyer par courriel"),
|
||||
cls="msg-actions"
|
||||
)
|
||||
return Div(encart_info, Div(text, cls="markdown-raw", style="display:none;"), Div(cls="markdown-rendered", style="line-height: 1.6;"), actions, cls="ai-bubble", style=f"border-left: 4px solid {couleur};")
|
||||
|
||||
return Div(
|
||||
trace_html, # Affichage du chemin de routage
|
||||
encart_info,
|
||||
Div(text, cls="markdown-raw", style="display:none;"),
|
||||
Div(cls="markdown-rendered", style="line-height: 1.6;"),
|
||||
actions,
|
||||
cls="ai-bubble",
|
||||
style=f"border-left: 4px solid {couleur};"
|
||||
)
|
||||
|
||||
def render_sidebar():
|
||||
history = load_history()
|
||||
@@ -50,8 +67,18 @@ def render_main_page(chat_id, utilisateur_nom="xavier", role="Super Admin"):
|
||||
messages_ui = []
|
||||
if chat_id in history:
|
||||
for msg in history[chat_id].get('messages', []):
|
||||
if msg['role'] == 'user': messages_ui.append(render_user_msg(msg['content']))
|
||||
else: messages_ui.append(render_ai_msg(msg['content'], msg.get('intention', ''), msg.get('fournisseur', ''), msg.get('modele', ''), msg.get('couleur', '#333'), msg.get('execution_time', '')))
|
||||
if msg['role'] == 'user':
|
||||
messages_ui.append(render_user_msg(msg['content']))
|
||||
else:
|
||||
messages_ui.append(render_ai_msg(
|
||||
msg['content'],
|
||||
msg.get('intention', ''),
|
||||
msg.get('fournisseur', ''),
|
||||
msg.get('modele', ''),
|
||||
msg.get('couleur', '#333'),
|
||||
msg.get('execution_time', ''),
|
||||
msg.get('trace', []) # <--- C'est ici qu'on transmet la trace aux anciens messages !
|
||||
))
|
||||
if messages_ui: messages_ui.append(Script("setTimeout(renderMarkdown, 150);"))
|
||||
|
||||
auto_refresh_script = Script('''
|
||||
|
||||
Reference in new issue
Block a user