maj auto 1

This commit is contained in:
xavier committed 2026-08-16 22:26:02 +02:00
1 parent a8f9ab28ed
commit d47bd67391
2 files changed
+131 -52

No files matched your search

+1
View File
@@ -3,3 +3,4 @@ venv/
.env
.sesskey
aethas_history.json
Echange GEMINY.txt
+130 -52
View File
@@ -7,6 +7,7 @@ import requests
from bs4 import BeautifulSoup
from dotenv import load_dotenv
from fasthtml.common import *
from starlette.responses import FileResponse
from openai import OpenAI
from groq import Groq
from google import genai
@@ -25,6 +26,26 @@ def load_history():
def save_history(history):
with open(HISTORY_FILE, 'w', encoding='utf-8') as f: json.dump(history, f, indent=4)
# --- SCRIPT DE NETTOYAGE AUTO ---
def nettoyer_vieilles_discussions():
history = load_history()
now = time.time()
un_an_en_secondes = 365 * 24 * 60 * 60
a_supprimer = []
for chat_id, data in history.items():
if not data.get('pinned', False):
if now - data.get('timestamp', now) > un_an_en_secondes:
a_supprimer.append(chat_id)
if a_supprimer:
for chat_id in a_supprimer:
del history[chat_id]
save_history(history)
print(f"🧹 AETHAS CORE : Nettoyage auto terminé. {len(a_supprimer)} discussion(s) de plus de 365 jours supprimée(s).")
nettoyer_vieilles_discussions()
svg_favicon = "%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cstyle%3E@keyframes pulse { 0% { opacity: 1; stroke-width: 5; } 50% { opacity: 0.5; stroke-width: 7; } 100% { opacity: 1; stroke-width: 5; } } .neon { animation: pulse 2s infinite; transform-origin: 50px 50px; transform: rotate(45deg); }%3C/style%3E%3Crect width='100' height='100' fill='%23121212'/%3E%3Cg class='neon'%3E%3Crect x='25' y='25' width='50' height='50' fill='none' stroke='%2300e5ff'/%3E%3Cpath d='M25 50 L75 50 M50 25 L50 75' stroke='%23b300ff'/%3E%3Ccircle cx='50' cy='50' r='8' fill='%23e0e0e0'/%3E%3C/g%3E%3C/svg%3E"
theme_hdrs = (
@@ -43,11 +64,10 @@ theme_hdrs = (
.sidebar-history { flex: 1; overflow-y: auto; margin-top: 15px; margin-bottom: 15px; }
.sidebar-footer { flex-shrink: 0; border-top: 1px solid #333; padding-top: 15px; text-align: center; }
/* CORRECTION: Boutons disposés en colonne proprement */
.nav-buttons { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
.new-chat-btn { display:block; text-align:center; text-decoration:none; background: transparent; border: 1px solid var(--accent-blue); color: var(--accent-blue); padding: 10px; border-radius: 5px; cursor: pointer; font-weight: bold; transition: 0.2s; }
.new-chat-btn:hover { background: rgba(0, 229, 255, 0.1); }
.export-btn { background: #2a2a35; border: 1px solid #444; color: white; padding: 10px; border-radius: 5px; cursor: pointer; transition: 0.2s; font-size: 1em; text-align:center;}
.export-btn { background: #2a2a35; border: 1px solid #444; color: white; padding: 10px; border-radius: 5px; cursor: pointer; transition: 0.2s; font-size: 1em; text-align:center; text-decoration:none; display:block;}
.export-btn:hover { background: #444; }
.history-item { display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #222; font-size: 0.9em; transition: 0.2s; }
@@ -86,64 +106,107 @@ theme_hdrs = (
.btn-submit { background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple)); color: white; border: none; width: 36px !important; height: 36px !important; min-width: 36px !important; max-width: 36px !important; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 1.1em; flex-shrink: 0 !important; box-sizing: border-box; }
.btn-submit:active { transform: scale(0.95); }
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: flex; }
.htmx-request.htmx-indicator { display: flex; }
.tech-loader-container { display: flex; align-items: center; gap: 15px; color: var(--accent-blue); font-style: italic; background: var(--bg-panel); padding: 15px 20px; border-radius: 15px 15px 15px 0; border: 1px dashed var(--accent-blue); width: fit-content; margin-bottom: 20px; font-size: 0.9em; box-shadow: 0 0 10px rgba(0, 229, 255, 0.1); }
.tech-spinner { width: 20px; height: 20px; border: 3px solid rgba(0, 229, 255, 0.2); border-top-color: var(--accent-blue); border-radius: 50%; animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
"""),
Script("""
function renderMarkdown() {
try {
document.querySelectorAll('.markdown-raw').forEach(el => {
if (el.dataset.rendered) return;
let container = el.nextElementSibling;
if (typeof marked !== 'undefined') {
container.innerHTML = marked.parse(el.textContent);
} else {
container.innerText = el.textContent;
}
el.dataset.rendered = "true";
el.classList.remove('markdown-raw');
container.querySelectorAll('pre').forEach(pre => {
try {
let code = pre.querySelector('code');
let lang = code.className.replace('language-', '') || 'texte';
if(code) {
let rawLang = code.className.replace('language-', '') || 'texte';
let lang = rawLang.toUpperCase();
let header = document.createElement('div');
header.className = 'code-header';
header.innerHTML = `<span style="color:#00e5ff; font-weight:bold;">${lang.toUpperCase()}</span><div><button onclick="copyCode(this)">📋 Copier</button></div>`;
header.innerHTML = '<span style="color:#00e5ff; font-weight:bold;">' + lang + '</span><div><button onclick="copyCode(this)">📋 Copier</button></div>';
pre.parentNode.insertBefore(header, pre);
hljs.highlightElement(code);
if (typeof hljs !== 'undefined') hljs.highlightElement(code);
}
} catch(err) { console.warn("Aethas - Erreur formatage code", err); }
});
});
let chat = document.getElementById('chat-history');
chat.scrollTop = chat.scrollHeight;
if (chat) chat.scrollTop = chat.scrollHeight;
} catch(globalErr) {
console.error("Aethas - Erreur fatale rendu Markdown", globalErr);
}
}
// --- SCRIPTS D'EXPORTATION ---
function downloadResponsePDF(btn) {
let content = btn.closest('.ai-bubble').querySelector('.markdown-rendered');
let originalColor = content.style.color;
let originalBg = content.style.backgroundColor;
try {
let renderedNode = btn.closest('.ai-bubble').querySelector('.markdown-rendered');
let rawNode = btn.closest('.ai-bubble').querySelector('.markdown-raw');
let originalContent = (renderedNode && renderedNode.innerHTML.trim() !== "") ? renderedNode : rawNode;
// Forçage du texte en noir sur blanc pour le PDF
content.style.color = '#000000';
content.style.backgroundColor = '#ffffff';
content.style.padding = '20px';
let content = originalContent.cloneNode(true);
let wrapper = document.createElement('div');
wrapper.style.backgroundColor = '#ffffff';
wrapper.style.padding = '20px';
wrapper.style.fontFamily = 'Arial, sans-serif';
wrapper.style.color = '#000000';
content.querySelectorAll('*').forEach(el => {
el.style.color = '#000000';
if (el.tagName === 'PRE' || el.tagName === 'CODE' || el.tagName === 'TABLE' || el.tagName === 'TH' || el.tagName === 'TD') {
el.style.backgroundColor = '#f4f4f4';
el.style.borderColor = '#aaaaaa';
el.style.borderWidth = '1px';
el.style.borderStyle = 'solid';
el.style.borderCollapse = 'collapse';
} else {
el.style.backgroundColor = 'transparent';
}
});
wrapper.appendChild(content);
let opt = {
margin: 10,
filename: 'Aethas38_Reponse.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, backgroundColor: '#ffffff' },
html2canvas: { scale: 2, backgroundColor: '#ffffff', useCORS: true },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
};
html2pdf().set(opt).from(content).save().then(() => {
content.style.color = originalColor;
content.style.backgroundColor = originalBg;
content.style.padding = '';
});
html2pdf().set(opt).from(wrapper).save();
} catch (err) {
alert("Erreur lors de la génération du PDF.");
}
}
function downloadResponseTXT(btn) {
let content = btn.closest('.ai-bubble').querySelector('.markdown-rendered');
let blob = new Blob([content.innerText], {type: 'text/plain;charset=utf-8'});
let rendered = btn.closest('.ai-bubble').querySelector('.markdown-rendered').innerText;
let raw = btn.closest('.ai-bubble').querySelector('.markdown-raw').innerText;
let finalContent = rendered ? rendered : raw;
let blob = new Blob([finalContent], {type: 'text/plain;charset=utf-8'});
let link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'Aethas38_Reponse.txt';
document.body.appendChild(link); link.click(); document.body.removeChild(link);
}
function downloadResponseDOC(btn) {
let content = btn.closest('.ai-bubble').querySelector('.markdown-rendered').innerHTML;
let renderedNode = btn.closest('.ai-bubble').querySelector('.markdown-rendered');
let content = (renderedNode && renderedNode.innerHTML.trim() !== "") ? renderedNode.innerHTML : btn.closest('.ai-bubble').querySelector('.markdown-raw').innerText;
let preHtml = "<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:w='urn:schemas-microsoft-com:office:word' xmlns='http://www.w3.org/TR/REC-html40'><head><meta charset='utf-8'><title>Aethas Export</title></head><body style='font-family: Arial, sans-serif; color: #000000; background-color: #ffffff;'>";
let postHtml = "</body></html>";
let blob = new Blob(['\\ufeff', preHtml + content + postHtml], { type: 'application/msword' });
@@ -152,8 +215,10 @@ theme_hdrs = (
}
function downloadResponseJSON(btn) {
let content = btn.closest('.ai-bubble').querySelector('.markdown-rendered').innerText;
let data = { "timestamp": new Date().toISOString(), "content": content };
let rendered = btn.closest('.ai-bubble').querySelector('.markdown-rendered').innerText;
let raw = btn.closest('.ai-bubble').querySelector('.markdown-raw').innerText;
let finalContent = rendered ? rendered : raw;
let data = { "timestamp": new Date().toISOString(), "content": finalContent };
let blob = new Blob([JSON.stringify(data, null, 2)], {type: 'application/json;charset=utf-8'});
let link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'Aethas38_Reponse.json';
document.body.appendChild(link); link.click(); document.body.removeChild(link);
@@ -167,29 +232,36 @@ theme_hdrs = (
setTimeout(() => btn.innerText = "📋 Copier", 2000);
}
function exportChat() {
let chat = document.getElementById('chat-history');
if(!chat) return;
let blob = new Blob([chat.innerText], {type: 'text/plain;charset=utf-8'});
let link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'Aethas38_Discussion_Globale.txt';
document.body.appendChild(link); link.click(); document.body.removeChild(link);
}
function handleFileSelection(input) {
let previewList = document.getElementById('file-preview-list');
previewList.innerHTML = '';
if (input.files) {
for (let i = 0; i < input.files.length; i++) {
let pill = document.createElement('div'); pill.className = 'file-pill'; pill.innerHTML = `📎 ${input.files[i].name}`;
let pill = document.createElement('div'); pill.className = 'file-pill'; pill.innerHTML = '📎 ' + input.files[i].name;
previewList.appendChild(pill);
}
}
}
function autoResize(textarea) {
if(!textarea) return;
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
}
function shareWhatsApp(btn) {
let rendered = btn.closest('.ai-bubble').querySelector('.markdown-rendered').innerText;
let raw = btn.closest('.ai-bubble').querySelector('.markdown-raw').innerText;
let content = rendered ? rendered : raw;
window.open('https://api.whatsapp.com/send?text=' + encodeURIComponent("🤖 *Aethas38 (IA) :*\n\n" + content), '_blank');
}
function shareEmail(btn) {
let rendered = btn.closest('.ai-bubble').querySelector('.markdown-rendered').innerText;
let raw = btn.closest('.ai-bubble').querySelector('.markdown-raw').innerText;
let content = rendered ? rendered : raw;
window.open('mailto:?subject=Réponse Aethas38 IA&body=' + encodeURIComponent(content), '_self');
}
""")
)
@@ -206,8 +278,11 @@ def interroger_gemini_cascade(prompt):
modeles_a_tester = ["gemini-3.1-pro-preview", "gemini-3.1-pro", "gemini-2.5-pro", "gemini-3.7-flash", "gemini-3.6-flash"]
for nom_modele in modeles_a_tester:
try:
chat = gemini_client.chats.create(model=nom_modele, config={"system_instruction": "Réponds toujours dans la langue de l'utilisateur."})
config = {"system_instruction": "Réponds toujours dans la langue de l'utilisateur. Utilise exclusivement le format Markdown pour les tableaux et la mise en forme. N'utilise JAMAIS de code LaTeX."}
chat = gemini_client.chats.create(model=nom_modele, config=config)
ia_response = chat.send_message(prompt)
if not ia_response.text:
raise Exception("Réponse vide de Google.")
return ia_response.text, nom_modele
except Exception:
continue
@@ -217,7 +292,6 @@ def rechercher_sur_google(requete):
try:
from googlesearch import search
urls = []
# Modification ici : num_results=5 au lieu de 3
for url in search(requete, num_results=5, lang="fr"):
urls.append(url)
return urls
@@ -276,6 +350,8 @@ def render_ai_msg(text, intention, nom_fournisseur, nom_modele, couleur):
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"
)
@@ -322,7 +398,7 @@ def get(session):
messages_ui.append(render_ai_msg(msg['content'], msg.get('intention', ''), msg.get('fournisseur', ''), msg.get('modele', ''), msg.get('couleur', '#333')))
if messages_ui:
messages_ui.append(Script("setTimeout(renderMarkdown, 100);"))
messages_ui.append(Script("setTimeout(renderMarkdown, 150);"))
return Title("Aethas38 - Mine_AI"), Body(
Div(
@@ -330,7 +406,7 @@ def get(session):
Div(H2("AETHAS 38", style="color:#00e5ff; margin:0; text-align:center;"), P("Tech Core", style="font-size:0.7em; color:#888; text-align:center;"), margin_bottom="20px"),
Div(
A("+ Nouvelle discussion", href="/new_chat", cls="new-chat-btn"),
Button("💾 Exporter JSON", cls="export-btn", onclick="exportChat()", title="Télécharger la conversation Globale"),
A("💾 Exporter JSON", href="/export_json", target="_blank", cls="export-btn", title="Télécharger la conversation Globale"),
cls="nav-buttons"
),
cls="sidebar-top"
@@ -344,7 +420,13 @@ def get(session):
),
Div(
Div(*messages_ui, id="chat-history", cls="chat-container"),
Div(*messages_ui, id="chat-history", cls="chat-container", style="padding-bottom: 20px;"),
Div(
Div(Div(cls="tech-spinner"), Span("Aethas Core analyse la requête..."), 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),
@@ -362,6 +444,7 @@ def get(session):
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='auto';",
enctype="multipart/form-data",
style="width: 100%; display: flex; justify-content: center;"
@@ -389,7 +472,6 @@ async def post(request, session: dict):
history = load_history()
# --- 1. INTELLIGENCE DE SÉCURITÉ DU ROUTEUR (Smarter Interceptor) ---
security_check_prompt = f"""Tu es l'Aiguilleur du système Aethas38. Ton rôle EXCLUSIF est d'analyser et de router la demande.
RÈGLE DE SÉCURITÉ ABSOLUE : Les recherches web, les serveurs Minecraft, les modpacks (ex: ATM10) SONT AUTORISÉS.
@@ -411,7 +493,6 @@ async def post(request, session: dict):
intention = decision.get('intention', '')
fournisseur = decision.get("fournisseur", "local").lower()
# Si Qwen détecte un VRAI danger
if intention == "Alerte Securite":
if chat_id not in history:
history[chat_id] = {"title": "Alerte Sécurité...", "pinned": False, "timestamp": time.time(), "messages": []}
@@ -423,7 +504,6 @@ async def post(request, session: dict):
return render_user_msg(prompt), render_ai_msg(reponse_securite, "Alerte Sécurité", "AETHAS CORE", "Cyber-Sec", "#ff0044"), Script("renderMarkdown();"), render_sidebar()
# --- 2. TRAITEMENT CLASSIQUE ---
urls_to_scrape = re.findall(r'https?://[^\s]+', prompt)
mots_cles = ["cherche sur", "recherche sur", "sur internet", "sur le web", "sur le net"]
if any(mot in prompt.lower() for mot in mots_cles):
@@ -451,25 +531,18 @@ async def post(request, session: dict):
if web_content:
full_prompt += f"\n\n[INSTRUCTION SYSTÈME STRICTE : Ne dis JAMAIS que tu ne peux pas naviguer sur internet ou télécharger des fichiers. Le système a déjà fait la recherche web pour toi. Voici le contenu brut extrait des sites. Utilise-le IMPÉRATIVEMENT pour formuler ta réponse sans mentionner tes propres limitations :]\n{web_content}"
prompt_tokens = len(full_prompt.split()) * 2
completion_tokens = 0
# Fonction d'appel à Groq centralisée pour éviter la duplication de code
def appeler_groq_api(prompt_text):
resp = groq_client.chat.completions.create(
messages=[{"role": "system", "content": "Réponds dans la langue de l'utilisateur. Tu as accès aux recherches web fournies."}, {"role": "user", "content": prompt_text}],
messages=[{"role": "system", "content": "Réponds dans la langue de l'utilisateur. Tu as accès aux recherches web fournies. Utilise uniquement Markdown. Pas de code LaTeX."}, {"role": "user", "content": prompt_text}],
model="llama-3.3-70b-versatile"
)
return resp.choices[0].message.content, "#f97316", "GROQ", "llama-3.3-70b"
if fournisseur == "gemini":
try:
# On tente Gemini en premier
reponse_finale, nom_modele = interroger_gemini_cascade(full_prompt)
couleur, nom_fournisseur = "#3b82f6", "GOOGLE GEMINI"
completion_tokens = len(reponse_finale.split()) * 2
except Exception as e_gemini:
# 🔄 IA DE SECOURS : Si Gemini échoue, on bascule silencieusement sur Groq
print(f"⚠️ Alerte système : Gemini indisponible ({e_gemini}). Bascule sur l'IA de secours (Groq)...")
intention = intention + " (Secours Groq)"
try:
@@ -487,7 +560,7 @@ async def post(request, session: dict):
else:
try:
ia_response = local_client.chat.completions.create(model="qwen2.5:3b", messages=[{"role": "system", "content": "Réponds dans la langue de l'utilisateur."}, {"role": "user", "content": full_prompt}])
ia_response = local_client.chat.completions.create(model="qwen2.5:3b", messages=[{"role": "system", "content": "Réponds dans la langue de l'utilisateur. Utilise le Markdown."}, {"role": "user", "content": full_prompt}])
reponse_finale, couleur, nom_fournisseur, nom_modele = ia_response.choices[0].message.content, "#10b981", "SERVEUR LOCAL", "qwen2.5:3b"
except Exception as e:
reponse_finale, couleur, nom_fournisseur, nom_modele = f"Erreur Serveur Local : {e}", "#ff0000", "ERREUR LOCAL", "Qwen HS"
@@ -560,5 +633,10 @@ def post(chat_id: str):
save_history(history)
return render_sidebar()
# --- ROUTE : EXPORT JSON COMPLET ---
@rt('/export_json')
def get():
return FileResponse(HISTORY_FILE, media_type='application/json', filename='Aethas38_Discussion_Globale.json')
if __name__ == '__main__':
serve(port=5001)