b3bef75981
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>
84 lines
2.1 KiB
Python
84 lines
2.1 KiB
Python
"""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")
|