diff --git a/bot_direct.py b/bot_direct.py index 1e5eca1..468196c 100644 --- a/bot_direct.py +++ b/bot_direct.py @@ -6,10 +6,12 @@ PoC minimal : WebSocket texte → API Hermes → réponse texte. Pas de Pipecat — intégration directe pour valider le concept. """ import asyncio +import base64 import json import os import subprocess import sys +import tempfile import httpx from websockets.asyncio.server import serve from websockets.http11 import Response, Headers @@ -42,6 +44,33 @@ SYSTEM_PROMPT = ( "Pas de markdown. Réponds comme si tu parlais à l'oral." ) +# ─── TTS (Text-to-Speech) ───────────────────────────────────── +EDGE_TTS_VOICE = "fr-FR-DeniseNeural" # Voix féminine française naturelle +EDGE_TTS_BIN = "/home/openclaw/workspace/jarvis-pipecat/venv/bin/edge-tts" + +async def generate_tts(text: str) -> bytes: + """Génère un MP3 à partir du texte avec edge-tts. Retourne les bytes audio.""" + with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f: + mp3_path = f.name + try: + proc = await asyncio.create_subprocess_exec( + EDGE_TTS_BIN, "--voice", EDGE_TTS_VOICE, + "--text", text, "--write-media", mp3_path, + stdout=asyncio.subprocess.DEVNULL, + stderr=asyncio.subprocess.DEVNULL, + ) + await asyncio.wait_for(proc.wait(), timeout=15) + with open(mp3_path, "rb") as f: + return f.read() + except Exception as e: + print(f"⚠️ TTS error: {e}") + return b"" + finally: + try: + os.unlink(mp3_path) + except: + pass + # ─── Page HTML ─────────────────────────────────────────────── HTML_PAGE = """ @@ -71,6 +100,9 @@ button{padding:10px 20px;border-radius:8px;border:none;background:var(--cyan); color:var(--bg);font-weight:bold;cursor:pointer;font-size:.95em} button:hover{opacity:.9} button:disabled{opacity:.5;cursor:default} +#mic{width:40px;height:40px;font-size:1.2em;flex-shrink:0;padding:0;display:flex;align-items:center;justify-content:center;border-radius:50%} +#mic.listening{background:var(--red);animation:pulse 1s infinite} +@keyframes pulse{0%,100%{box-shadow:0 0 0 0 var(--red)}50%{box-shadow:0 0 0 8px transparent}} @@ -78,14 +110,17 @@ button:disabled{opacity:.5;cursor:default}
🔴 Déconnecté
- + +
@@ -177,9 +234,14 @@ async def ws_handler(websocket): messages.append({"role": "assistant", "content": reply}) print(f"🤖 {reply[:80]}") + # Générer TTS en parallèle de l'envoi texte + audio_bytes = await generate_tts(reply) + audio_b64 = base64.b64encode(audio_bytes).decode() if audio_bytes else "" + await websocket.send(json.dumps({ "type": "response", "content": reply, + "audio": audio_b64, })) except Exception as e: