import 'dart:async'; import 'dart:convert'; import 'package:social_app/core/api/i_api_client.dart'; import '../ai/ai_decision_engine.dart'; import '../models/ag_ui_event.dart'; import '../models/tool_result.dart'; import '../tools/tool_registry.dart'; import 'mock_history_service.dart'; /// Mock ID 前缀常量 const _threadIdPrefix = 'thread_'; const _runIdPrefix = 'run_'; const _toolCallIdPrefix = 'tc_'; const _messageIdPrefix = 'msg_'; /// 流式输出延迟 (毫秒) const _streamChunkDelayMs = 50; /// 文本块大小 const _textChunkSize = 10; typedef EventCallback = void Function(AgUiEvent event); class AgUiService { final IApiClient? _apiClient; EventCallback onEvent; final AiDecisionEngine _decisionEngine; final MockHistoryService _historyService; AgUiService({EventCallback? onEvent, IApiClient? apiClient}) : onEvent = onEvent ?? ((_) {}), _apiClient = apiClient, _decisionEngine = AiDecisionEngine(), _historyService = MockHistoryService(); Future sendMessage(String content) async { if (_apiClient != null) { throw UnimplementedError('Real API not implemented'); } await _mockEventStream(content); } Future loadHistory({DateTime? beforeDate}) async { if (_apiClient != null) { throw UnimplementedError('Real API not implemented'); } await _mockLoadHistory(beforeDate: beforeDate); } bool hasEarlierHistory(DateTime fromDate) { return _historyService.hasEarlierHistory(fromDate); } Future _mockLoadHistory({DateTime? beforeDate}) async { final threadId = '$_threadIdPrefix${DateTime.now().millisecondsSinceEpoch}'; final runId = '$_runIdPrefix${DateTime.now().millisecondsSinceEpoch}'; onEvent(RunStartedEvent(threadId: threadId, runId: runId)); await Future.delayed(const Duration(milliseconds: 10)); // Determine target date, end early if no earlier history final DateTime targetDate; if (beforeDate != null) { final prevDate = _historyService.getPreviousDay(beforeDate); if (prevDate == null) { onEvent(RunFinishedEvent(threadId: threadId, runId: runId)); return; } targetDate = prevDate; } else { targetDate = _historyService.getLatestHistoryDate() ?? DateTime.now(); } final messages = _historyService.getHistoryForDay(targetDate); onEvent(MessagesSnapshotEvent(messages: messages)); await Future.delayed(const Duration(milliseconds: 10)); onEvent(RunFinishedEvent(threadId: threadId, runId: runId)); } Future _mockEventStream(String content) async { final threadId = '$_threadIdPrefix${DateTime.now().millisecondsSinceEpoch}'; final runId = '$_runIdPrefix${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 _mockToolCallFlow(String content) async { final args = _decisionEngine.getToolCallArgs(content); if (args == null) return; await _mockToolCallFlowWithArgs('create_calendar_event', args); } Future _mockToolCallFlowWithArgs( String toolName, Map args, ) async { final toolCallId = '$_toolCallIdPrefix${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 = '$_messageIdPrefix${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 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 _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 _mockTextMessageStream(List replies) async { for (final reply in replies) { final messageId = '$_messageIdPrefix${DateTime.now().millisecondsSinceEpoch}'; onEvent(TextMessageStartEvent(messageId: messageId, role: 'assistant')); for (var i = 0; i < reply.length; i += _textChunkSize) { final end = (i + _textChunkSize < reply.length) ? i + _textChunkSize : reply.length; final chunk = reply.substring(i, end); onEvent(TextMessageContentEvent(messageId: messageId, delta: chunk)); await Future.delayed(const Duration(milliseconds: _streamChunkDelayMs)); } onEvent(TextMessageEndEvent(messageId: messageId)); } } }