Files
eryao/docs/bugs/2026-04-08-followup-loading-and-voice-bug.md
T
qzl e80a82bef4 docs: 更新协议文档,删除废弃计划文档
- 更新 http-error-codes, user-points-chat-data-protocol
- 更新 divination-run-protocol, profile-protocol
- 删除废弃的后端和前端设计计划文档
2026-04-08 17:23:02 +08:00

262 lines
7.6 KiB
Markdown
Raw 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.
# Bug: 追问页面 UI 问题
日期:2026-04-08
状态:已确认(未修复)
## Bug 1: 追问页发送时顶部和消息下方重复加载UI
### 问题描述
在追问页面,用户输入信息发送后,顶部和消息下方同时出现加载UI,但顶部的加载UI是多余的。
### 根因分析
**文件**`apps/lib/features/divination/presentation/screens/follow_up_chat_screen.dart`
**问题代码**
1. 顶部 step 指示器(第 85-124 行):
```dart
if (_sending && _currentStepName != null)
Container(
// 显示 step 进度,如 "解读中..."、"推理中..."
child: Row(
children: [
CircularProgressIndicator(...),
Text(_stepLabel(_currentStepName!)),
],
),
)
```
2. 消息下方 streaming placeholder(第 565-584 行):
```dart
isStreamingPlaceholder
? Row(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(...),
Text(l10n.followUpGenerating), // "生成中..."
],
)
```
**事件触发流程**
| 顺序 | 事件 | 顶部指示器 | 消息下方 |
|------|------|----------|---------|
| 1 | `_submitText()` 调用 | 无 | streaming placeholder 显示 "生成中..." |
| 2 | `STEP_STARTED(stepName='divination')` | 显示 "解读中..." | 继续显示 |
| 3 | `STEP_FINISHED` | 消失 | 继续显示 |
| 4 | `STEP_STARTED(stepName='worker')` | 显示 "推理中..." | **同时显示** "生成中..." |
| 5 | `STEP_FINISHED` | 消失 | 继续显示 |
| 6 | `TEXT_MESSAGE_END` | 无 | placeholder 消失,显示真实内容 |
**问题**:在 worker 阶段(步骤 4),顶部显示 "推理中..." 同时消息下方显示 "生成中...",造成重复反馈。用户只需要一个加载反馈即可。
**修复方向**
1. **方案A**:只在 divination 阶段显示顶部 step 指示器,worker 阶段隐藏(因为 streaming placeholder 已经提供反馈)
```dart
// 第 85 行修改
if (_sending && _currentStepName != null && _currentStepName != 'worker')
```
2. **方案B**:移除顶部 step 指示器,完全依赖 streaming placeholder
3. **方案C**:在 worker 阶段隐藏 streaming placeholder,只显示顶部指示器
推荐 **方案A**,因为:
- divination 阶段没有 streaming placeholder,需要顶部指示器提供反馈
- worker 阶段有 streaming placeholder,不需要顶部指示器
- 改动最小,只加一个条件
---
## Bug 2: 语音模式录制时缺少动画UI
### 问题描述
根据 social-app 的实现,按住说话模式下录音时应该有动画效果,但 eryao 的实现只有文字,没有动画。
### 根因分析
**对比**
| 功能 | social-app | eryao |
|------|------------|-------|
| 录音中动画 | `recordingAnimation` widget(外部传入) | 只有文字 "录音中..." |
| 录音提示文字 | 带动画的 widget | 只有文字 |
| 录音中隐藏输入区域 | `IgnorePointer` + `Opacity` | 未实现 |
**eryao 当前实现**`message_composer.dart:159-172`):
```dart
if (_isRecording) {
if (!showRecordingInlineFeedback) {
return Text(recordingText, ...);
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(width: 16, height: 16), // 空白占位
const SizedBox(height: AppSpacing.xs),
Text(recordingText, ...), // "录音中..."
const SizedBox(height: AppSpacing.xs),
Text(recordingHintText, ...), // "上滑取消"
],
);
}
```
**social-app 实现**`message_composer.dart:222-239`):
```dart
if (_isRecording) {
if (!showRecordingInlineFeedback) {
return Text(resolvedRecordingText, ...);
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
recordingAnimation, // 动画widget(外部传入)
const SizedBox(height: AppSpacing.xs),
Text(resolvedRecordingText, ...),
const SizedBox(height: AppSpacing.xs),
Text(resolvedRecordingHintText, ...),
],
);
}
```
**差异**
1. `recordingAnimation` widget 缺失 - 需要外部传入,eryao 直接用空白占位
2. 录音中图标/按钮没有动画效果
**修复方向**
1. 在 `FollowUpChatScreen` 或其父组件中创建 `recordingAnimation` widget
2. 将 `recordingAnimation` 通过 `MessageComposer` 传入
3. 参考 social-app 的实现,使用脉冲动画或波形动画
**参考实现**(需要添加 `recording_animation.dart`):
```dart
class RecordingAnimation extends StatefulWidget {
final double size;
final Color color;
@override
State<RecordingAnimation> createState() => _RecordingAnimationState();
}
class _RecordingAnimationState extends State<RecordingAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
)..repeat(reverse: true);
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.2).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _scaleAnimation,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Container(
width: widget.size,
height: widget.size,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: widget.color.withValues(alpha: 0.3),
),
child: Icon(Icons.mic, color: widget.color, size: widget.size * 0.6),
),
);
},
);
}
}
```
---
## Bug 2 & 3: 直接复用 social-app 输入框(不需要 plus 按钮)
### 需求
**直接复用** social-app 的 `MessageComposer`,但**不需要 plus 按钮**。
### 复用清单
| 功能 | social-app | eryao | 处理 |
|------|------------|-------|------|
| `recordingAnimation` | required Widget | 空白占位 | **需要添加** |
| 双层阴影 | 有 | 无 | **需要添加** |
| `AppRadius.xxl` | 用于圆角 | 无此值 | **需要添加** |
| Plus 按钮 | 有 | 无 | **不需要**,保持无 |
| 图标 | `LucideIcons` | Material Icons | 保持 Material Icons |
### 实现步骤
#### 1. 添加 `AppRadius.xxl` 到 `design_tokens.dart`
```dart
class AppRadius {
static const double sm = 8;
static const double md = 12;
static const double lg = 16;
static const double xl = 20;
static const double xxl = 32; // 新增
static const double full = 999;
}
```
#### 2. 创建 `RecordingAnimation` widget
放在 `apps/lib/shared/widgets/` 下,参考 social-app 的脉冲动画效果。
#### 3. 修改 `MessageComposer`
- 添加 `recordingAnimation` 参数(required
- 添加双层阴影
- 使用 `AppRadius.xxl` 替代 `AppRadius.full`
- 移除 plus 按钮(eryao 原有的无 plus 逻辑保持不变)
- 保持 Material Icons
#### 4. 修改 `FollowUpChatScreen`
- 创建 `RecordingAnimation` 实例
- 传入 `MessageComposer`
---
## 相关文件
- `apps/lib/features/divination/presentation/screens/follow_up_chat_screen.dart`
- `apps/lib/shared/widgets/message_composer.dart`
- `apps/lib/shared/widgets/recording_animation.dart`(新建)
- `apps/lib/shared/theme/design_tokens.dart`
- `/home/qzl/Code/social-app/apps/lib/shared/widgets/message_composer.dart`
## 修复优先级
1. **高优先级**Bug 1(重复加载UI
2. **高优先级**Bug 2 & 3(直接复用 social-app 输入框)