From d68f6a43046d308a4e264d14559facc960e35513 Mon Sep 17 00:00:00 2001 From: Sam Rolfe Date: Sun, 30 Aug 2026 14:55:39 +1000 Subject: [PATCH] Console Voice Assistant (Jervis): text prompt box -> local LLM agent (/voice proxies to .13:8501) --- portal/main.py | 34 ++++++++++++++++++++++++++++++++++ portal/templates/base.html | 1 + portal/templates/voice.html | 31 +++++++++++++++++++++++++++++++ todo.txt | 1 + 4 files changed, 67 insertions(+) create mode 100644 portal/templates/voice.html diff --git a/portal/main.py b/portal/main.py index 8567219..3eb74b7 100644 --- a/portal/main.py +++ b/portal/main.py @@ -18,6 +18,7 @@ from zoneinfo import ZoneInfo MELB = ZoneInfo("Australia/Melbourne") import json import os +import httpx from pathlib import Path from typing import Annotated @@ -329,6 +330,39 @@ async def files_upload(request: Request, file: Annotated[UploadFile, File()]) -> ) + +VOICE_AGENT = os.getenv("VOICE_AGENT_URL", "http://192.168.20.13:8501") + + +@app.get("/voice", response_class=HTMLResponse) +async def voice_page(request: Request) -> HTMLResponse: + user = await _current_user(request) + if user is None: + return RedirectResponse("/login", status_code=303) + return templates.TemplateResponse(request, "voice.html", {"user": user}) + + +@app.post("/voice", response_class=HTMLResponse) +async def voice_send(request: Request, message: Annotated[str, Form()]) -> HTMLResponse: + user = await _current_user(request) + if user is None: + return RedirectResponse("/login", status_code=303) + command = message.strip() + error = reply = None + if command: + try: + async with httpx.AsyncClient(timeout=60) as client: + r = await client.post(f"{VOICE_AGENT}/voice", json={"text": command}) + r.raise_for_status() + reply = r.json().get("reply") or r.json().get("text") + except Exception as exc: # noqa: BLE001 + logger.warning("voice agent call failed", exc_info=True) + error = f"Voice assistant unavailable: {type(exc).__name__}" + return templates.TemplateResponse( + request, "voice.html", {"user": user, "command": command, "reply": reply, "error": error} + ) + + @app.get("/admin", response_class=HTMLResponse) async def admin_panel(request: Request) -> HTMLResponse: user = await _current_user(request) diff --git a/portal/templates/base.html b/portal/templates/base.html index 0a6982a..4d37025 100644 --- a/portal/templates/base.html +++ b/portal/templates/base.html @@ -16,6 +16,7 @@