diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml deleted file mode 100644 index a781fec..0000000 --- a/.gitea/workflows/deploy.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Build and Push Docker Image - -on: - push: - branches: [ main ] - -jobs: - build-and-push: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Log in to Gitea Container Registry - uses: docker/login-action@v2 - with: - registry: gitea.aethas38.duckdns.org - username: ${{ secrets.REGISTRY_USERNAME }} - password: ${{ secrets.REGISTRY_TOKEN }} - - - name: Build and push Docker image - uses: docker/build-push-action@v4 - with: - context: . - push: true - tags: gitea.aethas38.duckdns.org/xavier/multi-ia-codage:latest \ No newline at end of file diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 7b2233b..0000000 --- a/.gitignore +++ /dev/null @@ -1,54 +0,0 @@ -__pycache__/ -venv/ -.env -.sesskey -aethas_history.json -Echange GEMINY.txt -.vscode/ -aethas_logs.txt -Important/ -ARCHIVAGE ECHANGE GEMINY.txt -test financier/ -SAVE/ -Fichiers de configuration sensibles -.env -*.env -.env.local -Environnements virtuels Python -venv/ -.venv/ -env/ -ENV/ -Fichiers compilés Python -pycache/ -*.py[cod] -*$py.class -Fichiers de logs -*.log -Dossiers d'IDE -.vscode/ -.idea/ -*.swp -*.swo -Historique local de l'orchestrateur (si vous sauvegardez les conversations en JSON) -historique_*.json -sessions/ -Projet IA.pdf -Multi IA Personnel & familliale.pdf -WF ATTACHEMENT STIC.pdf -gitea cmd.md -consignes.txt -history.json -# Environnement et clés secrètes -.env - -# Dossiers dynamiques d'exécution -/logs/ -/sessions/ - -# Fichiers de données locales et clés de session -history.json -*.sesskey -__pycache__/ -*.pyc -app_config.json diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index adee702..0000000 --- a/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM python:3.13-slim - -WORKDIR /app - -# Installation des dépendances système (LibreOffice, etc.) -RUN apt-get update && apt-get install -y \ - libreoffice \ - curl \ - && rm -rf /var/lib/apt/lists/* - -# Copie et installation des dépendances Python -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -# Copie de tout le code source de la racine -COPY . . - -EXPOSE 5001 - -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "5001"] \ No newline at end of file diff --git a/backend/ai_engine.py b/backend/ai_engine.py deleted file mode 100644 index dfdc401..0000000 --- a/backend/ai_engine.py +++ /dev/null @@ -1,39 +0,0 @@ -# ai_engine.py -import os -import requests -import asyncio -from datetime import datetime - -os.makedirs("logs", exist_ok=True) -APP_LAUNCH_TIME = datetime.now().strftime("%Y%m%d_%H%M%S") -LOG_FILE = f"logs/AETHAS38_Run_{APP_LAUNCH_TIME}.log" - -def log_event(session_id, category, action): - sid_short = session_id[:8] if session_id and session_id != "system" else "SYSTEM" - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - with open(LOG_FILE, "a", encoding="utf-8") as f: - f.write(f"[{timestamp}] [Session:{sid_short}] [{category}] {action}\n")[cite: 11] - -def ask_llm(session_id, full_id, msg): - parts = full_id.split("|", 1) - provider, actual_model = parts[0], parts[1] if len(parts) > 1 else full_id - - log_event(session_id, "API_REQ", f"Provider: {provider} | Modèle: {actual_model}")[cite: 11] - try: - if provider == "openrouter": - res = requests.post("https://openrouter.ai/api/v1/chat/completions", headers={"Authorization": f"Bearer {os.getenv('OPENROUTER_API_KEY')}"}, json={"model": actual_model, "messages": [{"role": "user", "content": msg}]}) - if res.status_code == 200: return res.json()["choices"][0]["message"]["content"] - return f"⚠️ Erreur API ({provider}) : {res.text}"[cite: 11] - elif provider == "groq": - res = requests.post("https://api.groq.com/openai/v1/chat/completions", headers={"Authorization": f"Bearer {os.getenv('GROQ_API_KEY')}"}, json={"model": actual_model, "messages": [{"role": "user", "content": msg}]}) - if res.status_code == 200: return res.json()["choices"][0]["message"]["content"] - return f"⚠️ Erreur API ({provider}) : {res.text}"[cite: 11] - elif provider == "gemini": - res = requests.post(f"https://generativelanguage.googleapis.com/v1beta/models/{actual_model}:generateContent?key={os.getenv('GEMINI_API_KEY')}", json={"contents": [{"parts": [{"text": msg}]}]}) - if res.status_code == 200: return res.json()["candidates"][0]["content"]["parts"][0]["text"] - return f"⚠️ Erreur API ({provider}) : {res.text}"[cite: 11] - return "⚠️ Fournisseur API non implémenté ou ID invalide[cite: 11]." - except Exception as e: return f"⚠ Erreur de connexion : {str(e)}"[cite: 11] - -async def async_ask_llm(session_id, full_id, msg): - return await asyncio.to_thread(ask_llm, session_id, full_id, msg)[cite: 11] \ No newline at end of file diff --git a/backend/auth.py b/backend/auth.py deleted file mode 100644 index 00c4c84..0000000 --- a/backend/auth.py +++ /dev/null @@ -1,56 +0,0 @@ -# auth.py -import re -import pyotp -import hashlib -import hmac -import os -import smtplib -from email.mime.text import MIMEText -from email.mime.multipart import MIMEMultipart - -def validate_password_strength(password): - if len(password) < 12: return "Le mot de passe doit contenir au moins 12 caractères." - if not re.search(r"[A-Z]", password): return "Le mot de passe doit contenir au moins une majuscule." - if not re.search(r"[a-z]", password): return "Le mot de passe doit contenir au moins une minuscule." - if not re.search(r"[0-9]", password): return "Le mot de passe doit contenir au moins un chiffre." - if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password): return "Le mot de passe doit contenir au moins un caractère spécial." - return None - -def hash_password(password): - return hashlib.sha256(password.encode()).hexdigest() - -def verify_password(stored_hash, provided_password): - return hmac.compare_digest(stored_hash, hash_password(provided_password)) - -def generate_totp_secret(): - return pyotp.random_base32() - -def verify_totp(secret, code): - totp = pyotp.TOTP(secret) - return totp.verify(code) - -def send_invite_email(to_email, setup_link): - smtp_server = os.getenv("SMTP_SERVER") - smtp_port = int(os.getenv("SMTP_PORT", 587)) - smtp_user = os.getenv("SMTP_USER") - smtp_pass = os.getenv("SMTP_PASS") - - if not all([smtp_server, smtp_user, smtp_pass]): - return False - - msg = MIMEMultipart() - msg['From'] = f"AETHAS38 <{smtp_user}>" - msg['To'] = to_email - msg['Subject'] = "Invitation au Hub AETHAS38" - body = f"Vous avez été invité(e) à rejoindre le hub IA AETHAS38.\nLien de configuration 2FA : {setup_link}" - msg.attach(MIMEText(body, 'plain', 'utf-8')) - - try: - server = smtplib.SMTP(smtp_server, smtp_port) - server.starttls() - server.login(smtp_user, smtp_pass) - server.send_message(msg) - server.quit() - return True - except Exception: - return False \ No newline at end of file diff --git a/backend/catalog_manager.py b/backend/catalog_manager.py deleted file mode 100644 index f4c2edf..0000000 --- a/backend/catalog_manager.py +++ /dev/null @@ -1,57 +0,0 @@ -# catalog_manager.py -import os -import json -import re -import requests - -CATALOG_FILE = "sessions/catalog.json" -MODELS_DATA = [] - -def load_catalog(): - global MODELS_DATA - if os.path.exists(CATALOG_FILE): - with open(CATALOG_FILE, "r", encoding="utf-8") as f: - try: MODELS_DATA = json.load(f) - except: MODELS_DATA = [] - if not MODELS_DATA: - MODELS_DATA = [ - {"id": "groq|llama-3.1-8b-instant", "name": "Groq - Llama 3.1 (8B)", "is_free": True, "provider": "Général / Texte", "description": "Modèle par défaut."}, - {"id": "gemini|gemini-1.5-flash", "name": "Google - Gemini 1.5 Flash", "is_free": True, "provider": "Vision", "description": "Modèle par défaut."} - ] -load_catalog() - -def save_catalog(data): - global MODELS_DATA - MODELS_DATA = data - with open(CATALOG_FILE, "w", encoding="utf-8") as f: - json.dump(data, f, indent=4, ensure_ascii=False)[cite: 11] - -def get_budget(): - mgmt_key = os.getenv("OPENROUTER_MANAGEMENT_KEY") - if not mgmt_key: return "Clé manquante[cite: 11]" - try: - data = requests.get("https://openrouter.ai/api/v1/credits", headers={"Authorization": f"Bearer {mgmt_key}"}, timeout=5).json().get("data", {}) - return f"{data.get('total_credits', 0) - data.get('total_usage', 0):.4f} $"[cite: 11] - except: return "Erreur réseau[cite: 11]" - -def parse_txt_catalog(content: str): - lines = content.split('\n') - models = [] - current = {} - ext_date, ext_time = "Inconnue", "" - - for line in lines: - line_trim = line.strip() - if "Généré le" in line_trim: - match = re.search(r"Généré le (\d{2}/\d{2}/\d{4}) à (\d{2}:\d{2}:\d{2})", line_trim) - if match: - ext_date, ext_time = match.group(1), match.group(2) - if line_trim.startswith("Nom : "): current['name'] = line_trim.replace("Nom : ", "").strip() - elif line_trim.startswith("ID : "): current['id'] = line_trim.replace("ID : ", "").strip() - elif line_trim.startswith("Tarif : "): current['is_free'] = ("Gratuit" in line_trim) - elif line_trim.startswith("Spécialité : "): current['provider'] = line_trim.replace("Spécialité : ", "").strip() - elif line_trim.startswith("Description : "): current['description'] = line_trim.replace("Description : ", "").strip() - elif line_trim.startswith("---"): - if 'id' in current and 'name' in current: models.append(current) - current = {} - return models, ext_date, ext_time[cite: 11] \ No newline at end of file diff --git a/backend/config.py b/backend/config.py deleted file mode 100644 index ab4c8fa..0000000 --- a/backend/config.py +++ /dev/null @@ -1,25 +0,0 @@ -import json -import os - -# Détecte le dossier parent (racine du projet) dynamiquement -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -SESSIONS_DIR = os.path.join(BASE_DIR, "sessions") -CONFIG_FILE = os.path.join(SESSIONS_DIR, "app_config.json") - -def load_config(): - os.makedirs(SESSIONS_DIR, exist_ok=True) - if not os.path.exists(CONFIG_FILE): - default_config = { - "api_keys": {"openrouter": "", "groq": "", "gemini": ""}, - "smtp": {"host": "", "port": 587, "user": "", "password": ""}, - "custom_providers": [] - } - save_config(default_config) - return default_config - with open(CONFIG_FILE, "r", encoding="utf-8") as f: - return json.load(f) - -def save_config(config_data): - os.makedirs(SESSIONS_DIR, exist_ok=True) - with open(CONFIG_FILE, "w", encoding="utf-8") as f: - json.dump(config_data, f, indent=4, ensure_ascii=False) \ No newline at end of file diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml deleted file mode 100644 index ba5e265..0000000 --- a/backend/docker-compose.yml +++ /dev/null @@ -1,24 +0,0 @@ -services: - aethas38-core: - build: . - container_name: aethas38-multi-ia - restart: unless-stopped - ports: - - "5002:5001" - environment: - - TZ=Europe/Paris - env_file: - - .env - extra_hosts: - - host.docker.internal:host-gateway - command: uvicorn main:app --host 0.0.0.0 --port 5001 --reload - volumes: - - /mnt/stockage_raid/AppData/MULTI-IA-AETHAS38/sessions:/app/sessions - - /mnt/stockage_raid/AppData/MULTI-IA-AETHAS38/logs:/app/logs - - /mnt/stockage_raid/AppData/MULTI-IA-AETHAS38/history.json:/app/history.json - networks: - - dockge-network - -networks: - dockge-network: - external: true \ No newline at end of file diff --git a/backend/history_manager.py b/backend/history_manager.py deleted file mode 100644 index a4718e3..0000000 --- a/backend/history_manager.py +++ /dev/null @@ -1,36 +0,0 @@ -# history_manager.py -import os -import json -import uuid -from datetime import datetime - -HISTORY_FILE = "sessions/history.json" - -def load_index(): - if not os.path.exists(HISTORY_FILE): return {} - with open(HISTORY_FILE, "r", encoding="utf-8") as f: - try: return json.load(f) - except: return {}[cite: 11] - -def save_index(idx): - with open(HISTORY_FILE, "w", encoding="utf-8") as f: - json.dump(idx, f, indent=4, ensure_ascii=False)[cite: 11] - -def load_session_data(session_id): - path = f"sessions/{session_id}.json" - if not os.path.exists(path): return [] - with open(path, "r", encoding="utf-8") as f: - try: return json.load(f) - except: return [][cite: 11] - -def save_session_data(session_id, messages): - with open(f"sessions/{session_id}.json", "w", encoding="utf-8") as f: - json.dump(messages, f, indent=4, ensure_ascii=False)[cite: 11] - -def create_new_session(): - session_id = str(uuid.uuid4()) - idx = load_index() - idx[session_id] = {"title": f"Projet du {datetime.now().strftime('%d/%m %H:%M')}", "pinned": False} - save_index(idx) - save_session_data(session_id, []) - return session_id[cite: 11] \ No newline at end of file diff --git a/backend/main.py b/backend/main.py deleted file mode 100644 index f742235..0000000 --- a/backend/main.py +++ /dev/null @@ -1,207 +0,0 @@ -# main.py -from fasthtml.common import * -import os -import time -import requests -from dotenv import load_dotenv - -from auth import verify_password, verify_totp -from catalog_manager import MODELS_DATA, load_catalog, save_catalog, get_budget, parse_txt_catalog -from history_manager import load_index, save_index, load_session_data, save_session_data, create_new_session -from ai_engine import ask_llm, async_ask_llm, log_event - -load_dotenv() - -theme_hdrs = [ - Script(src="https://cdn.tailwindcss.com"), - Script(src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"), - Script(src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"), - Link(rel="stylesheet", href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/tokyo-night-dark.min.css"), - Style(""" - body { background-color: #0f172a; color: #f8fafc; font-family: system-ui, sans-serif; margin: 0;} - .sidebar { background-color: #1e293b; border-right: 1px solid #333; height: 100vh; padding: 20px; display: flex; flex-direction: column;} - .main-content { padding: 20px; height: 100vh; display: flex; flex-direction: column; position: relative;} - .chat-container { flex-grow: 1; overflow-y: auto; margin-bottom: 20px; border: 1px solid #333; padding: 15px; border-radius: 8px; background: #151e2e; } - .input-row { display: flex; gap: 10px; align-items: flex-end; width: 100%; background: #1e293b; padding: 10px; border-radius: 10px; border: 1px solid #333;} - .input-box { flex-grow: 1; padding: 12px; border-radius: 5px; background: #2a2a35; border: 1px solid #444; color: white; outline: none; } - .file-upload-btn { background: #2a2a35; border: 1px solid #444; color: #94a3b8; padding: 12px; border-radius: 5px; cursor: pointer; font-size:14px; display: flex; align-items:center; justify-content:center;} - .model-selector { padding: 8px; border-radius: 5px; background: #1e293b; border: 1px solid #444; color: #00e5ff; width: 100%; font-size: 12px; outline:none; } - .send-btn { padding: 12px 24px; border-radius: 5px; background: #00e5ff; color: #0f172a; font-weight: bold; cursor: pointer; border: none; } - .msg-user { color: #00e5ff; font-size: 12px; font-weight: bold; margin-bottom: 2px; margin-top: 15px;} - .msg-ia { color: #a855f7; font-size: 12px; font-weight: bold; margin-bottom: 2px; margin-top: 15px;} - .bubble-user { background: #1e293b; padding: 10px; border-radius: 5px; display: inline-block; color: white; border: 1px solid #333;} - .bubble-ia { background: #1e293b; padding: 15px; border-radius: 8px; display: block; color: #e2e8f0; border: 1px solid #333;} - .htmx-indicator { display: none; position: absolute; bottom: 180px; left: 50%; transform: translateX(-50%); background: rgba(15, 23, 42, 0.9); border: 1px solid #00e5ff; padding: 15px 30px; border-radius: 50px; color: #00e5ff; font-weight: bold; z-index: 50;} - .htmx-request .htmx-indicator { display: block; } - .export-btn { display: block; background: #0f172a; border: 1px solid #00e5ff; color: #00e5ff; font-weight: bold; padding: 10px; border-radius: 5px; text-align: center; cursor: pointer; width: 100%;} - """), - Script(""" - function filterModels() { - let filter = document.querySelector('input[name="filter_type"]:checked').value; - document.querySelectorAll('.model-selector option').forEach(opt => { - if(opt.value === "") return; - let isFree = opt.getAttribute('data-free') === 'true'; - if(filter === 'free' && !isFree) opt.style.display = 'none'; - else if(filter === 'paid' && isFree) opt.style.display = 'none'; - else opt.style.display = 'block'; - }); - } - function validateMultiIA() { - let wCount = 0; - for(let i=1; i<=2; i++) { if(document.getElementById('worker'+i).value !== "") wCount++; } - if(wCount === 0) { alert("Erreur : Sélectionnez au moins une IA de travail."); return false; } - return true; - } - function updateFileCount(input) { - let label = document.getElementById('file-label-text'); - if(input.files && input.files.length > 0) { label.innerText = `📎 ${input.files.length} fichier(s)`; label.style.color = '#00e5ff'; } - } - """) -] - -app, rt = fast_app(secret_key=os.getenv("SESSION_SECRET", "super-secret-key-fallback"), hdrs=theme_hdrs) - -os.makedirs("sessions", exist_ok=True) - -@rt('/login') -def get(): - return Title("Connexion - AETHAS38"), Body( - Div( - H2("AETHAS 38 - Accès Sécurisé", style="color:#00e5ff; margin-bottom:20px; text-align:center;"), - Form( - Input(type="email", name="email", placeholder="E-mail", cls="input-box mb-3 w-full", required=True), - Input(type="password", name="password", placeholder="Mot de passe", cls="input-box mb-3 w-full", required=True), - Input(type="text", name="totp_code", placeholder="Code 2FA (6 chiffres)", cls="input-box mb-4 w-full", required=True), - Button("Se connecter", type="submit", cls="send-btn w-full"), - hx_post="/auth/login", hx_target="#auth-response" - ), - Div(id="auth-response", style="color:red; margin-top:10px; text-align:center;"), - cls="sidebar", style="margin: auto; width: 400px; height: auto; border-radius: 10px; margin-top: 15vh;" - ), style="background-color: #0f172a; height: 100vh; display: flex;" - ) - -@rt('/auth/login') -def post(email: str, password: str, totp_code: str, session): - if email == os.getenv("ADMIN_EMAIL") and verify_password(os.getenv("ADMIN_PASSWORD_HASH"), password) and verify_totp(os.getenv("ADMIN_2FA_SECRET"), totp_code): - session["authenticated"] = True - session["role"] = "admin" - return Script("window.location.href = '/';") - return Div("Identifiants ou code 2FA incorrects.", style="color:#ef4444; font-weight:bold;") - -@rt('/logout') -def get(session): - session.clear() - return RedirectResponse('/login') - -@rt('/check_updates') -def get(session): - if not session.get("authenticated"): return Span("") - try: - res = requests.get("https://gitea.aethas38.duckdns.org/api/v1/repos/xavier/MULTI-IA-CODAGE/commits?limit=1", timeout=3) - if res.status_code == 200: - return Span(f"🟢 Gitea Connecté ({res.json()[0].get('sha', '')[:7]})", style="color:#10b981;") - except: pass - return Span("🟠 Gitea hors ligne", style="color:#fbbf24;") - -def render_history_list(): - idx = load_index() - items = [Button("➕ Nouveau Projet", cls="export-btn mb-3", style="background:#10b981; color:#0f172a;", hx_get="/session/new", hx_target="#chat-history")] - for sid, data in idx.items(): - items.append(Div(Div(data['title'], cls="truncate flex-grow cursor-pointer text-sm hover:text-cyan-400", hx_get=f"/session/load/{sid}", hx_target="#chat-history"), cls="flex justify-between items-center w-full mb-2 bg-[#2a2a35] p-2 rounded border border-[#444]")) - return Div(*items, id="history-list-container", hx_swap_oob="true") - -def render_model_dropdown(field_id, placeholder): - groups = {} - for m in MODELS_DATA: - prov = m.get('provider', 'Général') - if prov not in groups: groups[prov] = [] - groups[prov].append(m) - opts = [Option("--- Laisser vide ---", value="")] - for prov in sorted(groups.keys()): - grp_opts = [Option(f"{m['name']} ({'Gratuit' if str(m.get('is_free'))=='True' else 'Payant'})", value=m['id'], **{"data-free": str(m.get('is_free', False)).lower()}) for m in groups[prov]] - opts.append(Optgroup(label=prov)(*grp_opts)) - return Div(P(placeholder, style="color:#94a3b8; font-size:10px; font-weight:bold; margin-bottom:2px;"), Select(*opts, name=field_id, id=field_id, cls="model-selector", onchange="filterModels()"), style="margin-bottom: 8px;") - -SESSION_PROGRESS = {} - -@rt('/status/{sid}') -def get_status(sid: str): - return SESSION_PROGRESS.get(sid, "⚙️ Traitement en cours...") - -@rt('/') -def get(session): - if not session.get("authenticated"): return RedirectResponse('/login') - session_id = create_new_session() - return Title("AETHAS38 Orchestrateur"), Body( - Div( - Div( - H2("AETHAS 38", style="color:#00e5ff; font-weight:bold; font-size:24px;"), - Div("⏳ Vérification GitHub...", hx_get="/check_updates", hx_trigger="load", style="font-size:10px; margin-bottom:15px;"), - Div(H3("Historique", style="color:#94a3b8; font-size:12px; font-weight:bold; margin-bottom:10px;"), render_history_list(), style="flex-grow:1; overflow:hidden;"), - Button("📥 Exporter Modèles", type="button", cls="export-btn mb-2", onclick="window.location.href='/export_models'"), - A("Déconnexion", href="/logout", cls="export-btn text-center", style="color:#ef4444; border-color:#ef4444;"), - Div(P("BUDGET", style="color:#94a3b8; font-size:10px; font-weight:bold; margin-top:8px;"), P(get_budget(), id="budget-display", style="color:#10b981; font-size:16px; font-weight:bold;")), - cls="sidebar" - ), - Div( - Div(id="chat-history", cls="chat-container"), - Div(Span("⚙️ Préparation...", id="loading-text"), id="loading-tracker", cls="htmx-indicator"), - Form( - Input(type="hidden", name="session_id", value=session_id, id="current-session-id"), - Div( - Div(render_model_dropdown("writer", "1. Rédacteur"), render_model_dropdown("synthesizer", "7. Concaténeur"), style="border-right: 1px solid #444; padding-right:15px;"), - Div(render_model_dropdown("worker1", "2. Travailleur 1"), render_model_dropdown("worker2", "3. Travailleur 2"), style="padding: 0 15px;"), - cls="grid grid-cols-2 gap-4 mb-4 bg-[#151e2e] p-4 rounded border border-[#333]" - ), - Div( - Input(type="file", name="fichiers", id="file-upload", multiple=True, style="display:none;", onchange="updateFileCount(this)"), - Label(Span("📎 Fichiers", id="file-label-text"), _for="file-upload", cls="file-upload-btn w-40"), - Input(type="text", name="msg", placeholder="Votre consigne...", cls="input-box", required=True), - Button("Lancer", type="submit", cls="send-btn"), - cls="input-row" - ), - hx_post="/chat", hx_target="#chat-history", hx_swap="beforeend", hx_indicator="#loading-tracker", enctype="multipart/form-data", - onsubmit="return validateMultiIA();" - ), - cls="main-content" - ), - style="display: grid; grid-template-columns: 280px 1fr;" - ) - ) - -@rt('/chat') -async def post(msg: str, session_id: str, session, writer: str = "", worker1: str = "", worker2: str = "", synthesizer: str = "", fichiers: list[UploadFile] = None): - if not session.get("authenticated"): return RedirectResponse('/login') - workers = [w for w in [worker1, worker2] if w] - - SESSION_PROGRESS[session_id] = "⚡ Traitement des IA en cours..." - tasks = [async_ask_llm(session_id, w, msg) for w in workers] - results = await asyncio.gather(*tasks) - final_response = results[0] if results else "Aucun travailleur configuré." - - session_data = load_session_data(session_id) - session_data.append({"user": msg, "ia": final_response}) - save_session_data(session_id, session_data) - - return Div(P("Vous", cls="msg-user"), Div(msg, cls="bubble-user"), P("AETHAS38", cls="msg-ia"), Div(final_response, cls="bubble-ia")), P(get_budget(), id="budget-display", style="color:#10b981; font-size:16px; font-weight:bold;", hx_swap_oob="true") - -@rt('/export_models') -def get(session): - if not session.get("authenticated"): return RedirectResponse('/login') - return Response("=== CATALOGUE ===\nID: groq|llama-3.1-8b-instant\nTarif: Gratuit", media_type="text/plain", headers={"Content-Disposition": "attachment; filename=modeles.txt"}) - -@rt('/session/new') -def get(session): - if not session.get("authenticated"): return RedirectResponse('/login') - sid = create_new_session() - return Div(), Input(type="hidden", name="session_id", value=sid, id="current-session-id", hx_swap_oob="true"), render_history_list() - -@rt('/session/load/{sid}') -def get(sid: str, session): - if not session.get("authenticated"): return RedirectResponse('/login') - messages = load_session_data(sid) - return tuple(Div(P("Vous", cls="msg-user"), Div(m['user'], cls="bubble-user"), P("AETHAS38", cls="msg-ia"), Div(m['ia'], cls="bubble-ia")) for m in messages) + (Input(type="hidden", name="session_id", value=sid, id="current-session-id", hx_swap_oob="true"),) - -if __name__ == '__main__': - import uvicorn - uvicorn.run(app, host="0.0.0.0", port=5001) \ No newline at end of file diff --git a/backend/setup.py b/backend/setup.py deleted file mode 100644 index 766ac50..0000000 --- a/backend/setup.py +++ /dev/null @@ -1,148 +0,0 @@ -import os -import platform -import re -import pyotp -import qrcode -import psutil -from dotenv import load_dotenv - -def clear_screen(): - os.system('cls' if platform.system() == 'Windows' else 'clear') - -def check_hardware(): - print("=" * 60) - print(" 🖥️ ANALYSE DU MATÉRIEL ET LOGICIEL DU SERVEUR") - print("=" * 60) - - cpu_count = psutil.cpu_count(logical=True) - ram_gb = round(psutil.virtual_memory().total / (1024**3), 2) - os_info = f"{platform.system()} {platform.release()}" - - print(f"• Système d'exploitation : {os_info}") - print(f"• Cœurs CPU logiques : {cpu_count}") - print(f"• Mémoire RAM totale : {ram_gb} Go") - - # Évaluation pour phuzzy/minecraft - print("\n[Vérification du modèle lourd : phuzzy/minecraft]") - if ram_gb < 16: - print("⚠️ AVERTISSEMENT CRITIQUE : Votre serveur dispose de moins de 16 Go de RAM/VRAM.") - print(" Le modèle 'phuzzy/minecraft' requiert des ressources importantes.") - print(" Risque de ralentissements sévères ou de plantage par manque de mémoire (OOM).") - else: - print("✅ La configuration matérielle semble suffisante pour exécuter les modèles locaux (Qwen & Phuzzy).") - print("-" * 60 + "\n") - -def validate_password(password): - if len(password) < 12: - return "Le mot de passe doit contenir au moins 12 caractères." - if not re.search(r"[A-Z]", password): - return "Le mot de passe doit contenir au moins une majuscule." - if not re.search(r"[a-z]", password): - return "Le mot de passe doit contenir au moins une minuscule." - if not re.search(r"[0-9]", password): - return "Le mot de passe doit contenir au moins un chiffre." - if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password): - return "Le mot de passe doit contenir au moins un caractère spécial." - return None - -def configure_admin(): - print("=" * 60) - print(" 🔑 CRÉATION DU COMPTE ADMINISTRATEUR & SÉCURITÉ 2FA") - print("=" * 60) - - email = input("Entrez l'e-mail administrateur : ").strip() - - while True: - password = input("Définissez le mot de passe (min. 12 caractères, majuscules, minuscules, chiffres, symboles) : ").strip() - err = validate_password(password) - if err: - print(f"❌ Erreur : {err}") - else: - print("✅ Mot de passe valide.") - break - - # Génération 2FA (TOTP) - totp_secret = pyotp.random_base32() - totp_uri = pyotp.totp.TOTP(totp_secret).provisioning_uri(name=email, issuer_name="AETHAS38-Hub") - - print("\n--- DOUBLE AUTHENTIFICATION (2FA) ---") - print("Scannez le QR code ci-dessous avec votre application (Google Authenticator, KeePassXC, etc.) :") - - # Génération d'un QR code dans le terminal en texte ASCII - qr = qrcode.QRCode() - qr.add_data(totp_uri) - qr.make(fit=True) - qr.print_ascii(invert=True) - - print(nis := f"Clé secrète manuelle (si besoin) : {totp_secret}") - print("-" * 60 + "\n") - return email, password, totp_secret - -def configure_api_keys(): - print("=" * 60) - print(" 🌐 CONFIGURATION DES CLÉS API (PAR SPÉCIALITÉ)") - print("=" * 60) - print("Laissez vide si vous ne possédez pas la clé pour l'instant.\n") - - keys = {} - - print("1. [CODAGE & RAISONNEMENT] - OpenRouter & Management Key") - print(" -> Où la trouver : https://openrouter.ai/settings/keys") - keys["OPENROUTER_API_KEY"] = input(" Entrez OPENROUTER_API_KEY : ").strip() - keys["OPENROUTER_MANAGEMENT_KEY"] = input(" Entrez OPENROUTER_MANAGEMENT_KEY (pour le suivi du budget) : ").strip() - - print("\n2. [CODAGE ULTRA-RAPIDE] - Groq") - print(" -> Où la trouver : https://console.groq.com/keys") - keys["GROQ_API_KEY"] = input(" Entrez GROQ_API_KEY : ").strip() - - print("\n3. [MULTIMODAL & CODE] - Google Gemini") - print(" -> Où la trouver : https://aistudio.google.com/app/apikey") - keys["GEMINI_API_KEY"] = input(" Entrez GEMINI_API_KEY : ").strip() - - print("\n4. [CODAGE AVANCÉ] - DeepSeek") - print(" -> Où la trouver : https://platform.deepseek.com/api_keys") - keys["DEEPSEEK_API_KEY"] = input(" Entrez DEEPSEEK_API_KEY : ").strip() - - print("\n5. [TEXTE & GÉNÉRAL] - Mistral AI") - print(" -> Où la trouver : https://console.mistral.ai/api-keys/") - keys["MISTRAL_API_KEY"] = input(" Entrez MISTRAL_API_KEY : ").strip() - - print("\n6. [INFRASTRUCTURE & IA EDGE] - Cloudflare") - print(" -> Où la trouver : Tableau de bord Cloudflare (Workers & Pages > AI)") - keys["CLOUDFLARE_ACCOUNT_ID"] = input(" Entrez CLOUDFLARE_ACCOUNT_ID : ").strip() - keys["CLOUDFLARE_API_TOKEN"] = input(" Entrez CLOUDFLARE_API_TOKEN : ").strip() - - print("\n7. [MODÈLES LIBRES] - HuggingFace") - print(" -> Où la trouver : https://huggingface.co/settings/tokens") - keys["HUGGINGFACE_API_KEY"] = input(" Entrez HUGGINGFACE_API_KEY : ").strip() - - print("-" * 60 + "\n") - return keys - -def save_env(keys, admin_data): - env_content = f"""# ========================================== -# AETHAS38 - CONFIGURATION AUTOMATISÉE -# ========================================== - -# Administrateur principal -ADMIN_EMAIL={admin_data[0]} -ADMIN_PASSWORD_HASH={admin_data[1]} -ADMIN_2FA_SECRET={admin_data[2]} - -# Clés API Distantes -""" - for k, v in keys.items(): - env_content += f"{k}={v}\n" - - with open(".env", "w", encoding="utf-8") as f: - f.write(env_content) - print("✅ Fichier .env généré avec succès !") - -if __name__ == "__main__": - clear_screen() - check_hardware() - admin_data = configure_admin() - keys = configure_api_keys() - save_env(keys, admin_data) - print("\n🎉 Installation et configuration initiale terminées avec succès !") - print("Vous pouvez maintenant lancer le service Dockge / Docker.") \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html deleted file mode 100644 index df4139e..0000000 --- a/frontend/index.html +++ /dev/null @@ -1,311 +0,0 @@ - - -
- - -{{ m.description }}
-