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_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} 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 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 from __future__ import annotations
import json import json
import os
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent 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) @dataclass(frozen=True)
@@ -143,7 +162,7 @@ class EngineConfig:
def load_config(path: str | Path | None = None) -> 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(): if not config_path.is_absolute():
config_path = PROJECT_ROOT / config_path config_path = PROJECT_ROOT / config_path
data = json.loads(config_path.read_text(encoding="utf-8")) 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 fastapi.staticfiles import StaticFiles
from loguru import logger 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 from .pipeline import run_product_voice_pipeline
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
@@ -19,7 +19,12 @@ router = APIRouter(tags=["voice"])
@lru_cache(maxsize=1) @lru_cache(maxsize=1)
def get_voice_config() -> EngineConfig: 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: def _normalize_mount_path(path: str) -> str:
@@ -39,6 +44,7 @@ async def voice_health() -> dict[str, object]:
) )
return { return {
"status": "healthy", "status": "healthy",
"config": str(get_voice_config_path()),
"protocols": { "protocols": {
"/ws-product": "va.ws.v1.json_base64", "/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: def register_voice(app: FastAPI) -> None:
"""Mount voice websocket routes and optional browser demo static files.""" """Mount voice websocket routes and optional browser demo static files."""
if not DEFAULT_VOICE_CONFIG.exists(): voice_config_path = get_voice_config_path()
logger.warning(f"Voice config not found at {DEFAULT_VOICE_CONFIG}; voice demo disabled") if not voice_config_path.exists():
logger.warning(f"Voice config not found at {voice_config_path}; voice demo disabled")
return return
config = get_voice_config() config = get_voice_config()
app.include_router(router) app.include_router(router)
logger.info(f"Voice config loaded from {voice_config_path}")
if config.server.cors_origins: if config.server.cors_origins:
app.add_middleware( app.add_middleware(