docs: 心愿实现 chat 视图 loading 标识修复设计

This commit is contained in:
2026-07-20 00:16:05 +08:00
parent 4884099b9b
commit 95ce23f172
@@ -0,0 +1,109 @@
---
author: AI Assistant
created_at: 2026-07-19
purpose: 修复心愿实现 chat 视图在等待接口响应期间没有 loading 标识、呈现空白等待状态的问题
---
# 心愿实现 chat 视图 Loading 标识修复设计
## 概述
心愿实现页面(`ScriptView.vue` 的 chat 视图)在用户发送消息后、等待后端 SSE 接口响应的阶段,页面处于空白状态,没有任何 loading 标识,无法判断是否卡住。需要在等待期间展示动态 loading,提升等待体验。
## 问题根因
`ScriptView.vue` 中 chat-loading 组件的显示条件(line 177):
```html
<view v-if="generating && generationStatus !== 'failed'" class="chat-loading">
```
`runGeneration` 函数(line 1618-1672)中:
```js
generating.value = true // Line 1630
// ...
try {
currentStreamTask.value = startNovelStream({...}) // Line 1653 - 异步 fire-and-forget
} finally {
generating.value = false // Line 1671 - 立即执行!
}
```
**Bug**`startNovelStream` 是异步流式调用(fire-and-forget,没有 await),`finally` 在流启动后**立即**把 `generating` 设回 `false`。结果:
1. 用户点发送 → `generating = true` → 切换到 chat 视图
2. 几乎同时 → `finally` 执行 → `generating = false` → chat-loading 立即隐藏
3. 在第一个 SSE 事件(`status`)到达前,页面只有用户消息气泡,下方空白
4. 用户看到"发送 → 空白等待 → 突然跳出内容",无法判断系统是否在工作
## 修复方案
**用新的 computed `showChatLoading` 替换原有显示条件**,语义精确匹配"等待首个 assistant 响应"
```js
const showChatLoading = computed(() => {
if (generationPhase.value !== 'generating') return false
if (generationStatus.value === 'failed') return false
const lastMsg = resultMessages.value[resultMessages.value.length - 1]
// 最后一条消息是 user(刚发送的请求),说明还没收到 assistant 响应
return lastMsg && lastMsg.role === 'user'
})
```
```html
<view v-if="showChatLoading" class="chat-loading">
<view class="loading-orbit" :class="{ streaming: generationStatus === 'streaming' }">
<view class="orbit-ring outer"></view>
<view class="orbit-ring inner"></view>
<view class="orbit-core"></view>
</view>
<text class="loading-copy">{{ generationCopy }}</text>
</view>
```
### 场景覆盖表
| 场景 | generationPhase | 最后一条消息 | loading 显示 |
|---|---|---|---|
| 用户刚发送 | 'generating' | user | ✅ 显示 |
| SSE status 事件(processing 阶段)| 'generating' | user | ✅ 显示 |
| `clarification_card` 到达 | 'generating' | assistant (card) | ❌ 隐藏 |
| 用户回答澄清问题 | 'generating' | user | ✅ 显示 |
| `outline_created` 到达 | 'generating' | assistant (outline) | ❌ 隐藏 |
| 用户提交大纲修改意见 | 'generating' | user | ✅ 显示 |
| `novel_start` 到达 | 'generating' | assistant (novel) | ❌ 隐藏 |
| `novel_done` 到达 | 'done' | assistant | ❌ 隐藏 |
### 为什么这样设计
1. **用 `generationPhase` 而非 `generating`**`generationPhase``runGeneration` 开始时设为 'generating',在 `novel_done` 时设为 'done',完整覆盖生成流程生命周期。`generating` 因为 try-finally 立即重置,不可靠。
2. **用最后一条消息的 role 判断**assistant 消息(card/outline/novel)到达后,用户看到的是交互内容而非空白,loading 应自动隐藏。通过判断最后一条消息的 role,精确实现"等待首个响应"的语义。
3. **支持多轮交互**:用户回答澄清问题、提交大纲修改后,最后一条又变回 user,loading 自动重新显示。无需额外的 flag 管理。
## 改动文件
- `mini-program/src/pages/main/ScriptView.vue`
- 新增 computed `showChatLoading`(在 `generationCopy` 附近,约 line 930-950
- 替换 line 177 的 `v-if` 条件
## 不影响范围
- `chat-loading` 组件的样式、动画内容保持不变
- `generating` 变量的其他用途(按钮禁用、重试逻辑等)不变
- `generationStatus``generationPhase``generationCopy` 等 computed 不变
- `home` 视图、`generation` 视图、`chat-input-bar` 不受影响
- `sendChat``sendRewrite``sendChatAsContinue` 等其他流程不受影响(它们本身有 await,generating 状态正确)
## 验收标准
- [ ] H5`http://localhost:5180`):点击发送后,chat 视图立即显示 loading 动画(loading-orbit + generationCopy 文本)
- [ ] Loading 持续到 `clarification_card` / `outline_created` / `novel_start` 任一事件到达前
- [ ] 澄清卡片 / 大纲 / 小说消息出现后,loading 自动隐藏
- [ ] 用户回答澄清、提交大纲修改后,loading 重新显示直到下一个 assistant 响应到达
- [ ] 失败状态(`generationStatus === 'failed'`)不显示 loading,显示 resume 按钮
- [ ] 浏览器 Console 无新增错误
- [ ] mp-weixin 编译通过(`npm run build:mp-weixin` 无 error
- [ ] 编译产物中 `.chat-loading` 的 v-if 使用 `showChatLoading`