demo mount to fastapi

This commit is contained in:
Xin Wang
2026-05-22 10:23:17 +08:00
parent 0d82acc0d2
commit 2c7f0f440b
10 changed files with 86 additions and 22 deletions

View File

@@ -25,7 +25,7 @@ uv venv .venv
source .venv/bin/activate
uv pip install -r requirements.txt
export OPENAI_API_KEY=...
uv run uvicorn engine.main:app --reload --host 0.0.0.0 --port 8001
uv run uvicorn engine.main:app --reload --host 0.0.0.0 --port 8000
```
Or pass keys directly in `config.json`.
@@ -34,12 +34,21 @@ Or pass keys directly in `config.json`.
uv run python -m engine.main --config ./config.json
```
Browser demo (served from the same process when `server.serve_webpage` is
true in `config.json`):
```text
http://localhost:8000/demo/
```
See `examples/webpage/README.md` for details.
## Protocols
Pipecat-native endpoint:
```text
ws://localhost:8001/ws
ws://localhost:8000/ws
```
The websocket payloads are Pipecat protobuf frames. A client should use Pipecat's websocket client/serializer stack or generate frames compatible with `pipecat.frames.protobufs.frames_pb2`.
@@ -54,7 +63,7 @@ Important defaults:
Product endpoint:
```text
ws://localhost:8001/ws-product
ws://localhost:8000/ws-product
```
This endpoint uses a stable JSON/base64 protocol named `va.ws.v1`. It is meant for browser, mobile, or other product applications that should not depend on Pipecat's internal protobuf frame schema.

View File

