feat(chat): add AgUiService with mock event stream
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user