#!/usr/bin/env python3 """Apply fail-closed Atlas voice integration patches to pinned Hermes WebUI.""" from pathlib import Path ROOT = Path("/opt/hermes-webui") def replace_exact(path: Path, before: str, after: str, count: int = 1) -> None: """Replace an exact upstream fragment and fail when the pin has drifted.""" source = path.read_text(encoding="utf-8") if source.count(before) != count: raise SystemExit(f"Atlas voice patch context changed in {path}: {before[:80]!r}") path.write_text(source.replace(before, after, count), encoding="utf-8") index = ROOT / "static/index.html" replace_exact( index, '', '', ) replace_exact( index, '', '\n', ) ui = ROOT / "static/ui.js" replace_exact(ui, "function _playEdgeTtsChunked(text, btn){", "function _playEdgeTtsChunked(text, btn, engineOverride){") replace_exact( ui, "body:JSON.stringify({text:chunk, voice:voice, rate:rate, pitch:pitch})", "body:JSON.stringify({text:chunk, voice:voice, rate:rate, pitch:pitch, engine:engineOverride||'edge'})", ) replace_exact( ui, "if(engine==='edge'){\n _playEdgeTtsChunked(clean, btn);", "if(engine==='edge'||engine==='atlas'){\n _playEdgeTtsChunked(clean, btn, engine);", ) replace_exact( ui, "if(engine==='edge'){\n _playEdgeTtsChunked(clean, null);", "if(engine==='edge'||engine==='atlas'){\n _playEdgeTtsChunked(clean, null, engine);", ) routes = ROOT / "api/routes.py" marker = " # ── ElevenLabs TTS ──────────────────────────────────────────────────\n" atlas = ''' # ── Atlas private Jetson TTS ───────────────────────────────────────── if engine == "atlas": atlas_url = os.getenv("HERMES_WEBUI_ATLAS_TTS_URL", "").strip() expected_url = "http://hermes-tts.hermes.svc.cluster.local:9001/v1/audio/speech" if atlas_url != expected_url: from api.helpers import bad as _bad return _bad(handler, "Atlas private TTS is not configured", 503) speed = 1.0 if rate_str: try: speed = max(0.5, min(2.0, 1.0 + (float(rate_str.rstrip("%")) / 100.0))) except ValueError: speed = 1.0 request_body = json.dumps({ "model": "piper", "input": text, "voice": "en_US-lessac-high", "speed": speed, }).encode("utf-8") request = Request(atlas_url, data=request_body, headers={ "Content-Type": "application/json", "Accept": "audio/wav", }) try: with _tts_open( request, timeout=45, opener_factory=lambda: build_opener(ProxyHandler({}), _NoRedirectTtsHandler()), ) as response: audio_data = _buffer_tts_audio_response(response) except Exception: logger.exception("Atlas private TTS generation failed") from api.helpers import bad as _bad return _bad(handler, "Atlas private TTS generation failed", 502) handler.send_response(200) handler.send_header("Content-Type", "audio/wav") handler.send_header("Cache-Control", "no-store") handler.send_header("Content-Length", str(len(audio_data))) handler.end_headers() try: handler.wfile.write(audio_data) except (BrokenPipeError, ConnectionResetError): pass return True ''' replace_exact(routes, marker, atlas + marker)