Files
park-desktop/docs/voice-integration-plan.md

8.9 KiB
Raw Permalink Blame History

DPM 实时语音对话整合方案(复用 live-avatar 云化栈)

目标:砍掉数字人/口型/wav2lip 等全部视觉部分,只保留「语音识别 + 对话 + 实时交互」链路; 前端在 DPM React 应用中新增对话页(复用 live-avatar demo 前端的样式与逻辑); 后端并入 DPM backend/pyproject.toml。 基线:live-avatar 云化栈已在 /Volumes/Pine/mycode/opc/live 跑通(s2s v0.2.11 + cloud-migration 补丁 + demo 前端)。


0. 现状对比

live-avatar(已跑通) DPM(现状)
前端 原生 JS demoindex.html / main.js / s2s-ws-client.js / 两个 worklet React 19 + Vite + Tailwind/ai 页已有 AiChatPanel(文字 + 录音上传式语音)
后端 s2s 独立进程 :8765VAD 本地 + 云 ASR/LLM/TTS FastAPI :8000;已有 app/asr.pydashscope paraformer)、app/llm.pyqwen + 工具)、SSL_CERT_FILE=certifi 同款处理
语音体验 实时全双工:本地 VAD → 流式云 ASR → 流式 LLM → 流式 TTS → 音频回放 录音 → 上传 → 转写 → 文字回复(无流式、无 TTS 回音)

DPM 缺的正是 live-avatar 栈的实时交互能力;两边的云凭据/CA 处理已一致(dashscope + certifi),合并成本低。


1. 两个关键决策点

