Skip to content

Commit b047da4

Browse files
KrabbypattylHongzhi Wenclaude
authored
更新新手引导胶囊台词和中文语音 (#1601)
* 更新新手引导胶囊台词和中文语音 * fix: align guide playback matching * fix: sync guide locales on main * fix: align plugin manager locale hunks * fix(tutorial): allow compact chat intro activation * fix(chat): keep split guide line during shared speech * fix(tutorial): allow home intro chat activation clicks * fix(tutorial): sync zh-CN settings peek copy with audio * fix(i18n): align zh-CN locale diff range * fix(tutorial): sync settings peek copy with audio * fix(tutorial): sync zh-CN intro copy with audio * fix(tutorial): allow manual plugin dashboard click * fix(chat): compactCaptionPreview 补 isGuide 满足必填字段 #1620 新增的 compactCaptionPreview 未设 isGuide,与本 PR 把 CompactMessagePreview.isGuide 设为必填冲突;合并后 tsc -b 报 TS2345 (npm run typecheck 走 solution 式 tsconfig.json files:[] 是 no-op, 未抓到这个 build break,CodeRabbit 指出)。补 isGuide: false——事件 管线字幕永远不是 guide,语义正确。 验证:tsc -b 通过、vitest 全量 163/163 通过。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(chat): guide 分支一并清 compactSpeechPreviewTurnIdRef isGuide 早返回分支只清了 Id/Text ref,漏清 turnId ref;兄弟的 id-changed 分支三个 ref 全清。残留旧 turnId 会让 compactPreservedSpeechMatchesEndingGap 在普通 turn 之后紧接 guide 时拿到过期 turnId 误判。清掉与兄弟分支保持一致。 验证:tsc -b 通过、vitest 163/163 通过。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Hongzhi Wen <cartabio.coder1@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 70bacb5 commit b047da4

48 files changed

Lines changed: 1438 additions & 204 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/design/home-tutorial-yui-guide-performance-owner-stage-breakdown.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
- `main_routers/websocket_router.py`:后端 `home_tutorial_state` 接收和 `greeting_check` 教程态兜底。
5050
- `main_routers/system_router.py``utils/tutorial_prompt_state.py`:提示状态、教程生命周期、handoff token 后端状态。
5151

52-
聊天 UI 只以 React 实现为准,入口是 `frontend/react-neko-chat/` 构建出的组件。旧的 `#chat-container` 不应作为当前实现依据
52+
聊天 UI 只以 React compact 实现为准,入口是 `frontend/react-neko-chat/` 构建出的组件。首页普通模式下,新手教程的聊天上下文高亮应落到 compact 胶囊对话输入框(`.compact-chat-surface-frame` / `data-compact-geometry-item="capsule|input"`),旧的 `#chat-container` 或整窗 `#react-chat-window-shell` 不应作为当前高亮依据
5353

5454
## 3. 启动与提示状态
5555

@@ -127,10 +127,11 @@ window.createYuiGuideDirector = function createYuiGuideDirector(options) {}
127127

128128
### 6.1 前奏与输入激活
129129

130-
首页引导启动后,Yui 会先等待必要的用户交互以解锁浏览器音频播放。当前实现会围绕 React 聊天输入区、聊天窗口和语音按钮建立高亮与旁白
130+
首页引导启动后,Yui 会先等待必要的用户交互以解锁浏览器音频播放。当前实现会围绕 React compact 胶囊对话输入框和语音按钮建立高亮与旁白
131131

132-
`intro_basic` 会向聊天窗口追加教程消息,播放本地预录语音,并驱动表情轨道。外置 `/chat` 窗口模式下,教程消息和按钮锁定状态通过 `appInterpage` / BroadcastChannel 同步。
133-
首页普通模式的 prelude 激活提示是刻意例外:输入框上方的 overlay 气泡(例如“点一下这里,我就能开始说话啦~”)不进入聊天记录;正式教程旁白(例如 `intro_basic` 及后续旁白)才追加到对话窗。首页内嵌聊天直接 append 到 React chat;N.E.K.O.-PC 的外置 `/chat` 窗口通过 BroadcastChannel 注入教程消息,只有外置聊天窗通信失败时才退回 overlay 气泡兜底。
132+
`intro_basic` 会向 React chat 教程消息流追加内容,播放本地预录语音,并驱动表情轨道。首页普通模式的可见高亮目标是 compact 胶囊对话输入框;外置 `/chat` 窗口模式下,教程消息和按钮锁定状态通过 `appInterpage` / BroadcastChannel 同步。
133+
首页普通模式的 prelude 激活提示是刻意例外:compact 胶囊输入框上方的 overlay 气泡(例如“点一下这里,我就能开始说话啦~”)不进入聊天记录;正式教程旁白(例如 `intro_basic` 及后续旁白)才追加到 React chat。首页内嵌聊天直接 append 到 React chat;N.E.K.O.-PC 的外置 `/chat` 窗口通过 BroadcastChannel 注入教程消息,只有外置聊天窗通信失败时才退回 overlay 气泡兜底。
134+
教程消息进入 React compact 胶囊后,`yui-guide-` 消息应直接显示当前文本 patch,不等待普通助手语音消息的播放进度同步;教程终止/销毁时,仍需把活跃教程消息收尾为完整 `blocks` + `status: 'sent'`
134135

135136
教程锁开启时,首页嵌入 React chat 必须进入硬禁用状态:输入框 disabled/readOnly,发送、截图、图片导入、工具按钮、GalGame 选项和 ChoicePrompt 选项不可触发。宿主层 `handleComposerSubmit()` 也要检查同一锁,防止键盘提交、焦点残留或程序调用绕过组件状态。
136137

@@ -277,7 +278,7 @@ token 由后端签名、带 TTL、同源校验、单次消费。目标页消费
277278
- 移除 spotlight、precise highlight、Ghost Cursor、临时演出 DOM。
278279
- 移除 `yui-taking-over``yui-guide-plugin-dashboard-running` 等接管 class。
279280
- 停止或销毁语音队列、表情轨道、wakeup、监听器、计时器。
280-
- 解锁 React 聊天按钮和输入区
281+
- 解锁 React compact 胶囊输入框、聊天按钮和相关工具入口
281282
- 释放首页教程交互锁,并派发锁状态变化事件。
282283
- 恢复首页旧发送入口、React composer、截图、图片导入、剪贴板图片粘贴入口的正常可用状态。
283284
- 通知 WebSocket greeting 链路恢复 pending 检查;后端教程阻塞状态依赖 TTL 自动兜底,不能长期污染 greeting。
@@ -289,7 +290,7 @@ token 由后端签名、带 TTL、同源校验、单次消费。目标页消费
289290

290291
## 12. 修改时的注意点
291292

292-
- 不要把旧 `#chat-container` 当作活跃聊天 UI
293+
- 不要把旧 `#chat-container` 或整窗 `#react-chat-window-shell` 当作教程聊天高亮目标;普通首页模式和外置 `/chat` 模式都应优先高亮 compact 胶囊对话输入框
293294
- 不要把模型位置写成固定 `left/top` 或固定像素尺寸;当前以模型真实 bounds 和视口安全区为准。
294295
- 不要直接 resize 全局 Live2D renderer 来服务教程模型,否则会影响正常 Live2D 体验。
295296
- 采样猫爪二级面板按钮前,必须等面板布局稳定,尤其是右侧空间不足时面板会翻到左侧。

docs/design/home-yui-guide-lifecycle-modularization.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
1. 文档级交互守卫注册与销毁
7676
2. `overlay.setTakingOver()` 生命周期
7777
3. 首页接管期正脸锁 / 鼠标跟踪关闭与恢复
78-
4. 外置聊天窗按钮禁用与 spotlight 同步
78+
4. 外置聊天窗按钮禁用与 spotlight 同步;聊天 spotlight 由 Director 指向 compact 胶囊对话输入框
7979
5. 触控 passthrough 特判
8080

8181
它不负责:
@@ -152,7 +152,7 @@ this.setTutorialTakingOver(false)
152152
白名单判断仍然留在 Director 的页面语义层,例如:
153153
154154
1. skip 按钮
155-
2. 首页输入框激活
155+
2. 首页 compact 胶囊输入框激活
156156
3. 手动打开插件管理面板入口
157157
4. 系统弹窗
158158
@@ -412,7 +412,7 @@ restoreTutorialAvatarOverride() {
412412
2. 允许点击的白名单是否都在 `isAllowedTutorialInteractionTarget()`
413413
3. skip 后是否还能落到统一 `requestTutorialDestroy()`
414414
4. destroy / pagehide / remote terminate 时是否会走到 `restoreTutorialAvatarOverride()`
415-
5. 是否有外置聊天窗模式,需要同步按钮禁用或 spotlight
415+
5. 是否有外置聊天窗模式,需要同步按钮禁用或 spotlight;不要把聊天高亮目标退回旧整窗 shell
416416
417417
## 当前接入文件
418418

docs/design/home-yui-guide-text-highlight-cursor-flow.md

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@ takeover_return_control
3333
高亮流程:
3434

3535
1. `runWakeupPrelude()` 完成苏醒后进入聊天 intro。
36-
2. 普通首页模式调用 `ensureChatVisible()`,再用 `focusAndHighlightChatInput()` 把 persistent spotlight 放到聊天输入区
37-
3. 气泡锚定输入区,手动定位到输入框正上方
38-
4. 外置聊天窗模式不高亮首页输入区,改为 `setExternalizedChatSpotlight('window')`
36+
2. 普通首页模式调用 `ensureChatVisible()`,再用 `focusAndHighlightChatInput()` 把 persistent spotlight 放到 React compact 胶囊对话输入框
37+
3. 气泡锚定 compact 胶囊输入框,手动定位到胶囊正上方
38+
4. 外置聊天窗模式不高亮首页胶囊输入框,改为 `setExternalizedChatSpotlight('input')`,由独立 `/chat` 页高亮自己的 compact 胶囊输入框
3939

4040
ghost cursor 流程:
4141

42-
1. 普通首页模式读取输入区 rect。
43-
2. cursor 出现在输入区中心
44-
3. cursor 先 wobble,等待用户点击输入区完成激活
42+
1. 普通首页模式读取 compact 胶囊输入框 rect。
43+
2. cursor 出现在胶囊输入框中心
44+
3. cursor 先 wobble,等待用户点击胶囊输入框完成激活
4545
4. 用户激活后隐藏提示气泡,overlay 进入 taking-over 状态,cursor 再 wobble 一次。
4646

4747
注意:
@@ -55,17 +55,17 @@ ghost cursor 流程:
5555

5656
1. `tutorial.yuiGuide.lines.introGreetingReply`
5757
- 中文:“微风、阳光,还有刚刚好出现的你。初次见面,我是林悠怡……”
58-
2. 文本进入聊天窗口,语音 key 为 `intro_greeting_reply`
58+
2. 文本追加到 React chat 教程消息流,语音 key 为 `intro_greeting_reply`
5959

6060
高亮流程:
6161

62-
1. 这一段延续上一阶段输入区/聊天区的 spotlight 状态。
62+
1. 这一段延续上一阶段 compact 胶囊输入框的 spotlight 状态。
6363
2. 不打开新的业务面板。
6464
3. 不执行真实 UI 点击。
6565

6666
ghost cursor 流程:
6767

68-
1. 激活后 cursor 保持在输入区附近
68+
1. 激活后 cursor 保持在 compact 胶囊输入框附近
6969
2. 这一段主要由 YUI 模型演出承接,ghost cursor 不负责展示新 UI。
7070

7171
时间 cue:
@@ -79,17 +79,17 @@ ghost cursor 流程:
7979

8080
1. `tutorial.yuiGuide.lines.introBasic`
8181
- 中文:“这里有一个神奇的小按钮!只要点击它,就可以直接和我聊天啦!……”
82-
2. 文本进入聊天窗口,语音 key 为 `intro_basic`
82+
2. 文本追加到 React chat 教程消息流,语音 key 为 `intro_basic`
8383

8484
高亮流程:
8585

8686
1. `runIntroVoiceControlButtonShowcase()` 先调用 `highlightChatWindow()`
87-
2. persistent spotlight 放到聊天窗口;如果是外置聊天窗,则同步外置窗口 spotlight。
87+
2. 普通首页模式下,persistent spotlight 放到 React compact 胶囊对话输入框;如果是外置聊天窗,则同步独立 `/chat` 页的 compact 胶囊输入框 spotlight。
8888
3. action spotlight 放到语音控制按钮,也就是 `#${p}-btn-mic` 的圆形按钮 shell。
8989

9090
ghost cursor 流程:
9191

92-
1. 如果 cursor 还没有位置,先从输入区或默认原点出现
92+
1. 如果 cursor 还没有位置,先从 compact 胶囊输入框或默认原点出现
9393
2. cursor 在旁白前段移动到语音控制按钮中心。
9494
3. 移动时长按语音时长约 16% 估算,限制在 900-2200ms。
9595
4. 当前流程只展示按钮,不点击语音按钮。
@@ -98,7 +98,7 @@ ghost cursor 流程:
9898

9999
1. 进入语音入口展示时,director 调用 `YuiGuideAvatarStage.startIntroVoiceCursorLookAt()` 建立短 session,只锁 `lookAt`,不接管 `motion``expression`
100100
2. 该 session 期间打开 `window.nekoYuiGuideIntroVoiceLookAtActive`,只让第 2 段绕过新手教程正脸锁的视线归零逻辑。
101-
3. cursor 从输入区移动到语音控制按钮期间,模型面部和视线跟随 ghost cursor 当前坐标;实现上由 director 提供 `overlay.getCursorPosition()`,adapter 每帧把该点喂给 Live2D 的 `model.focus(x, y)`,并以 Live2D 头部/气泡锚点作为 LookAt 原点,在 Yui 专用 temporary pose 中直接写入 `ParamAngleX/Y/Z``ParamEyeBallX/Y``ParamBodyAngleX/Y/Z`
101+
3. cursor 从 compact 胶囊输入框移动到语音控制按钮期间,模型面部和视线跟随 ghost cursor 当前坐标;实现上由 director 提供 `overlay.getCursorPosition()`,adapter 每帧把该点喂给 Live2D 的 `model.focus(x, y)`,并以 Live2D 头部/气泡锚点作为 LookAt 原点,在 Yui 专用 temporary pose 中直接写入 `ParamAngleX/Y/Z``ParamEyeBallX/Y``ParamBodyAngleX/Y/Z`
102102
4. cursor 到达语音控制按钮并短暂停留时,模型面部和视线也停在按钮方向,强化“她在看这个按钮”的因果关系。
103103
5. Live2D 的 temporary pose override 会在原始 `coreModel.update()` 后再应用一次,确保本段 ghost cursor LookAt 不被正脸锁、SDK 原始 update、motion 或 focusController 在同一帧覆盖掉。
104104
6. 语音入口展示结束后,director 在 `finally` 中停止 handle;adapter 清理本段 temporary pose、恢复捕获到的参数、清零 Live2D `focusController`,并关闭 `nekoYuiGuideIntroVoiceLookAtActive`,让教程正脸锁继续接管后续阶段。
@@ -114,13 +114,13 @@ ghost cursor 流程:
114114

115115
1. `tutorial.yuiGuide.lines.takeoverCaptureCursor`
116116
- 中文:“超级魔法开关出现!只要点一下这里,我就可以把小爪子伸到你的键盘和鼠标上啦!……”
117-
2. 文本进入聊天窗口,语音 key 为 `takeover_capture_cursor`
117+
2. 文本追加到 React chat 教程消息流,语音 key 为 `takeover_capture_cursor`
118118
3. 旁白和 UI 自动操作并行执行。
119119

120120
高亮流程:
121121

122122
1. 场景开始时 overlay 进入 taking-over。
123-
2. persistent spotlight 先回到聊天窗口,表示台词仍在聊天区输出
123+
2. persistent spotlight 先回到 compact 胶囊对话输入框,表示台词仍属于聊天上下文
124124
3. 猫爪按钮 `#${p}-btn-agent` 被作为 retained extra spotlight 保留。
125125
4. 点击猫爪后打开猫爪/Agent 面板。
126126
5. 猫爪总开关 `agent-master` 用虚拟 spotlight `takeover-agent-master-toggle` 扩大高亮范围。
@@ -155,7 +155,7 @@ ghost cursor 流程:
155155

156156
1. `tutorial.yuiGuide.lines.takeoverPluginPreviewHome`
157157
- 中文:“还没完呢!你快看快看,这里还有超多好玩的插件呢!”
158-
2. 文本进入聊天窗口,语音 key 为 `takeover_plugin_preview_home`
158+
2. 文本追加到 React chat 教程消息流,语音 key 为 `takeover_plugin_preview_home`
159159

160160
高亮流程一:
161161

@@ -177,7 +177,7 @@ ghost cursor 流程一:
177177

178178
1. `tutorial.yuiGuide.lines.takeoverPluginPreviewDashboard`
179179
- 中文:“有了它们,我不光能看 B 站弹幕,还能帮你关灯开空调……”
180-
2. 插件 dashboard 打开后,文本仍追加到聊天窗口
180+
2. 插件 dashboard 打开后,文本仍追加到 React chat 教程消息流
181181
3. 语音 key 为 `takeover_plugin_preview_dashboard`
182182

183183
高亮流程二:
@@ -205,13 +205,13 @@ ghost cursor 流程二:
205205

206206
1. `tutorial.yuiGuide.lines.takeoverSettingsPeekIntro`
207207
- 中文:“当然啦,如果你想让本喵多和你聊聊天,也不是不行啦……设置都在这个齿轮里。”
208-
2. 文本进入聊天窗口,语音 key 为 `takeover_settings_peek_intro`
208+
2. 文本追加到 React chat 教程消息流,语音 key 为 `takeover_settings_peek_intro`
209209

210210
高亮流程一:
211211

212212
1. 场景开始先关闭猫爪/Agent 面板。
213213
2. settings 按钮 `#${p}-btn-settings` 被设置为圆形 spotlight,并作为 retained extra spotlight 保留。
214-
3. persistent spotlight 仍放在聊天窗口
214+
3. persistent spotlight 仍放在 compact 胶囊对话输入框
215215
4.`openSettingsPanel` 语音 cue 时,action spotlight 放到 settings 按钮。
216216

217217
ghost cursor 流程一:
@@ -227,7 +227,7 @@ ghost cursor 流程一:
227227
2. `tutorial.yuiGuide.lines.takeoverSettingsPeekDetailPart2`
228228
- 中文:“等一下!你在干嘛?该不会是想把我换掉吧?啊啊啊不行!快关掉,快关掉!”
229229
3. 语音 key 统一为 `takeover_settings_peek_detail`
230-
4. 第一段先以流式消息进入聊天窗口,第二段在 `showSecondLine` cue 到达时追加。
230+
4. 第一段先以流式消息追加到 React chat 教程消息流,第二段在 `showSecondLine` cue 到达时追加。
231231

232232
高亮流程二:
233233

@@ -267,7 +267,7 @@ ghost cursor 流程二:
267267

268268
1. `tutorial.yuiGuide.lines.takeoverReturnControl`
269269
- 中文:“好啦好啦,不霸占你的电脑啦!控制权还给你了喵!……”
270-
2. 文本进入聊天窗口,语音 key 为 `takeover_return_control`
270+
2. 文本追加到 React chat 教程消息流,语音 key 为 `takeover_return_control`
271271
3. 语音播放到 70% 时触发 `returnPetalTransition` cue。
272272

273273
高亮流程:
@@ -311,7 +311,7 @@ ghost cursor 流程:
311311
- 中文:“喂!不要拽我啦,现在还没轮到你的回合呢!”
312312
2. 后续可能使用 `tutorial.yuiGuide.lines.interruptResistLight3`
313313
- 中文:“等一下啦!还没结束呢,不要这么随便打断我啦!”
314-
3. 文本进入聊天窗口,不等待当前 scene 的流式暂停。
314+
3. 文本追加到 React chat 教程消息流,不等待当前 scene 的流式暂停。
315315

316316
高亮流程:
317317

@@ -338,7 +338,7 @@ ghost cursor 流程:
338338

339339
1. `tutorial.yuiGuide.lines.interruptAngryExit`
340340
- 中文:“人类!你真的很没礼貌喵!既然你这么想自己操作……”
341-
2. 文本进入聊天窗口,允许 angry exit 期间继续流式输出。
341+
2. 文本追加到 React chat 教程消息流,允许 angry exit 期间继续流式输出。
342342

343343
高亮流程:
344344

@@ -355,7 +355,7 @@ ghost cursor 流程:
355355

356356
## 高亮类型速查
357357

358-
1. persistent spotlight:持续高亮当前讲解上下文,例如聊天窗口、输入区
358+
1. persistent spotlight:持续高亮当前讲解上下文,例如 compact 胶囊对话输入框或当前业务面板
359359
2. action spotlight:当前 cursor 要移动/点击的主要目标,例如猫爪按钮、开关、设置按钮。
360360
3. secondary spotlight:辅助目标,主要由 `applyGuideHighlights()` 支持。
361361
4. retained extra spotlight:跨多个动作保留的目标,例如猫爪按钮、settings 按钮。
@@ -365,11 +365,11 @@ ghost cursor 流程:
365365

366366
## 维护规则
367367

368-
1. 新增教程台词时,先确认文本输出位置:聊天窗口、overlay 气泡,还是外部页面 handoff。
368+
1. 新增教程台词时,先确认文本输出位置:React chat 教程消息流、overlay 气泡,还是外部页面 handoff。聊天上下文高亮优先落到 compact 胶囊对话输入框;外置 `/chat` 模式也应同步 `input` spotlight,不要回退到旧整窗高亮
369369
2. 每段台词最多有一个主 persistent spotlight;多个 UI 目标应使用 action/secondary/extra,而不是反复重设 persistent。
370370
3. ghost cursor 的移动必须跟真实 UI 操作一致:先高亮,再移动,再 click,再调用真实打开/开关 API。
371371
4. 不能只移动 cursor 而不执行真实状态变更,也不能只改状态而没有可见 click 反馈。
372372
5. dashboard、settings 等跨面板流程结束时必须清理 retained、virtual、scene extra 和 action spotlight。
373373
6. 打断分支要暂停并恢复当前 scene,不能把抵抗文本当成新的主线 step。
374374
7. 如果某个目标找不到,当前流程应安全跳过或走 fallback,不能卡死教程。
375-
8. 外置聊天窗模式没有首页输入框激活,但后续台词、高亮和 takeover 主线仍继续。
375+
8. 外置聊天窗模式没有首页 compact 胶囊输入框激活,但后续台词、高亮和 takeover 主线仍继续。

0 commit comments

Comments
 (0)