from fasthtml.common import * from modules.database import load_history 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") 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 "" 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;" ) 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"), Button("📝 TXT", onclick="downloadResponseTXT(this)", cls="msg-action-btn", title="Télécharger en Texte"), Button("🗄️ JSON", onclick="downloadResponseJSON(this)", cls="msg-action-btn", title="Télécharger en JSON"), Button("💬 WhatsApp", onclick="shareWhatsApp(this)", cls="msg-action-btn", title="Partager sur WhatsApp"), Button("📧 Email", onclick="shareEmail(this)", cls="msg-action-btn", title="Envoyer par Email"), 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};" ) def render_sidebar(): history = load_history() sorted_chats = sorted(history.items(), key=lambda x: (not x[1].get('pinned', False), -x[1].get('timestamp', 0))) items = [] for chat_id, data in sorted_chats: pin_icon = "📌" if data.get('pinned', False) else "📍" items.append( Div( A(Span(data['title'], style="color:inherit;"), href=f"/load_chat/{chat_id}", cls="history-link"), Button(pin_icon, cls="history-btn", hx_post=f"/pin/{chat_id}", hx_target="#sidebar-content", hx_swap="outerHTML"), Button("✏️", cls="history-btn", hx_post=f"/rename/{chat_id}", hx_prompt="Nouveau nom :", hx_target="#sidebar-content", hx_swap="outerHTML"), Button("🗑️", cls="history-btn", hx_post=f"/delete/{chat_id}", hx_target="#sidebar-content", hx_swap="outerHTML"), cls="history-item", id=f"chat-item-{chat_id}" ) ) return Div(*items, id="sidebar-content", cls="sidebar-history", hx_swap_oob="true") def render_main_page(chat_id): history = load_history() 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 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"), Div( render_paypal_donation_widget(), 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("👤 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"), 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( Input(type="hidden", name="chat_id", value=chat_id), Div( Div(id="file-preview-list"), 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" ), 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;" ), cls="input-area" ), 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" )