1.1 后端:内嵌进 DPM FastAPI(推荐) vs 独立 s2s 进程

  • 推荐:内嵌。s2s v0.2.11 的 api/openai_realtime/websocket_router.create_app(pool, stop_event) 本身就返回一个 FastAPI 应用, 可在 DPM app.main 里构建管线池后 app.mount("/v1", s2s_app),与现有 /api/* 共存、单进程单端口。 Tauri 桌面打包只需一个后端进程,无子进程管理。
  • 备选:s2s 独立跑 :8765,DPM 负责拉起/守护。改动最小,但两进程两端口,打包与部署更重。

1.2 前端:iframe 内嵌(推荐 v1 vs React 组件移植(v2

  • 推荐 v1iframe 内嵌。把 live-avatar demo/ 目录拷入 DPM public/voice-demo/(去掉数字人 iframe/视觉元素), React 新增 VoiceAssistant.jsx(路由 /voice)里 <iframe src="/voice-demo/"> 全屏。 100% 复用现有样式与逻辑、零 React 改写、风险最低WS 地址由 voice-demo/config.jsVITE_API_BASE 派生。
  • v2React 组件移植。把 s2s-ws-client.js + 两个 worklet 拷入 src/voice/,用 hooks 驱动、JSX 重绘圆球/气泡, 与 DPM 布局/Tailwind 完全融合;工作量较大,建议先 v1 跑通再决定。

2. 后端合并清单

2.1 backend/pyproject.toml

dependencies = [
    # ...现有依赖(fastapi/uvicorn/dashscope/certifi 已覆盖)...
    # s2s 云化栈(path 依赖,指向补丁后的 s2s-cloud;或 vendored 到 backend/vendor/
    "speech-to-speech @ file:///Volumes/Pine/mycode/opc/live/s2s-cloud",
    # s2s 运行所需(base 依赖里 Darwin 专用的 mlx/transformers 等不要全装)
    "torch>=2.4",        # VAD(silero) 本地必需;CPU 亦可
    "numpy>=1.26",
    "scipy>=1.10",
    "httpx>=0.28",
    "websockets>=12",
    "nltk==3.9.4",
    "openai==2.28.0",
    "soundfile>=0.13",
]

安装用 pip install -e "backend[voice]" --no-deps + 手动补依赖,避免 s2s base 依赖把 mlx/transformers 等大件拖进来。 torch 是唯一的大依赖(VAD 本地必需);若部署机无 GPUCPU torch 跑 silero 足够。

2.2 配置(app/config.py + backend/.env

DASHSCOPE_API_KEY=sk-...          # 已有(与 app/asr.py、app/llm.py 共用)
S2S_ENABLED=1
S2S_NUM_PIPELINES=4               # 并发会话数
S2S_WS_PATH=/v1/realtime
S2S_STT_MODEL=qwen3-asr-flash-realtime
S2S_LLM_MODEL=qwen-plus
S2S_TTS_MODEL=qwen3-tts-flash-realtime
S2S_TTS_VOICE=Cherry

2.3 代码(新 app/s2s_bridge.py + 挂载)

  1. s2s_bridge.py:读取 Settings → 用与 start_backend.sh 等价的参数构建管线池 --stt dashscope-asr --llm_backend chat-completions --tts qwen3-cloud --enable_live_transcription false), 返回 websocket_router.create_app(pool, stop_event)
  2. app/main.py lifespanENABLED 时启动池 → app.mount(settings.S2S_WS_PATH, s2s_app);关闭时 stop。
  3. 现有 /api/ai/chat/api/ai/asr 保留(旧 AiChatPanel 流程不破坏)。

3. 前端合并清单

  1. src/pages/VoiceAssistant.jsx(新):
    • 全屏 <iframe src="/voice-demo/" />v1),或未来 React 移植版。
    • 加入 App.jsx 路由:<Route path="/voice" element={<VoiceAssistant />} />(建议放在 ScreenLayout 内或独立全屏)。
  2. public/voice-demo/:拷贝 live-avatar demo/index.html / main.js / style.css / ws/ / worklets/ / textchat.html), 并删除数字人 iframeindex.html 里的 #avatar-frame → 背景改纯色/园区图)。
  3. public/voice-demo/config.js:暴露 window.VOICE_WS_URL(从 VITE_API_BASE 派生 ws://<host>:<port>/v1/realtime)。
  4. 麦克风权限:
    • 浏览器开发模式:localhost 直接可用。
    • Tauri v2 打包src-tauri/tauri.conf.jsonapp.macOSPrivateApi: true(macOS 麦克风)+ 系统隐私授权说明; Windows 需在 manifest/权限描述中声明 microphone。

4. 实施步骤

步骤 内容 工作量
P1 后端内嵌:pyproject 加依赖 → s2s_bridge.py → mount → 冒烟(WS 连得上、说话→回复→出声) 0.51 天
P2 前端:public/voice-demo 拷贝去视觉化 → VoiceAssistant.jsx + 路由 → WS 指向 DPM 后端 0.5 天
P3 联调:DPM 页面语音对话全链路 + 并发验证 0.5 天
P4(可选) React 原生移植(hooks 驱动 client + workletsTailwind 重绘 UI 12 天

5. 风险与注意

  • torch 体积:VAD 本地必需(s2s 管线结构固定 VAD 本地),CPU 版可接受;若部署机极简可评估 VAD 上云(不推荐,延迟差)。
  • 单管线槽S2S_NUM_PIPELINES 默认 1 → 并发对话会拒连;按需调到 4。
  • WS 鉴权:内网/桌面场景可无鉴权;如需,在 session.update 里带 token 校验。
  • iframe 限制:v1 iframe 内样式隔离、无法用 DPM 主题;v2 移植可解决。
  • Tauri 麦克风:打包前务必配置 macOSPrivateApi + 权限描述,否则桌面版无声/无权限。

附录 A · 视觉触发子系统(USB 摄像头 + 桌面端检测)

目标:大屏前 USB 摄像头拍观众;检测有人正对且停留 ≥10 秒 → 主动问候 + 全屏联动;问候后静默 60 秒

A.1 架构与数据通路

大屏机(Tauri 应用,fullscreen
  └─ USB 摄像头(大屏前,拍观众)
       └─ webview getUserMedia(一路视频流)
            ├─ 持续检测:MediaPipe FaceMesh 1~2fps(本机、不出设备)
            │     ├─ 头姿 yaw/pitch 各 ±15° = 正对
            │     └─ 连续 10s 正对 → 触发(窗口内 ≥80% 帧正对,防抖)
            └─ 按需抓帧:LLM 决定时 canvas 取当前帧 → input_image → 云 qwen3-vl(人数/人物)
触发动作:
  ① 主动问候:client.sendUserText("检测到有人正对摄像头…请主动问候") + requestResponse()
     → s2s 走 LLM 生成问候语 → TTS 播报(零后端改动,复用现有 WS 协议)
  ② 全屏联动:调后端 → event_bus / mqtt.publish → 各屏响应
静默窗口:问候触发后 60s 冷却,期内检测照跑但不触发

A.2 Tauri 摄像头权限(必须配置)

  • macOStauri.conf.jsonapp.macOSPrivateApi: trueInfo.plist 加 NSCameraUsageDescription / NSMicrophoneUsageDescription(麦克风语音对话也需要)。
  • WindowsNSIS 打包目标)WebView2 的 getUserMedia 跟随系统隐私设置 (设置 → 隐私和安全性 → 相机/麦克风 → 允许该应用);安装包建议在应用清单声明设备能力。
  • 大屏机摄像头常开:确认系统隐私里该应用允许相机,否则检测与抓帧同时失效。

A.3 隐私与合规

  • 持续检测帧仅在本机内存处理、即用即弃,不上云;只有 LLM 决定抓的那一帧发往百炼 VLM。
  • 大屏前采集观众人脸属个人信息处理 → 大屏旁需公示"影像识别用于互动服务"。

A.4 落地清单(并入 P2/P3

说明
前端新增 useCameraDetect hook getUserMedia + MediaPipe FaceMesh + 状态机(IDLE→FACING→TRIGGER→SILENT
依赖 @mediapipe/tasks-visionface_detector + face_landmarker
参数化 停留 10s / 静默 60s / 角度 ±15° / 频率 1~2fps → 配置文件或设置页
触发动作 主动问候(sendUserText+create+ MQTT 联动(后端 API
首对话抓帧 与检测共用同一路视频流,canvas 取帧 → input_image → qwen3-vl

注:视觉子系统是可选增强,不影响主语音对话方案;摄像头/检测不可用时语音链路照常工作。