feat(chat): add AgUiService with mock event stream

This commit is contained in:
qzl
2026-02-28 13:38:26 +08:00
parent 7e0e6457d7
commit 1fd33c57a7
@@ -0,0 +1,167 @@
import 'dart:async';
import 'dart:convert';
import 'package:social_app/core/config/env.dart';
import '../ai/ai_decision_engine.dart';
import '../models/ag_ui_event.dart';
import '../models/tool_result.dart';
import '../tools/tool_registry.dart';
typedef EventCallback = void Function(AgUiEvent event);
class AgUiService {
EventCallback onEvent;
final AiDecisionEngine _decisionEngine;
AgUiService({EventCallback? onEvent})
: onEvent = onEvent ?? ((_) {}),
_decisionEngine = AiDecisionEngine();
Future<void> sendMessage(String content) async {
if (Env.isMockApi) {
await _mockEventStream(content);
} else {
throw UnimplementedError('Real API not implemented');
}
}
Future<void> _mockEventStream(String content) async {
final threadId = 'thread_${DateTime.now().millisecondsSinceEpoch}';
final runId = 'run_${DateTime.now().millisecondsSinceEpoch}';
onEvent(RunStartedEvent(threadId: threadId, runId: runId));
final forceTrigger = _decisionEngine.tryForceTrigger(content);
if (forceTrigger != null) {
await _mockToolCallFlowWithArgs(forceTrigger.toolName, forceTrigger.args);
} else if (_decisionEngine.shouldTriggerToolCall(content)) {
await _mockToolCallFlow(content);
}
final replies = _generateReplies(content);
if (replies.isNotEmpty) {
await _mockTextMessageStream(replies);
}
onEvent(RunFinishedEvent(threadId: threadId, runId: runId));
}
Future<void> _mockToolCallFlow(String content) async {
final args = _decisionEngine.getToolCallArgs(content);
if (args == null) return;
await _mockToolCallFlowWithArgs('create_calendar_event', args);
}
Future<void> _mockToolCallFlowWithArgs(
String toolName,
Map<String, dynamic> args,
) async {
final toolCallId = 'tc_${DateTime.now().millisecondsSinceEpoch}';
onEvent(ToolCallStartEvent(toolCallId: toolCallId, toolCallName: toolName));
onEvent(ToolCallArgsEvent(toolCallId: toolCallId, delta: jsonEncode(args)));
onEvent(ToolCallEndEvent(toolCallId: toolCallId));
final validation = ToolRegistry.validateArgs(toolName, args);
if (!validation.ok) {
onEvent(
ToolCallErrorEvent(
toolCallId: toolCallId,
error: validation.error ?? 'Validation failed',
code: 'VALIDATION_ERROR',
),
);
return;
}
try {
ToolRegistry.initialize();
final result = await ToolRegistry.execute(toolName, args);
final ui = _buildUiCard(toolName, result);
final messageId = 'msg_${DateTime.now().millisecondsSinceEpoch}';
onEvent(
ToolCallResultEvent(
messageId: messageId,
toolCallId: toolCallId,
result: result,
ui: ui,
),
);
} catch (e) {
onEvent(
ToolCallErrorEvent(
toolCallId: toolCallId,
error: e.toString(),
code: 'EXECUTION_ERROR',
),
);
}
}
UiCard? _buildUiCard(String toolName, Map<String, dynamic> result) {
if (toolName == 'create_calendar_event') {
return UiCard(
cardType: 'calendar',
data: CalendarCardData(
id: result['eventId'] ?? '',
title: result['title'] ?? '',
description: result['description'],
startAt: result['startAt'] ?? '',
endAt: result['endAt'],
timezone: result['timezone'],
location: result['location'],
color: result['color'],
sourceType: result['sourceType'],
).toJson(),
actions: [
CardAction(
type: 'link',
label: '查看详情',
target: '/calendar/${result['eventId']}',
),
],
);
}
return null;
}
List<String> _generateReplies(String content) {
final intent = _decisionEngine.matchIntent(content);
switch (intent) {
case Intent.createEvent:
return ['好的,我已经为您创建了日程安排。'];
case Intent.searchEvent:
return ['您今天有以下日程:\n- 10:00 团队会议\n- 14:00 产品评审'];
case Intent.unknown:
return ['我理解了您的问题,让我来帮您处理。'];
}
}
Future<void> _mockTextMessageStream(List<String> replies) async {
for (final reply in replies) {
final messageId = 'msg_${DateTime.now().millisecondsSinceEpoch}';
onEvent(TextMessageStartEvent(messageId: messageId, role: 'assistant'));
const chunkSize = 10;
for (var i = 0; i < reply.length; i += chunkSize) {
final end = (i + chunkSize < reply.length)
? i + chunkSize
: reply.length;
final chunk = reply.substring(i, end);
onEvent(TextMessageContentEvent(messageId: messageId, delta: chunk));
await Future.delayed(const Duration(milliseconds: 50));
}
onEvent(TextMessageEndEvent(messageId: messageId));
}
}
}