Add VOICE_CONFIG env var to select the voice pipeline config file.

Defaults to config/voice.json; relative paths resolve from project root.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Xin Wang
2026-05-22 16:29:27 +08:00
parent bc2aa5b133
commit a10f0a586b
3 changed files with 36 additions and 6 deletions

View File

@@ -8,3 +8,6 @@ APP_ID=683ea1bc86197e19f71fc1ae
DELETE_SESSION_URL=http://127.0.0.1:3030/api/core/chat/delHistory?chatId={chatId}&appId={appId}
DELETE_CHAT_URL=http://127.0.0.1:3030/api/core/chat/item/delete?contentId={contentId}&chatId={chatId}&appId={appId}
GET_CHAT_RECORDS_URL=http://127.0.0.1:3030/api/core/chat/getPaginationRecords
# Voice demo (Pipecat /ws-product). Relative to project root, or an absolute path.
VOICE_CONFIG=config/voice.json

View File

@@ -1,11 +1,30 @@
from __future__ import annotations
import json
import os
from dataclasses import dataclass, field
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
DEFAULT_VOICE_CONFIG = PROJECT_ROOT / "config" / "voice.json"
DEFAULT_VOICE_CONFIG_REL = "config/voice.json"
def resolve_voice_config_path() -> Path:
"""Return the voice config path from VOICE_CONFIG or the default."""
configured = os.getenv("VOICE_CONFIG", DEFAULT_VOICE_CONFIG_REL).strip()
if not configured:
configured = DEFAULT_VOICE_CONFIG_REL
path = Path(configured)
if not path.is_absolute():
path = PROJECT_ROOT / path
return path
DEFAULT_VOICE_CONFIG = resolve_voice_config_path()
@dataclass(frozen=True)
@@ -143,7 +162,7 @@ class EngineConfig:
def load_config(path: str | Path | None = None) -> EngineConfig:
config_path = Path(path) if path is not None else DEFAULT_VOICE_CONFIG
config_path = Path(path) if path is not None else resolve_voice_config_path()
if not config_path.is_absolute():
config_path = PROJECT_ROOT / config_path
data = json.loads(config_path.read_text(encoding="utf-8"))

View File

@@ -8,7 +8,7 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from loguru import logger
from .config import DEFAULT_VOICE_CONFIG, EngineConfig, load_config
from .config import EngineConfig, load_config, resolve_voice_config_path
from .pipeline import run_product_voice_pipeline
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
@@ -19,7 +19,12 @@ router = APIRouter(tags=["voice"])
@lru_cache(maxsize=1)
def get_voice_config() -> EngineConfig:
return load_config(DEFAULT_VOICE_CONFIG)
return load_config()
@lru_cache(maxsize=1)
def get_voice_config_path() -> Path:
return resolve_voice_config_path()
def _normalize_mount_path(path: str) -> str:
@@ -39,6 +44,7 @@ async def voice_health() -> dict[str, object]:
)
return {
"status": "healthy",
"config": str(get_voice_config_path()),
"protocols": {
"/ws-product": "va.ws.v1.json_base64",
},
@@ -62,12 +68,14 @@ async def product_websocket_endpoint(websocket: WebSocket) -> None:
def register_voice(app: FastAPI) -> None:
"""Mount voice websocket routes and optional browser demo static files."""
if not DEFAULT_VOICE_CONFIG.exists():
logger.warning(f"Voice config not found at {DEFAULT_VOICE_CONFIG}; voice demo disabled")
voice_config_path = get_voice_config_path()
if not voice_config_path.exists():
logger.warning(f"Voice config not found at {voice_config_path}; voice demo disabled")
return
config = get_voice_config()
app.include_router(router)
logger.info(f"Voice config loaded from {voice_config_path}")
if config.server.cors_origins:
app.add_middleware(