Console Voice Assistant (Jervis): text prompt box -> local LLM agent (/voice proxies to .13:8501)

This commit is contained in:
2026-08-30 14:55:39 +10:00
parent a347aefdff
commit d68f6a4304
4 changed files with 67 additions and 0 deletions

View File

@@ -18,6 +18,7 @@ from zoneinfo import ZoneInfo
MELB = ZoneInfo("Australia/Melbourne") MELB = ZoneInfo("Australia/Melbourne")
import json import json
import os import os
import httpx
from pathlib import Path from pathlib import Path
from typing import Annotated 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) @app.get("/admin", response_class=HTMLResponse)
async def admin_panel(request: Request) -> HTMLResponse: async def admin_panel(request: Request) -> HTMLResponse:
user = await _current_user(request) user = await _current_user(request)

View File

@@ -16,6 +16,7 @@
<div class="nav-right"> <div class="nav-right">
<a href="/transcriber" class="button-utility">Transcriber</a> <a href="/transcriber" class="button-utility">Transcriber</a>
<a href="/files" class="button-utility">Files</a> <a href="/files" class="button-utility">Files</a>
<a href="/voice" class="button-utility">Voice</a>
{% if user.is_admin %} {% if user.is_admin %}
<a href="/admin" class="button-utility">Admin</a> <a href="/admin" class="button-utility">Admin</a>
{% endif %} {% endif %}

View File

@@ -0,0 +1,31 @@
{% extends "base.html" %}
{% block title %}Voice Assistant — {{ settings.APP_NAME }}{% endblock %}
{% block content %}
<div class="page" style="max-width:560px">
<h1 class="greeting">Voice Assistant (Jervis)</h1>
<p class="muted">Type a command for the home assistant — it's sent to the local LLM agent, which acts (lights, shopping, weather, reminders…) and speaks the reply on the house speakers.</p>
<div class="admin-panel">
<div class="linkout-card stack">
{% if error %}<div class="form-error">{{ error }}</div>{% endif %}
<form method="post" action="/voice">
<div class="form-field">
<label for="message">Command</label>
<input class="text-input" id="message" name="message"
placeholder="e.g. turn on the lounge lights, what's the weather, add milk to the shopping list…"
value="{{ command or '' }}" required>
</div>
<button class="button button-primary width-full" type="submit">Send</button>
</form>
{% if reply %}
<div class="linkout-card stack" style="margin-top:16px">
<strong>Jervis:</strong>
<p style="margin:8px 0 0">{{ reply }}</p>
</div>
{% endif %}
<p class="faint margin-top" style="font: var(--type-caption)">
Uses the local voice agent on `.13` (OmniRoute LLM). Replies are also spoken via Snapcast.
</p>
</div>
</div>
</div>
{% endblock %}

View File

@@ -20,3 +20,4 @@ x 2026-08-26 2026-08-26 DS Harness - ensure no bash or local editing possible
x 2026-08-26 2026-08-26 Remove OpenWebUI - Caddy, folder, docker +project:family-home-lab +agent:family_home_lab x 2026-08-26 2026-08-26 Remove OpenWebUI - Caddy, folder, docker +project:family-home-lab +agent:family_home_lab
(B) Home dash: add a common 'Links' section with categories Media/Work/Family. Media: Spotify, Netflix. Work: mail.google.com, calendar.google.com, secure1.inmotionhosting.com/index/login, mxt.smsglobal.com/login/ +project:family-home-lab +agent:family_home_lab (B) Home dash: add a common 'Links' section with categories Media/Work/Family. Media: Spotify, Netflix. Work: mail.google.com, calendar.google.com, secure1.inmotionhosting.com/index/login, mxt.smsglobal.com/login/ +project:family-home-lab +agent:family_home_lab
(C) Sam admin dashboard panel: add router links — netcom router http://192.168.20.1:8079/login.html (may be in Caddy) and second router http://192.168.20.254/ +project:family-home-lab +agent:family_home_lab (C) Sam admin dashboard panel: add router links — netcom router http://192.168.20.1:8079/login.html (may be in Caddy) and second router http://192.168.20.254/ +project:family-home-lab +agent:family_home_lab
(C) Send commands to LLM voice-controlled network for Home Assistant, etc. +project:family-home-lab +agent:family_home_lab