feat: 统一智能体路由 + 删除 onboarding

- 新增 Unified 智能体(合并 health/diet/medication/exercise/default 全部工具)
- AI 自动判断意图并调用对应工具,前端胶囊自动切换
- 删除 onboarding 智能体及所有建档引导 UI
- 前端始终发 unified 类型,通过 tool_result 事件同步胶囊状态
This commit is contained in:
MingNian
2026-06-08 14:55:38 +08:00
parent b5f2977b32
commit fc7150ad60
9 changed files with 887 additions and 312 deletions

View File

@@ -5,7 +5,7 @@ import 'auth_provider.dart';
import 'data_providers.dart';
import '../utils/sse_handler.dart';
enum MessageType { text, dataConfirm, medicationConfirm, dietAnalysis, reportAnalysis, quickOptions, agentWelcome, taskCard, onboarding }
enum MessageType { text, dataConfirm, medicationConfirm, dietAnalysis, reportAnalysis, quickOptions, agentWelcome, taskCard }
class ChatMessage {
final String id;
@@ -27,7 +27,7 @@ class ChatMessage {
bool get isUser => role == 'user';
}
enum ActiveAgent { default_, consultation, health, diet, medication, report, exercise, onboarding }
enum ActiveAgent { default_, consultation, health, diet, medication, report, exercise }
class ChatState {
final ActiveAgent activeAgent;
@@ -122,49 +122,10 @@ class ChatNotifier extends Notifier<ChatState> {
ChatState build() {
Future.microtask(() {
insertTaskCard();
_checkOnboarding();
});
return const ChatState();
}
void _checkOnboarding() async {
try {
final api = ref.read(apiClientProvider);
final res = await api.get('/api/user/health-archive');
final archive = res.data['data'];
// 档案全空 → 触发建档引导
final isEmpty = archive == null ||
(archive['diagnosis'] == null && archive['surgeryType'] == null &&
(archive['allergies'] == null || (archive['allergies'] as List).isEmpty) &&
(archive['chronicDiseases'] == null || (archive['chronicDiseases'] as List).isEmpty) &&
(archive['dietRestrictions'] == null || (archive['dietRestrictions'] as List).isEmpty));
if (isEmpty && !state.messages.any((m) => m.type == MessageType.onboarding)) {
final db = ref.read(localDbProvider);
final skipped = await db.read('onboarding_skipped');
if (skipped == null) {
state = state.copyWith(messages: [
...state.messages,
ChatMessage(id: 'onboarding_card', role: 'assistant', content: '', createdAt: DateTime.now(), type: MessageType.onboarding),
]);
}
}
} catch (_) {}
}
void startOnboarding() {
setAgent(ActiveAgent.onboarding);
// Remove onboarding card
state = state.copyWith(messages: state.messages.where((m) => m.type != MessageType.onboarding).toList());
// Send trigger message
Future.microtask(() => _sendToAI('开始建档'));
}
void dismissOnboarding() async {
final db = ref.read(localDbProvider);
await db.write('onboarding_skipped', DateTime.now().toIso8601String());
state = state.copyWith(messages: state.messages.where((m) => m.type != MessageType.onboarding).toList());
}
void insertTaskCard() {
if (state.messages.any((m) => m.type == MessageType.taskCard)) return;
state = state.copyWith(messages: [ChatMessage(
@@ -179,6 +140,37 @@ class ChatNotifier extends Notifier<ChatState> {
void setAgent(ActiveAgent a) {
_subscription?.cancel();
state = state.copyWith(activeAgent: a);
ref.read(selectedAgentProvider.notifier).select(a);
}
/// 根据 AI 调用的工具自动切换智能体胶囊
void _switchAgentByTool(String tool) {
ActiveAgent? agent;
switch (tool) {
case 'record_health_data':
case 'query_health_records':
agent = ActiveAgent.health;
break;
case 'estimate_food_text':
agent = ActiveAgent.diet;
break;
case 'manage_medication':
agent = ActiveAgent.medication;
break;
case 'manage_exercise':
agent = ActiveAgent.exercise;
break;
case 'request_doctor':
agent = ActiveAgent.consultation;
break;
case 'analyze_report':
agent = ActiveAgent.report;
break;
}
if (agent != null) {
ref.read(selectedAgentProvider.notifier).select(agent);
state = state.copyWith(activeAgent: agent);
}
}
Future<void> loadConversation(String convId) async {
@@ -298,10 +290,9 @@ class ChatNotifier extends Notifier<ChatState> {
return;
}
final agentPath =
state.activeAgent.name.replaceFirst('default_', 'default');
// 始终用 unified 智能体AI 自动判断意图分配工具
final stream = SseHandler.connect(
agentType: agentPath,
agentType: 'unified',
message: text,
conversationId: state.conversationId,
token: token,
@@ -346,6 +337,8 @@ class ChatNotifier extends Notifier<ChatState> {
state = state.copyWith(thinkingText: j['message'] as String?);
case 'tool_result':
final tool = j['tool'] as String? ?? '';
// 根据 AI 调用的工具自动切换智能体胶囊
_switchAgentByTool(tool);
if (tool == 'record_health_data') {
ref.invalidate(latestHealthProvider);
}