import os # Création du dossier de rangement os.makedirs("modules", exist_ok=True) # 1. database.py with open("modules/database.py", "w", encoding="utf-8") as f: f.write('''import os import json import time HISTORY_FILE = "aethas_history.json" def load_history(): if not os.path.exists(HISTORY_FILE): with open(HISTORY_FILE, 'w', encoding='utf-8') as f: json.dump({}, f) with open(HISTORY_FILE, 'r', encoding='utf-8') as f: return json.load(f) def save_history(history): with open(HISTORY_FILE, 'w', encoding='utf-8') as f: json.dump(history, f, indent=4) def nettoyer_vieilles_discussions(): history = load_history() now = time.time() un_an_en_secondes = 365 * 24 * 60 * 60 a_supprimer = [cid for cid, data in history.items() if not data.get('pinned', False) and now - data.get('timestamp', now) > un_an_en_secondes] if a_supprimer: for cid in a_supprimer: del history[cid] save_history(history) print(f"🧹 AETHAS CORE : Nettoyage auto terminé. {len(a_supprimer)} discussion(s) supprimée(s).") ''') # 2. scraper.py with open("modules/scraper.py", "w", encoding="utf-8") as f: f.write('''import requests from bs4 import BeautifulSoup def rechercher_sur_google(requete): try: url = "https://lite.duckduckgo.com/lite/" data = {'q': requete} headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"} resp = requests.post(url, data=data, headers=headers, timeout=5) soup = BeautifulSoup(resp.text, 'html.parser') urls = [] for a in soup.find_all('a', class_='result-link', href=True): href = a['href'] if href.startswith('http') and 'duckduckgo' not in href: urls.append(href) if len(urls) >= 5: break if not urls: for a in soup.find_all('a', href=True): href = a['href'] if href.startswith('http') and 'duckduckgo' not in href and href not in urls: urls.append(href) if len(urls) >= 5: break return urls[:5] except Exception as e: print(f"Erreur recherche web: {e}") return [] def extraire_contenu_urls(urls_list): scraped_text = "" for url in urls_list: try: resp = requests.get(url, timeout=5, headers={"User-Agent": "Mozilla/5.0"}) soup = BeautifulSoup(resp.text, 'html.parser') for script in soup(["script", "style", "nav", "footer"]): script.extract() scraped_text += f"\\n\\n--- Source web extraite ({url}) ---\\n{soup.get_text(separator=' ', strip=True)[:3000]}" except Exception as e: scraped_text += f"\\n\\n[Impossible de récupérer {url}: {e}]" return scraped_text ''') # 3. ai_engine.py (Contient les clients et l'accès OpenAI) with open("modules/ai_engine.py", "w", encoding="utf-8") as f: f.write('''import os from dotenv import load_dotenv from openai import OpenAI from groq import Groq from google import genai load_dotenv() local_client = OpenAI(base_url="http://192.168.1.20:11434/v1", api_key="ollama") groq_client = Groq(api_key=os.getenv("GROQ_API_KEY")) gemini_client = genai.Client(api_key=os.getenv("GEMINI_API_KEY")) 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: 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 raise Exception("Tous les modèles Gemini sont indisponibles actuellement.") 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. 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" ''') # 4. router.py with open("modules/router.py", "w", encoding="utf-8") as f: f.write('''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"} ''') # 5. ui_components.py (Gestion de l'affichage UI et sidebar) with open("modules/ui_components.py", "w", encoding="utf-8") as f: f.write('''from fasthtml.common import * from modules.database import load_history 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): 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;"), 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") ''') print("📁 Dossier 'modules/' et sous-scripts générés avec succès !")