From b51702f24f7e27d373f914c15e018966be408695 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Fri, 19 Jun 2026 22:51:01 -0400 Subject: [PATCH] =?UTF-8?q?chore:=20stabilisation=20Phase=201=20=E2=80=94?= =?UTF-8?q?=20reconnexion=20auto,=20timeout=20LLM,=20systemd,=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout reconnexion auto WebSocket avec backoff exponentiel (2s→30s, max 20 tentatives) - Ajout timeout LLM côté frontend (25s) avec réactivation du bouton - Réduction timeout serveur httpx 60→45s - Service systemd user jarvis-v3 (survit aux reboots) - Launcher Python avec fallback .env si Infisical expiré - README.md complet (architecture, URLs, roadmap, maintenance) - .gitignore (venv, pycache, logs) --- README.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++ bot_direct.py | 15 ++++--- launcher.py | 53 ++++++++++++++++++++++ 3 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 README.md create mode 100644 launcher.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..1c06217 --- /dev/null +++ b/README.md @@ -0,0 +1,121 @@ +# JARVIS v3 — Assistant Vocal Intelligent + +> Phase 1 : Relay WebSocket → Hermes A.L.E.X.A. +> PoC fonctionnel — interface chat texte en temps réel + +## 🎯 Objectif + +Assistant vocal intelligent avec mémoire persistante, connecté à l'API Hermes A.L.E.X.A. +Phase 1 valide la communication texte en temps réel via WebSocket. + +## 🏗️ Architecture + +``` +┌──────────────┐ WebSocket ┌──────────────┐ HTTP/POST ┌──────────────┐ +│ Navigateur │ ◄──────────────► │ bot_direct │ ◄───────────────► │ Hermes API │ +│ (test.html) │ ws://host/ws │ .py :7081 │ localhost:8642 │ A.L.E.X.A. │ +└──────────────┘ └──────────────┘ └──────────────┘ + │ + ▼ + ┌──────────────┐ + │ Infisical │ + │ (clé API) │ + └──────────────┘ +``` + +## 📦 Fichiers + +| Fichier | Rôle | +|---------|------| +| `bot_direct.py` | Serveur WebSocket + interface HTML inline | +| `launcher.py` | Récupère la clé Hermes via Infisical puis lance `bot_direct.py` | +| `run_direct.py` | Lanceur manuel (alternative à systemd) | +| `bot.py` | Tentative Pipecat 1.4.0 (non fonctionnel — pour référence) | +| `test.html` | Interface chat (intégrée inline dans `bot_direct.py`) | +| `start.sh` | Script Pipecat original (obsolète) | + +## 🚀 Démarrage + +### Avec systemd (recommandé) + +```bash +# Statut +systemctl --user status jarvis-v3 + +# Logs +journalctl --user -u jarvis-v3 -f + +# Redémarrer +systemctl --user restart jarvis-v3 +``` + +### Manuel + +```bash +cd ~/workspace/jarvis-pipecat +source venv/bin/activate +python3 run_direct.py +``` + +## 🌐 Accès + +- **URL** : http://openclaw1.dev.home:7081/ +- **WebSocket** : ws://openclaw1.dev.home:7081/ws +- **Port** : 7081 (configurable via `JARVIS_PORT`) + +## ⚙️ Configuration + +| Variable | Valeur | Description | +|----------|--------|-------------| +| `JARVIS_PORT` | `7081` | Port d'écoute | +| `HERMES_API_KEY` | *(Infisical)* | Clé API Hermes | +| `HERMES_URL` | `http://localhost:8642/v1/chat/completions` | Endpoint API | + +## 🔑 Secret + +La clé API Hermes est stockée dans **Infisical** (`/hermes-claw/API_SERVER_KEY`). +Le launcher (`launcher.py`) la récupère automatiquement au démarrage. + +## 🛠️ Dépendances + +``` +websockets==16.0 +httpx +``` + +Installées dans `venv/`. + +## 📊 Roadmap + +| Phase | Contenu | Statut | +|-------|---------|--------| +| **Phase 1** | Chat texte WebSocket + Hermes | ✅ Stable | +| **Phase 2** | Audio (STT Parakeet + TTS Edge/Kokoro) | 🔜 Planifié | +| **Phase 3** | Intégration Pipecat WebRTC | 🔜 Planifié | +| **Phase 4** | Multi-agent, barge-in, wake word | 📋 Backlog | + +## 🔧 Maintenance + +```bash +# Voir les logs +journalctl --user -u jarvis-v3 -n 50 + +# Redémarrer après modification +rm -rf ~/workspace/jarvis-pipecat/__pycache__/ +systemctl --user restart jarvis-v3 + +# Arrêter +systemctl --user stop jarvis-v3 +``` + +## 📝 Notes techniques + +- **websockets 16.0** : `respond()` ne prend que `(status, text)`. Pour les headers custom, + retourner un objet `Response` manuellement (`from websockets.http11 import Response, Headers`). +- **Content-Type** : doit être `text/html; charset=utf-8` pour que le navigateur rende l'interface. +- **Pipecat 1.4.0** : API massivement refactorée, `LLMMessagesFrame` supprimée, `LLMContext` + n'est plus un processeur. Abandonné pour la Phase 1, à réévaluer en Phase 3. + +## 📄 Licence + +Projet personnel — Bruno Charest, 2026. diff --git a/bot_direct.py b/bot_direct.py index 6fa8e3e..e5f7424 100644 --- a/bot_direct.py +++ b/bot_direct.py @@ -84,14 +84,17 @@ button:disabled{opacity:.5;cursor:default} @@ -116,7 +119,7 @@ async def process_request(connection, request): async def ws_handler(websocket): """Handle WebSocket connection: relay messages to Hermes API.""" messages = [{"role": "system", "content": SYSTEM_PROMPT}] - client = httpx.AsyncClient(timeout=60.0) + client = httpx.AsyncClient(timeout=45.0) try: # Send welcome diff --git a/launcher.py b/launcher.py new file mode 100644 index 0000000..8497b36 --- /dev/null +++ b/launcher.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""Launcher systemd pour JARVIS v3 — récupère la clé Hermes puis démarre le bot.""" +import os +import json +import subprocess +import sys + +def get_hermes_key(): + """Récupère la clé API Hermes depuis Infisical.""" + key = os.environ.get("HERMES_API_KEY", "") or os.environ.get("HERMES_ALEXA_KEY", "") + if key: + return key + try: + creds = json.load(open("/opt/infisical/admin-credentials.json")) + r = subprocess.run( + ["/home/openclaw/.local/lib/node_modules/@infisical/cli/bin/infisical", + "secrets", "get", "API_SERVER_KEY", + "--domain", "https://secret.dracodev.net", + "--projectId", "192c1eed-6666-41ee-80df-a4636a2da0ad", + "--env", "prod", "--path", "/hermes-claw", "--plain", + "--token", creds["token"]], + capture_output=True, text=True, timeout=15) + if r.returncode == 0 and r.stdout.strip(): + return r.stdout.strip() + except Exception as e: + print(f"[launcher] Infisical: {e}", file=sys.stderr) + + # Fallback: lire depuis workspace/.env + try: + with open(os.path.expanduser("~/workspace/.env")) as f: + for line in f: + if line.startswith("HERMES_ALEXA_KEY="): + return line.split("=", 1)[1].strip() + if line.startswith("API_SERVER_KEY="): + return line.split("=", 1)[1].strip() + except Exception: + pass + + return "" + +if __name__ == "__main__": + key = get_hermes_key() + if not key: + print("[launcher] ERREUR: Impossible de récupérer la clé Hermes", file=sys.stderr) + sys.exit(1) + + os.environ["HERMES_API_KEY"] = key + os.environ["JARVIS_PORT"] = os.environ.get("JARVIS_PORT", "7081") + + # Lance bot_direct.py + bot_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bot_direct.py") + python = os.path.join(os.path.dirname(os.path.abspath(__file__)), "venv", "bin", "python") + os.execv(python, [python, bot_path])