低配版赛博女友复盘——从一句话到数字人回应,一条链路的搭建与取舍

仓库:ydflow/cyber-girlfriend-16gb · React + Node.js + Python
一句话:让一张角色图片拥有声音、表情和与你对话的能力。

项目截图

一、背景:把「一句话」变成「一次完整的回应」

数字人、虚拟陪伴这类方向很火,但大多数 demo 止步于「一段视频」或「一段对话」。这个项目的野心是把它做成一条完整的链路:

上传角色图片、设定性格,按下麦克风说话。角色听懂后给出贴合设定的回答,播放合成语音,并在本地生成带口型和表情的回应视频。

一次回应的旅程是这样的:

1
2
麦克风 → 火山引擎语音识别 → DeepSeek 生成回复 → 火山引擎语音合成
→ LivePortrait / MuseTalk 本地动画 → 浏览器播放

二、技术组成:本地运行与云端调用并存

部分 技术与职责
前端 React,浏览器录音、静音检测、界面、本地会话保存
后端 Node.js,编排服务调用、配置管理、密钥保护
对话 DeepSeek,结合角色设定 + 记忆 + 近期上下文生成回复
语音识别/合成 火山引擎,ASR + TTS
动画 Python + LivePortrait(人像动作)+ MuseTalk(口型),本地生成视频

一个关键设计是**「本地运行 + 云端调用」并存**:网页、后端、动画服务跑在本机(隐私可控),但语音识别、对话生成、语音合成走外部 API(需要联网和凭据)。

三、几个值得记录的取舍

1. 先做「一轮回应」,不做「实时视频通话」

诚实地说,当前是一轮语音输入 → 生成回复 → 生成视频 → 播放,不是边说边生成的实时通话。首次加载模型、准备角色、生成视频都需要等待。

这个取舍很务实:先把一条链路的正确性跑通,再谈实时性。实时数字人通话是另一个量级的工程难题,值得单独立项。

2. 容错体验:动画失败就回退到纯语音

动画生成失败时,前端会尝试回退到纯语音播放。不让「视频失败」变成「整个体验失败」——这是体验设计上的降级保护。

3. 密钥保护:后端不返回真实 Key

后端保存配置时调用密钥保护函数,读取配置的接口只返回配置状态与必要的非密钥参数,不返回真实 API Key 或 Token。公开上传时也排除本地配置文件。这是安全底线。

4. 情绪与表情参数化

回复支持开心、害羞、难过、关心、生气、惊讶、中性等状态,并带强度参数。动画服务用这些参数选择和调整动作——让「表情」不是随机播放,而是被「情绪」驱动的。

四、诚实的能力边界

README 里明确写了定位和边界,这是我很看重的一点:

  • 「16GB 显存」是配置方向,不是保证:尚未提供公开版在全新环境的显存峰值、耗时、帧率基准,不代表所有 16GB 显卡都能达到同样效果;
  • 默认角色用通用占位图:展示图不作为可选角色安装,个人原图、私密设定、测试视频都不在仓库里;
  • 没有自动总结、长期记忆、跨设备同步:记忆面板展示已有条目,但不会自动写入长期记忆。

五、几点复盘

  1. 「先把一条链路跑通」比「一步到位」更现实。数字人从一句话到回应视频,拆成 ASR→对话→TTS→动画四段,一段段验证,比试图一步做出实时通话更靠谱。
  2. 降级保护是体验的一部分。动画失败回退语音,让用户在「不完美」时依然有可用体验。
  3. 密钥不进仓库、不进返回是硬底线。个人项目最容易在这上面翻车。
  4. 诚实标注「16GB 不代表都能跑」,比吹一个「低配也能玩」的噱头更负责。

这个项目让我意识到,数字人真正的难点不在「能不能动」,而在链路每一环的可靠衔接。下一篇讲日程小程序「日序」,是一个完全不同风格的项目——本地优先、纯 TypeScript、201 个测试。