1
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-18 19:40:33 +08:00
parent 54b3d3c490
commit 8c3fbd9bcf
15 changed files with 904 additions and 65 deletions

View File

@@ -0,0 +1,471 @@
# AI 原生 NPC 单轮聊天会话迭代 PRD
更新时间:`2026-04-18`
## 0. 文档目的
本 PRD 只定义 `npc_chat` 在冒险主面板中的这一轮迭代落地方式。
目标不是新建一套聊天系统,而是在保持现有游戏 UI 外壳、剧情面板结构、消息流位置不变的前提下,把 `npc_chat` 从“触发一次后回到普通剧情流”升级为“进入可持续续写的单轮聊天会话”。
本次文档必须直接指导编码,避免需求落地漂移。
---
## 1. 一句话定义
玩家点击 `npc_chat` 后,进入 NPC 聊天模式;每次只完成一轮“玩家输入 -> NPC 回复 -> 关系变化消息 -> 下一轮 3 个建议选项 + 1 个自定义输入”,直到玩家主动退出聊天。
---
## 2. 背景与问题
当前 `npc_chat` 更接近“触发一段剧情性对话”,而不是“持续聊天”:
1. 玩家点击聊天后,常常是一次性生成较长结果,再回到普通冒险选项。
2. 缺少稳定的续聊状态,无法把多轮聊天作为一个连续会话维持。
3. 好感度变化更多停留在逻辑层,玩家在消息流中感知不明显。
4. 没有单独的退出聊天控制,用户只能被动等系统切回普通状态。
5. 选项形态仍偏剧情动作,不够像聊天接话。
这会导致 `npc_chat` 的体验不像“和角色对话”,而像“触发一个剧情功能”。
---
## 3. 本次目标
本次迭代必须同时满足以下目标:
1. `npc_chat` 触发后进入聊天交互态。
2. 聊天态沿用当前主冒险面板,不新增页面、不弹新系统。
3. 每次用户只推进一轮对话。
4. 每轮结束后稳定出现 `3` 个建议续聊选项。
5. 每轮结束后稳定出现 `1` 个自定义输入框。
6. 玩家选择建议项或提交自定义输入后,继续在同一消息队列中续写。
7. 好感度增减必须作为“系统消息”插入到对话消息队列中。
8. NPC 回复必须支持流式传输,并在前端边接收边解析显示。
9. 背包按钮所在行的最右侧必须新增“退出聊天”按钮。
10. 退出聊天后恢复普通冒险态,不保留当前聊天输入框与聊天建议项。
---
## 4. 明确不做
本次不做:
1. 不新建独立聊天页面。
2. 不重做现有主面板 UI 结构。
3. 不引入多 NPC 并行聊天。
4. 不做聊天记录存档面板。
5. 不做复杂关系公式配置后台。
6. 不做语音输入、表情、附件等扩展输入能力。
7. 不把前端改成承担关系计算或剧情判定。
---
## 5. 核心体验
## 5.1 进入聊天
当玩家点击 `npc_chat` 选项时:
1. 保持当前冒险面板布局不变。
2. 中部内容区切换为聊天消息流展示模式。
3. 底部选项区切换为聊天建议区。
4. 底部附加一个自定义输入框与发送按钮。
5. 顶层不跳转、不弹窗、不覆盖成新页面。
## 5.2 单轮推进
单轮定义固定为:
1. 玩家通过“建议选项”或“自定义输入”提交一句话。
2. 玩家消息立即进入消息队列。
3. NPC 回复开始流式显示。
4. 流式结束后,如果有关系变化,插入一条系统消息。
5. 系统刷新下一轮 `3` 个建议选项。
6. 系统保留自定义输入入口,等待下一轮。
## 5.3 退出聊天
玩家点击“退出聊天”后:
1. 当前聊天态结束。
2. 面板底部恢复普通冒险选项区域。
3. 本轮聊天输入框、聊天建议项消失。
4. 当前故事内容回到普通故事展示逻辑。
退出聊天不触发额外确认弹窗。
---
## 6. UI 设计要求
## 6.1 保持不变的部分
以下 UI 外壳保持当前实现:
1. 主冒险面板整体框架。
2. 对话消息区所在位置。
3. 底部操作区的整体层级。
4. 队伍按钮、背包按钮的视觉风格。
## 6.2 必须变化的部分
### 消息区
1. 聊天态下,消息区按时间顺序展示:
- 玩家消息
- NPC 消息
- 系统关系变化消息
2. 系统关系变化消息必须和普通消息共用同一消息流容器。
3. 系统关系变化消息视觉上应与玩家/NPC 气泡有明确区分。
### 底部按钮区
1. 左侧仍保留队伍、背包按钮。
2. 右侧在聊天态下展示“退出聊天”按钮。
3. “退出聊天”按钮必须位于该行最右侧。
4. 非聊天态下,该位置仍保持原有刷新选项按钮逻辑。
### 选项区
1. 聊天态下只展示 `3` 个续聊选项。
2. 聊天态下不展示普通剧情选项附带的说明文案、目标提示、影响摘要。
3. 聊天态下选项文案本身就是“下一句怎么接”。
### 输入区
1. 聊天态下在 3 个建议项下方展示输入框。
2. 输入框右侧固定展示发送按钮。
3. 输入框在请求进行中禁用。
4. 点击发送或回车提交时,进入下一轮。
## 6.3 移动端要求
1. 移动端优先保证输入框与发送按钮可点击。
2. 三个建议选项必须保持纵向堆叠,不做横向卡片排布。
3. “退出聊天”按钮在小屏下仍需完整可见,不允许被背包按钮挤出。
4. 输入框与发送按钮在窄屏下优先保证输入框宽度,其次压缩按钮内边距。
---
## 7. 前后端职责边界
遵循“前端只负责表现,逻辑和数据放后端”原则。
## 7.1 前端职责
前端只负责:
1. 进入/退出聊天态的 UI 切换。
2. 渲染当前消息队列。
3. 发送玩家本轮输入。
4. 接收流式事件并实时更新 NPC 当前回复文本。
5. 渲染系统关系变化消息。
6. 渲染下一轮 `3` 个建议项与自定义输入框。
前端不负责:
1. 生成 NPC 回复文本。
2. 生成建议续聊选项。
3. 计算关系增减。
4. 解析剧情逻辑分支。
## 7.2 后端职责
后端负责:
1. 接收 NPC 单轮聊天请求。
2. 结合当前世界、角色、NPC 状态、历史消息构建 prompt。
3. 流式生成 NPC 回复。
4. 解析回复内容。
5. 生成下一轮 3 个建议续聊选项。
6. 计算并返回本轮关系增减。
7. 通过 SSE 向前端发送流式事件与最终结果。
---
## 8. 数据结构要求
## 8.1 前端故事态扩展
`StoryMoment` 需要具备聊天态附加状态:
```ts
type StoryNpcChatState = {
npcId: string;
npcName: string;
turnCount: number;
customInputPlaceholder?: string;
};
```
要求:
1. 仅当当前故事处于 NPC 聊天模式时写入。
2. `turnCount` 表示已完成的轮数。
3. `npcId` 用于保证只续写当前聊天对象。
## 8.2 聊天消息结构
消息队列需要支持系统消息:
```ts
type StoryDialogueTurn = {
speaker: 'player' | 'npc' | 'companion' | 'system';
speakerName?: string;
text: string;
affinityDelta?: number;
};
```
要求:
1. `system` 只用于关系变化、系统反馈类消息。
2. `affinityDelta` 仅在关系变化消息中写入。
## 8.3 单轮接口契约
请求:
```ts
type NpcChatTurnRequest = {
worldType: WorldType;
player: Character;
encounter: Encounter;
history: StoryMoment[];
dialogue: StoryDialogueTurn[];
playerMessage: string;
npcState: {
affinity: number;
chattedCount: number;
recruited: boolean;
};
context: StoryGenerationContext;
};
```
返回完成事件载荷:
```ts
type NpcChatTurnResult = {
npcReply: string;
affinityDelta: number;
affinityText: string;
suggestions: string[];
};
```
---
## 9. 流式协议
本次统一使用 SSE。
## 9.1 事件类型
后端至少输出以下事件:
1. `reply_delta`
2. `complete`
3. `error`
## 9.2 事件含义
### `reply_delta`
用途:
1. 逐步推送 NPC 当前回复文本增量。
2. 前端收到后立即更新消息流中“当前 NPC 气泡”的文本。
### `complete`
用途:
1. 标记本轮流式输出结束。
2. 一次性返回最终结果对象:
- `npcReply`
- `affinityDelta`
- `affinityText`
- `suggestions`
### `error`
用途:
1. 标记本轮失败。
2. 前端停止流式态并回退到可继续输入的状态。
## 9.3 前端解析规则
1. 当收到第一个 `reply_delta` 时,若消息流末尾还没有 NPC 临时消息,前端先插入一条空的 NPC 消息。
2. 每次收到 `reply_delta`,替换该临时 NPC 消息文本。
3. 收到 `complete` 后,把临时 NPC 消息固化为最终文本。
4. 如果 `affinityDelta !== 0`,在 NPC 消息后追加一条系统关系消息。
5. 之后再刷新下一轮建议选项。
---
## 10. 关系变化显示规则
## 10.1 显示位置
关系变化必须作为一条独立消息插入消息队列,位置在本轮 NPC 回复之后、下一轮建议项之前。
## 10.2 文案规则
1. 有增长时显示正向文案,例如:`关系升温 好感 +3`
2. 有下降时显示负向文案,例如:`关系转冷 好感 -2`
3. 无变化时不强制插入系统消息。
## 10.3 视觉规则
1. 关系变化消息居中显示。
2. 关系变化消息使用不同于普通对话气泡的视觉样式。
3. 正向变化可使用更暖色的边框/底色。
4. 负向变化与中性反馈使用系统消息样式。
---
## 11. 交互流程
## 11.1 进入流程
```text
玩家点击 npc_chat
-> 系统进入聊天态
-> 当前故事切换为聊天消息模式
-> 展示本轮可选接话
-> 展示自定义输入框
```
## 11.2 单轮流程
```text
玩家点击建议项或提交输入
-> 玩家消息立即入队
-> 前端发起 /runtime/chat/npc/turn/stream
-> 后端流式返回 NPC 回复
-> 前端边接收边渲染 NPC 当前回复
-> complete 返回最终结果
-> 前端插入关系变化系统消息
-> 前端刷新下一轮 3 个建议项
-> 等待下一轮输入
```
## 11.3 退出流程
```text
玩家点击退出聊天
-> 清理当前聊天态标记
-> 当前故事回到普通冒险展示
-> 恢复普通选项区
```
---
## 12. 状态更新规则
每完成一轮聊天,系统必须更新:
1. `npcState.affinity`
2. `npcState.chattedCount`
3. `npcState.relationState`
4. `npcState.stanceProfile`
5. `storyHistory`
6. `currentStory.dialogue`
7. `currentStory.npcChatState.turnCount`
要求:
1. 当前轮玩家输入和 NPC 回复都要进入故事历史。
2. 关系变化消息属于展示型系统消息,可进入当前对话队列,但不要求作为独立剧情行动历史参与模型推理。
---
## 13. 异常与兜底
## 13.1 流式失败
如果流式失败:
1. 当前轮玩家消息保留。
2. 不保留半截乱码式 NPC 文本。
3. 前端恢复可继续输入状态。
4. 使用后端或前端兜底建议项,保证仍有 3 个建议续聊选项可选。
## 13.2 建议项不足
如果后端返回建议项不足 `3` 条:
1. 由后端优先补齐兜底话术。
2. 前端只展示最多 `3` 条。
## 13.3 空输入
空输入、纯空格输入不发请求。
---
## 14. 代码落点
本次迭代应优先落在现有链路:
### 前端
1. `src/hooks/story/npcEncounterActions.ts`
2. `src/hooks/story/useStoryInteractionCoordinator.ts`
3. `src/hooks/story/useStoryFlowCoordinator.ts`
4. `src/hooks/useStoryGeneration.ts`
5. `src/services/aiService.ts`
6. `src/components/AdventurePanel.tsx`
7. `src/components/GameShell.tsx`
### 共享契约
1. `packages/shared/src/contracts/story.ts`
### 后端
1. `server-node/src/routes/runtimeRoutes.ts`
2. `server-node/src/services/chatService.ts`
3. `server-node/src/modules/ai/chatPromptBuilders.ts`
4. `server-node/src/modules/ai/chatOrchestrator.ts`
要求:
1. 复用现有故事流、运行时接口和主面板。
2. 不另起新页面、新 store、新聊天系统。
---
## 15. 验收标准
以下全部满足才算通过:
1. 点击 `npc_chat` 后,面板进入聊天态而不是跳去新页面。
2. 当前消息区能连续展示玩家消息、NPC 消息、系统关系消息。
3. 每轮结束后稳定出现 `3` 个建议项。
4. 每轮结束后稳定出现 `1` 个自定义输入框。
5. 点击建议项可继续下一轮。
6. 输入自定义文本后点击发送或回车可继续下一轮。
7. NPC 回复支持流式逐字/逐段显示。
8. 好感度变化会以消息形式插入聊天队列。
9. 背包按钮所在行最右侧可见“退出聊天”按钮。
10. 点击“退出聊天”后恢复普通冒险态。
11. 聊天态下不显示普通剧情选项的说明、目标提示、影响摘要。
12. 移动端下输入框、发送按钮、退出按钮均可正常操作。
---
## 16. 后续可扩展方向
本次上线后,再考虑后续迭代:
1. 更精细的关系变化公式。
2. 基于 NPC 性格的建议续聊风格差异。
3. 聊天摘要沉淀到长期记忆。
4. 聊天中触发支线、任务、物品、邀约等事件。
5. 退出聊天后保留“最近一次聊天摘要”。