e80a82bef4
- 更新 http-error-codes, user-points-chat-data-protocol - 更新 divination-run-protocol, profile-protocol - 删除废弃的后端和前端设计计划文档
262 lines
7.6 KiB
Markdown
262 lines
7.6 KiB
Markdown
# 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 输入框)
|