feat: VoiceCoach v2 — livekit-agents 1.5 personal voice assistant
Replaces pipecat 0.0.48 (6 manual patches, DuplicateIdentity reconnect storms) with livekit-agents 1.5 SDK. New features: - SvelteKit UI with live transcript, VAD ring, mode bar, vocal meter, history view - SQLite session store with WAL mode (cross-session memory) - Async SpeechBrain vocal/emotion analysis injected per turn - Homelab tools: SSH/docker, Prometheus alerts, Home Assistant, SearXNG search - OpenClaw LLM gateway preserved (qwen2.5:14b via Ollama) - 4 modes: coaching, vent, practice, briefing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"""Token server + sessions REST API — FastAPI."""
|
||||
import asyncio
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
import uvicorn
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from livekit import api as livekit_api
|
||||
from pydantic import BaseModel
|
||||
|
||||
import store
|
||||
|
||||
LIVEKIT_API_KEY = os.environ["LIVEKIT_API_KEY"]
|
||||
LIVEKIT_API_SECRET = os.environ["LIVEKIT_API_SECRET"]
|
||||
LIVEKIT_PUBLIC_URL = os.getenv("LIVEKIT_PUBLIC_URL", "wss://voice.hobbs.farm")
|
||||
|
||||
app = FastAPI(title="VoiceCoach API")
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
class TokenRequest(BaseModel):
|
||||
identity: str = "jeremy"
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup():
|
||||
await store.init_db()
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
return {"status": "ok", "version": "2.0"}
|
||||
|
||||
|
||||
@app.post("/api/token")
|
||||
async def get_token(req: TokenRequest):
|
||||
token = livekit_api.AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET)
|
||||
token.with_grants(livekit_api.VideoGrants(
|
||||
room_join=True,
|
||||
room="voicecoach",
|
||||
can_publish=True,
|
||||
can_subscribe=True,
|
||||
))
|
||||
token.identity = req.identity
|
||||
token.ttl = timedelta(hours=8)
|
||||
return {"token": token.to_jwt(), "url": LIVEKIT_PUBLIC_URL}
|
||||
|
||||
|
||||
@app.get("/api/sessions")
|
||||
async def list_sessions(limit: int = 20):
|
||||
return await store.get_sessions(limit=limit)
|
||||
|
||||
|
||||
@app.get("/api/sessions/{session_id}/messages")
|
||||
async def get_messages(session_id: str):
|
||||
msgs = await store.get_session_messages(session_id)
|
||||
if not msgs:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
return msgs
|
||||
|
||||
|
||||
@app.get("/api/profile/{key}")
|
||||
async def get_profile(key: str):
|
||||
value = await store.get_profile(key)
|
||||
if value is None:
|
||||
raise HTTPException(status_code=404, detail="Key not found")
|
||||
return {"key": key, "value": value}
|
||||
|
||||
|
||||
@app.put("/api/profile/{key}")
|
||||
async def set_profile_key(key: str, body: dict):
|
||||
await store.set_profile(key, str(body.get("value", "")))
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
def run():
|
||||
uvicorn.run(app, host="0.0.0.0", port=8080, log_level="info")
|
||||
Reference in New Issue
Block a user