feat: 添加 Analytics 分析功能(行为追踪、错误码、协议更新)
This commit is contained in:
@@ -12,6 +12,7 @@ import 'package:social_app/core/chat/chat_list_item.dart';
|
||||
import 'package:social_app/core/chat/chat_orchestrator.dart';
|
||||
import 'package:social_app/core/chat/chat_history_repository.dart';
|
||||
import 'package:social_app/core/chat/chat_timeline_reconciler.dart';
|
||||
import 'package:social_app/core/analytics/tracker.dart';
|
||||
import 'package:social_app/core/l10n/l10n.dart';
|
||||
import 'chat_bloc_recovery_utils.dart';
|
||||
|
||||
@@ -20,6 +21,13 @@ part 'chat_bloc_send.dart';
|
||||
part 'chat_bloc_history.dart';
|
||||
part 'chat_bloc_attachments.dart';
|
||||
|
||||
typedef ChatCompletedCallback =
|
||||
void Function({
|
||||
required String conversationId,
|
||||
required int messageCount,
|
||||
required int responseTimeMs,
|
||||
});
|
||||
|
||||
class ChatState implements ChatOrchestratorState {
|
||||
@override
|
||||
final List<ChatListItem> items;
|
||||
@@ -115,12 +123,14 @@ class ChatBloc extends Cubit<ChatState> implements ChatOrchestrator {
|
||||
required ChatApi chatApi,
|
||||
ChatHistoryRepository? historyRepository,
|
||||
Future<void> Function()? onCalendarMutated,
|
||||
ChatCompletedCallback? onChatCompleted,
|
||||
Duration recoveryPollInterval = const Duration(milliseconds: 700),
|
||||
Duration recoveryTimeout = const Duration(seconds: 20),
|
||||
}) : _service =
|
||||
service ??
|
||||
AgUiService(chatApi: chatApi, historyRepository: historyRepository),
|
||||
_onCalendarMutated = onCalendarMutated,
|
||||
_onChatCompleted = onChatCompleted,
|
||||
_recoveryPollInterval = recoveryPollInterval,
|
||||
_recoveryTimeout = recoveryTimeout,
|
||||
super(const ChatState()) {
|
||||
@@ -129,9 +139,14 @@ class ChatBloc extends Cubit<ChatState> implements ChatOrchestrator {
|
||||
|
||||
final AgUiService _service;
|
||||
final Future<void> Function()? _onCalendarMutated;
|
||||
final ChatCompletedCallback? _onChatCompleted;
|
||||
final Duration _recoveryPollInterval;
|
||||
final Duration _recoveryTimeout;
|
||||
String? _activeUserId;
|
||||
DateTime? _activeRunStartedAt;
|
||||
DateTime? _activeRunFirstResponseAt;
|
||||
String? _activeRunId;
|
||||
String? _activeThreadId;
|
||||
int _sessionEpoch = 0;
|
||||
final Map<String, Uint8List> _attachmentPreviewCache = <String, Uint8List>{};
|
||||
final Map<String, Future<Uint8List?>> _attachmentPreviewInflight =
|
||||
@@ -259,4 +274,54 @@ class ChatBloc extends Cubit<ChatState> implements ChatOrchestrator {
|
||||
emit(state.copyWith(error: error.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _recordRunStarted({required String runId, required String threadId}) {
|
||||
_activeRunStartedAt = DateTime.now();
|
||||
_activeRunFirstResponseAt = null;
|
||||
_activeRunId = runId;
|
||||
_activeThreadId = threadId;
|
||||
}
|
||||
|
||||
void _recordRunFirstResponse() {
|
||||
_activeRunFirstResponseAt ??= DateTime.now();
|
||||
}
|
||||
|
||||
void _trackChatCompleted() {
|
||||
final startedAt = _activeRunStartedAt;
|
||||
if (startedAt == null) {
|
||||
return;
|
||||
}
|
||||
final firstResponseAt = _activeRunFirstResponseAt ?? DateTime.now();
|
||||
final responseTimeMs = firstResponseAt.difference(startedAt).inMilliseconds;
|
||||
final threadId = _activeThreadId?.trim();
|
||||
final runId = _activeRunId?.trim();
|
||||
final conversationId = (threadId != null && threadId.isNotEmpty)
|
||||
? threadId
|
||||
: runId;
|
||||
if (conversationId == null || conversationId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final onChatCompleted = _onChatCompleted;
|
||||
if (onChatCompleted != null) {
|
||||
onChatCompleted(
|
||||
conversationId: conversationId,
|
||||
messageCount: 1,
|
||||
responseTimeMs: responseTimeMs,
|
||||
);
|
||||
return;
|
||||
}
|
||||
AnalyticsTracker.instance.trackAgentChatCompleted(
|
||||
conversationId: conversationId,
|
||||
scenario: 'assistant',
|
||||
messageCount: 1,
|
||||
responseTimeMs: responseTimeMs,
|
||||
);
|
||||
}
|
||||
|
||||
void _clearRunMetrics() {
|
||||
_activeRunStartedAt = null;
|
||||
_activeRunFirstResponseAt = null;
|
||||
_activeRunId = null;
|
||||
_activeThreadId = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@ extension _ChatBlocEvents on ChatBloc {
|
||||
void _handleEvent(AgUiEvent event) {
|
||||
switch (event.type) {
|
||||
case AgUiEventType.runStarted:
|
||||
final runStartedEvent = event as RunStartedEvent;
|
||||
_recordRunStarted(
|
||||
runId: runStartedEvent.runId,
|
||||
threadId: runStartedEvent.threadId,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
isSending: false,
|
||||
@@ -17,11 +22,14 @@ extension _ChatBlocEvents on ChatBloc {
|
||||
),
|
||||
);
|
||||
case AgUiEventType.runFinished:
|
||||
_trackChatCompleted();
|
||||
_clearRunMetrics();
|
||||
emit(
|
||||
_resetRunState().copyWith(items: _removeToolCallItems(state.items)),
|
||||
);
|
||||
case AgUiEventType.runError:
|
||||
final errorEvent = event as RunErrorEvent;
|
||||
_clearRunMetrics();
|
||||
final isCanceledByUser = errorEvent.code == 'RUN_CANCELED';
|
||||
emit(
|
||||
_resetRunState(
|
||||
@@ -72,6 +80,7 @@ extension _ChatBlocEvents on ChatBloc {
|
||||
}
|
||||
|
||||
void _handleTextMessageEnd(TextMessageEndEvent event) {
|
||||
_recordRunFirstResponse();
|
||||
final timestamp = DateTime.now();
|
||||
final items = _updateOrAddMessage(
|
||||
state.items,
|
||||
|
||||
Reference in New Issue
Block a user