@@ -1,8 +1,10 @@
{
"server": {
"host": "0.0.0.0",
"port": 8001,
"cors_origins": ["http://localhost:3000", "http://localhost:8080"]
"port": 8000,
"cors_origins": ["http://localhost:3000", "http://localhost:8080"],
"serve_webpage": true,
"webpage_mount": "/demo"
},
"audio": {
"sample_rate_hz": 16000,

View File

@@ -1,7 +1,7 @@
{
"server": {
"host": "0.0.0.0",
"port": 8001,
"port": 8000,
"cors_origins": ["*"]
},
"audio": {

View File

@@ -1,7 +1,7 @@
{
"server": {
"host": "0.0.0.0",
"port": 8001,
"port": 8000,
"cors_origins": ["*"]
},
"audio": {

View File

@@ -1,7 +1,7 @@
{
"server": {
"host": "0.0.0.0",
"port": 8001,
"port": 8000,
"cors_origins": ["*"]
},
"audio": {

View File

@@ -10,6 +10,8 @@ class ServerConfig:
host: str = "0.0.0.0"
port: int = 8000
cors_origins: list[str] = field(default_factory=list)
serve_webpage: bool = True
webpage_mount: str = "/demo"
@dataclass(frozen=True)

View File

@@ -2,19 +2,30 @@ from __future__ import annotations
import argparse
from functools import lru_cache
from pathlib import Path
from fastapi import FastAPI, WebSocket
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from .config import EngineConfig, load_config
from .pipeline import run_product_voice_pipeline, run_voice_pipeline
WEBPAGE_DIR = Path(__file__).resolve().parent.parent / "examples" / "webpage"
@lru_cache(maxsize=8)
def get_config(path: str = "config.json") -> EngineConfig:
return load_config(path)
def _normalize_mount_path(path: str) -> str:
normalized = path.strip() or "/demo"
if not normalized.startswith("/"):
normalized = f"/{normalized}"
return normalized.rstrip("/") or "/"
def create_app(config_path: str = "config.json") -> FastAPI:
config = get_config(config_path)
app = FastAPI(title="AI VideoAssistant Engine v5 Pipecat Minimal", version="0.1.0")
@@ -28,6 +39,12 @@ def create_app(config_path: str = "config.json") -> FastAPI:
allow_headers=["*"],
)
webpage_mount = (
_normalize_mount_path(config.server.webpage_mount)
if config.server.serve_webpage
else None
)
@app.get("/health")
async def health() -> dict[str, object]:
return {
@@ -40,6 +57,7 @@ def create_app(config_path: str = "config.json") -> FastAPI:
"product_text_input": True,
"product_text_interrupt": True,
},
"demo": webpage_mount,
"llm_provider": config.services.llm.provider,
"stt_provider": config.services.stt.provider,
"tts_provider": config.services.tts.provider,
@@ -55,6 +73,13 @@ def create_app(config_path: str = "config.json") -> FastAPI:
await websocket.accept()
await run_product_voice_pipeline(websocket, config)
if config.server.serve_webpage and WEBPAGE_DIR.is_dir() and webpage_mount:
app.mount(
webpage_mount,
StaticFiles(directory=str(WEBPAGE_DIR), html=True),
name="webpage",
)
return app

View File

@@ -39,29 +39,48 @@ examples/webpage/
## Run
1. Start the engine (default port `8001`):
1. Start the engine (default port `8000`):
```bash
cd AI-VideoAssistant-engine-v5-pipecat-minimal
source .venv/bin/activate
export OPENAI_API_KEY=...
uvicorn engine.main:app --host 127.0.0.1 --port 8001
uvicorn engine.main:app --host 127.0.0.1 --port 8000
```
2. In another terminal, serve the page from a port that's on the
engine's CORS allow-list (see `config.json`). The default config
allows `http://localhost:8080`:
2. Open the demo page served by the same process:
```bash
cd AI-VideoAssistant-engine-v5-pipecat-minimal/examples/webpage
python -m http.server 8080
```text
http://127.0.0.1:8000/demo/
```
3. Open <http://localhost:8080> in Chrome, Edge, or Safari.
- Click **Connect** (uses `ws://127.0.0.1:8001/ws-product` by default).
- Pick a microphone if needed, click **Enable mic**, and start
speaking. The browser will prompt for microphone access on first
use. Device names may appear only after permission is granted.
The default websocket URL is derived from the page host
(`ws://127.0.0.1:8000/ws-product`). Click **Connect**, pick a
microphone if needed, click **Enable mic**, and start speaking.
Mount path and on/off are controlled in `config.json`:
```json
"server": {
"serve_webpage": true,
"webpage_mount": "/demo"
}
```
Set `"serve_webpage": false` in production if you serve the UI elsewhere.
### Standalone static server (optional)
You can still serve the files from another port for UI-only iteration.
Add that origin to `server.cors_origins` in `config.json` if needed:
```bash
cd AI-VideoAssistant-engine-v5-pipecat-minimal/examples/webpage
python -m http.server 8080
```
Then open <http://localhost:8080> and point the URL field at
`ws://127.0.0.1:8000/ws-product`.
> The browser's mic API requires a secure context. `http://localhost`
> qualifies; if you serve from another host, use HTTPS and a `wss://`

View File

@@ -18,6 +18,11 @@ const PROTOCOL = "va.ws.v1";
const MAX_WS_LOG_LINES = 120;
const AUDIO_DELTA_LOG_INTERVAL_MS = 1000;
function defaultWsUrl() {
const scheme = location.protocol === "https:" ? "wss:" : "ws:";
return `${scheme}//${location.host}/ws-product`;
}
const els = {
url: document.getElementById("ws-url"),
connectBtn: document.getElementById("connect-btn"),
@@ -885,6 +890,8 @@ window.addEventListener("beforeunload", () => {
}
});
els.url.value = defaultWsUrl();
setStatus("idle", "Disconnected");
setConnectButton();
setMicButton();

View File

@@ -20,7 +20,7 @@
<input
id="ws-url"
type="text"
value="ws://127.0.0.1:8001/ws-product"
placeholder="ws://host/ws-product"
spellcheck="false"
autocomplete="off"
/>