← 档案馆 L2D / COMPANION
LOCAL RULES
CASE 05 / LIVE2D

AI 陪伴

ASR · LLM · TTS · LIP SYNC

待机
NEUTRAL
LOADING HARU
点击角色切换表情 · 鼠标移动触发视线跟随 HARU / CUBISM 4
TECH ARCHIVE · 05

AI 陪伴

渲染层与 03 的 Pixi 舞台完全相同,差异全部集中在「听、想、说、口型」这条业务管线: 浏览器语音识别 → 流式 LLM → 分句 TTS → 参数级口型。

01

实现要点

Implementation
  1. 零配置优先:本地规则覆盖问候、情绪、工作、吃喝等常见话题;远程配置只写入 sessionStorage,关掉标签页即清除。
  2. 流式情绪标签:约定回复以 [emotion:name] 开头,解析后立即切换表情,正文从标签之后开始显示,标签本身不会进入气泡。
  3. 分句 TTS:按句号、问号、感叹号切分,一句一个 SpeechSynthesisUtterance;onstart 打开口型,onboundary 给一个能量脉冲,句末自然衰减。
  4. 口型钩子:监听 beforeModelUpdate,在 coreModel.update() 之前写入 ParamMouthOpenY,与待机动作、表情的嘴型取较大值。
  5. 可打断与降级:每轮持有递增的 turn token,打断时中止 fetch、清空 TTS 队列、口型归零;浏览器不发声时 5 秒看门狗降级为纯文字,远程失败回退本地规则。
02

关键代码

Code
JS
const match = start.match(/^\[emotion:\s*([a-z_-]+)\s*\]\s*/i);
if (match) {
  raw = start.slice(match[0].length);
  setEmotion(match[1]);
}

internal.on('beforeModelUpdate', () => {
  if (!lipActive) return;
  const open = 0.18 + 0.24 * (0.5 + 0.5 * Math.sin(now * 9.4)) + lipBoost;
  core.setParameterValueById('ParamMouthOpenY',
    Math.max(core.getParameterValueById('ParamMouthOpenY') || 0, open));
});

state.turn += 1;
state.abort?.abort();
tts.cancel();
03

注意

Caveats
  • 远程模式要求端点允许浏览器跨域直连;语音识别需要 Chrome / Edge 与麦克风权限,Firefox 与部分内网浏览器会降级为纯文本。
  • 语音合成的音色来自系统;模型动作自带的 wav 已在初始化时静音,避免和 TTS 抢音频。
04

不同用法

Variants