Files

216 lines
5.8 KiB
Dart
Raw Permalink Normal View History

// ignore_for_file: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
part of 'chat_bloc.dart';
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,
isWaitingFirstToken: true,
isCancelling: false,
error: null,
currentStage: null,
hasSeenStep: false,
),
);
case AgUiEventType.runFinished:
_trackChatCompleted();
_clearRunMetrics();
emit(_resetRunState());
case AgUiEventType.runError:
final errorEvent = event as RunErrorEvent;
_clearRunMetrics();
final isCanceledByUser = errorEvent.code == 'RUN_CANCELED';
emit(
_resetRunState(error: isCanceledByUser ? null : errorEvent.message),
);
case AgUiEventType.stepStarted:
_handleStepStarted(event as StepStartedEvent);
case AgUiEventType.stepFinished:
_handleStepFinished(event as StepFinishedEvent);
case AgUiEventType.textMessageEnd:
_handleTextMessageEnd(event as TextMessageEndEvent);
case AgUiEventType.toolCallResult:
_handleToolCallResult(event as ToolCallResultEvent);
case AgUiEventType.unknown:
break;
}
}
void _handleStepStarted(StepStartedEvent event) {
emit(
state.copyWith(
currentStage: stageFromStepName(event.stepName),
hasSeenStep: true,
),
);
}
void _handleStepFinished(StepFinishedEvent event) {
if (state.currentStage == stageFromStepName(event.stepName)) {
emit(state.copyWith(currentStage: null));
}
}
void _handleTextMessageEnd(TextMessageEndEvent event) {
_recordRunFirstResponse();
final timestamp = DateTime.now();
final items = _updateOrAddMessage(
state.items,
event.messageId,
event.answer,
event.suggestedActions,
timestamp,
);
emit(
state.copyWith(
items: items,
currentMessageId: null,
isWaitingFirstToken: false,
isStreaming: false,
),
);
}
List<ChatListItem> _updateOrAddMessage(
List<ChatListItem> items,
String messageId,
String content,
List<String> suggestedActions,
DateTime timestamp,
) {
final result = List<ChatListItem>.from(items);
final index = result.indexWhere(
(item) => item.id == messageId && item is TextMessageItem,
);
if (index >= 0) {
final existing = result[index] as TextMessageItem;
result[index] = existing.copyWith(
content: content,
isStreaming: false,
suggestedActions: suggestedActions,
);
return result;
}
result.add(
TextMessageItem(
id: messageId,
content: content,
timestamp: timestamp,
sender: MessageSender.ai,
isStreaming: false,
suggestedActions: suggestedActions,
),
);
return result;
}
void _handleToolCallResult(ToolCallResultEvent event) {
if (_shouldRefreshCalendarForTool(event)) {
unawaited(_refreshCalendarAfterToolMutation());
}
final timestamp = DateTime.now();
final items = List<ChatListItem>.from(state.items);
final uiSchema = event.uiSchema;
if (uiSchema != null) {
_upsertToolResultUi(items, event.toolCallId, uiSchema, timestamp);
}
emit(state.copyWith(items: items));
}
void _upsertToolResultUi(
List<ChatListItem> items,
String toolCallId,
Map<String, dynamic> uiSchema,
DateTime timestamp,
) {
final uiItemId = '$toolCallId-ui';
final uiItem = ToolResultItem(
id: uiItemId,
callId: toolCallId,
uiSchema: uiSchema,
timestamp: timestamp,
sender: MessageSender.ai,
);
final existingIndex = items.indexWhere((item) => item.id == uiItemId);
if (existingIndex >= 0) {
items[existingIndex] = uiItem;
return;
}
items.add(uiItem);
}
List<ChatListItem> _convertHistoryMessages(List<HistoryMessage> messages) {
final converted = <ChatListItem>[];
for (final msg in messages) {
final normalizedRole = msg.role.toLowerCase();
final isUser = normalizedRole == 'user';
final isTool = normalizedRole == 'tool';
final sender = isUser ? MessageSender.user : MessageSender.ai;
final attachments = msg.attachments
.map(
(attachment) => <String, dynamic>{
'url': attachment.url,
'mimeType': attachment.mimeType,
},
)
.toList();
if (!isTool && (msg.content.isNotEmpty || isUser)) {
converted.add(
TextMessageItem(
id: msg.id,
content: msg.content,
timestamp: msg.timestamp,
sender: sender,
isLocalEcho: false,
attachments: attachments,
suggestedActions: msg.suggestedActions,
),
);
}
if (isTool && msg.uiSchema != null) {
converted.add(
ToolResultItem(
id: '${msg.id}-ui',
callId: msg.id,
uiSchema: msg.uiSchema!,
timestamp: msg.timestamp,
sender: MessageSender.ai,
),
);
}
}
return converted;
}
DateTime? _extractDateFromItems(List<ChatListItem> items) {
if (items.isEmpty) {
return null;
}
return items
.map(
(item) => DateTime(
item.timestamp.year,
item.timestamp.month,
item.timestamp.day,
),
)
.reduce((a, b) => a.isBefore(b) ? a : b);
}
}