Files
happy-life-star/docs/superpowers/specs/2026-07-19-chat-view-loading-fix-design.md

110 lines
5.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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`