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:
```text
http://127.0.0.1:8000/demo/
```
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
```
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.
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"
/>