feat: 全面UI改造 — 报告/饮食/运动/用药/侧边栏/对话流/胶囊
- 报告模块:重写AI解读页,统一白色卡片+三栏布局(指标→解读→医生审核);加删除功能+后端删接口;VLM自动识别报告类型生成中文标题;去五颜六色
- 饮食分析:全面重设计,暖橙主题配色,图片自适应,餐次emoji选择器,去紫色
- 运动确认卡片:修复duration_minutes JSON类型转换,支持中文数字识别(三十分钟/半小时→30);主区域只显示运动名,字段行改为横向排列
- 流式输出:简化为最基础逐字追加,去buffer+Timer+淡入动画
- 欢迎卡片:每智能体独立渐变色(青/橙/蓝/紫/绿/粉),加卡片入场滑入+淡入动画(600ms)
- 确认卡片:头部去紫色背景改浅灰白,字段行去图标改纯信息横排,编辑框去双重边框
- 用药管理:胶囊改TabBar,添加按钮改右下FAB;打卡按钮改对号圆圈形式;药丸图标白底
- 侧边栏:加VIP服务/保险栏+图标,标题字体放大;服务包去查看更多+去VIP金色标签
- 胶囊:首页智能体胶囊白底深色字; side胶囊加阴影
- 对话流:reverse=false,今日健康出现在顶部; 对话页input bar匹配主页面样式
- 其他:个人资料去紫色+去多余入口;设置页白底图标;蓝牙页加返回按钮;报告管理改名
- 后端:加DELETE /api/reports/{id}; 修复manage_exercise数据类型转换;AI提示优化中文数字
This commit is contained in:
@@ -77,8 +77,7 @@ ActiveAgent _parseAgent(String? type) {
|
||||
|
||||
class ChatNotifier extends Notifier<ChatState> {
|
||||
StreamSubscription<Map<String, dynamic>>? _subscription;
|
||||
String _streamBuffer = '';
|
||||
Timer? _streamTimer;
|
||||
ActiveAgent? _lastTriggeredAgent;
|
||||
|
||||
void markNeedsRebuild() => state = state.copyWith();
|
||||
|
||||
@@ -201,18 +200,20 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
)]);
|
||||
}
|
||||
|
||||
/// 点击胶囊:先出用户标签 → 0.5 秒后出欢迎卡片,不走 AI
|
||||
/// 点击胶囊:先出用户标签 → 0.4 秒后出欢迎卡片,不走 AI
|
||||
/// 重复点击同一胶囊不重复弹卡片
|
||||
void triggerAgent(ActiveAgent agent, String label) {
|
||||
if (_lastTriggeredAgent == agent) return;
|
||||
_lastTriggeredAgent = agent;
|
||||
|
||||
final userMsg = ChatMessage(
|
||||
id: 'agent_trigger_${DateTime.now().millisecondsSinceEpoch}',
|
||||
role: 'user',
|
||||
content: label,
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
// 先出用户消息
|
||||
state = state.copyWith(messages: [...state.messages, userMsg]);
|
||||
|
||||
// 短暂延迟后弹出卡片
|
||||
Future.delayed(const Duration(milliseconds: 400), () {
|
||||
insertAgentWelcome(agent);
|
||||
});
|
||||
@@ -221,6 +222,7 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
Future<void> sendImage(String imagePath, String text) async {
|
||||
final file = File(imagePath);
|
||||
if (!await file.exists()) return;
|
||||
_lastTriggeredAgent = null;
|
||||
|
||||
// 先显示用户消息(本地显示图片路径)
|
||||
final userMsg = ChatMessage(
|
||||
@@ -264,6 +266,7 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
|
||||
Future<void> sendMessage(String text) async {
|
||||
if (text.trim().isEmpty || state.isStreaming) return;
|
||||
_lastTriggeredAgent = null;
|
||||
|
||||
final userMsg = ChatMessage(
|
||||
id: '${DateTime.now().millisecondsSinceEpoch}',
|
||||
@@ -333,21 +336,14 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
case 'conversation_id':
|
||||
state = state.copyWith(conversationId: j['data']?.toString());
|
||||
case 'answer':
|
||||
_streamBuffer += (j['data'] as String?) ?? '';
|
||||
final messageType = j['type'] as String? ?? 'text';
|
||||
aiMsg.type = _parseMessageType(messageType);
|
||||
if (j['metadata'] is Map) {
|
||||
aiMsg.metadata = Map<String, dynamic>.from(j['metadata']);
|
||||
}
|
||||
// 逐字释放
|
||||
_streamTimer?.cancel();
|
||||
_streamTimer = Timer.periodic(const Duration(milliseconds: 40), (t) {
|
||||
if (_streamBuffer.isEmpty) { t.cancel(); state = state.copyWith(thinkingText: null); return; }
|
||||
aiMsg.content += _streamBuffer[0];
|
||||
_streamBuffer = _streamBuffer.substring(1);
|
||||
state = state.copyWith(thinkingText: null);
|
||||
_update(aiMsg);
|
||||
});
|
||||
aiMsg.content += (j['data'] as String?) ?? '';
|
||||
state = state.copyWith(thinkingText: null);
|
||||
_update(aiMsg);
|
||||
case 'notice':
|
||||
state = state.copyWith(thinkingText: j['message'] as String?);
|
||||
case 'tool_result':
|
||||
@@ -388,12 +384,6 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
}
|
||||
|
||||
void _done(ChatMessage m) {
|
||||
_streamTimer?.cancel();
|
||||
// 释放剩余 buffer
|
||||
while (_streamBuffer.isNotEmpty) {
|
||||
m.content += _streamBuffer[0];
|
||||
_streamBuffer = _streamBuffer.substring(1);
|
||||
}
|
||||
final u = state.messages.toList();
|
||||
if (!u.any((x) => x.id == m.id) && m.content.isNotEmpty) u.add(m);
|
||||
state = state.copyWith(messages: u, isStreaming: false, thinkingText: null);
|
||||
|
||||
Reference in New Issue
Block a user