fix: stabilize chat run lifecycle rendering
This commit is contained in:
@@ -118,10 +118,16 @@ class ChatBloc extends Cubit<ChatState> {
|
||||
),
|
||||
);
|
||||
case AgUiEventType.runFinished:
|
||||
emit(_resetRunState());
|
||||
emit(
|
||||
_resetRunState().copyWith(items: _removeToolCallItems(state.items)),
|
||||
);
|
||||
case AgUiEventType.runError:
|
||||
final errorEvent = event as RunErrorEvent;
|
||||
emit(_resetRunState(error: errorEvent.message));
|
||||
emit(
|
||||
_resetRunState(
|
||||
error: errorEvent.message,
|
||||
).copyWith(items: _markActiveToolCallsFailed(state.items)),
|
||||
);
|
||||
case AgUiEventType.stepStarted:
|
||||
_handleStepStarted(event as StepStartedEvent);
|
||||
case AgUiEventType.stepFinished:
|
||||
@@ -167,9 +173,11 @@ class ChatBloc extends Cubit<ChatState> {
|
||||
_upsertUiSchema(items, event.messageId, uiSchema, timestamp);
|
||||
}
|
||||
|
||||
final withoutToolCalls = _removeToolCallItems(items);
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
items: items,
|
||||
items: withoutToolCalls,
|
||||
currentMessageId: null,
|
||||
isWaitingFirstToken: false,
|
||||
isStreaming: false,
|
||||
@@ -264,13 +272,38 @@ class ChatBloc extends Cubit<ChatState> {
|
||||
}
|
||||
|
||||
void _handleToolCallResult(ToolCallResultEvent event) {
|
||||
final items = state.items.where((item) {
|
||||
return !(item is ToolCallItem && item.id == event.toolCallId);
|
||||
final items = state.items.map((item) {
|
||||
if (item is ToolCallItem && item.id == event.toolCallId) {
|
||||
return item.copyWith(status: ToolCallStatus.completed);
|
||||
}
|
||||
return item;
|
||||
}).toList();
|
||||
|
||||
emit(state.copyWith(items: items));
|
||||
}
|
||||
|
||||
List<ChatListItem> _removeToolCallItems(List<ChatListItem> items) {
|
||||
return items.where((item) => item is! ToolCallItem).toList();
|
||||
}
|
||||
|
||||
List<ChatListItem> _markActiveToolCallsFailed(List<ChatListItem> items) {
|
||||
return items.map((item) {
|
||||
if (item is! ToolCallItem) {
|
||||
return item;
|
||||
}
|
||||
if (item.status == ToolCallStatus.error) {
|
||||
return item;
|
||||
}
|
||||
if (item.status == ToolCallStatus.completed) {
|
||||
return item;
|
||||
}
|
||||
return item.copyWith(
|
||||
status: ToolCallStatus.error,
|
||||
errorMessage: '本次运行已失败',
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
void _handleToolCallError(ToolCallErrorEvent event) {
|
||||
final items = state.items.map((item) {
|
||||
if (item is ToolCallItem && item.id == event.toolCallId) {
|
||||
@@ -341,16 +374,18 @@ class ChatBloc extends Cubit<ChatState> {
|
||||
}
|
||||
|
||||
Future<void> sendMessage(String content, {List<XFile>? images}) async {
|
||||
final messageId = 'user-${DateTime.now().millisecondsSinceEpoch}';
|
||||
final attachments = (images ?? const <XFile>[])
|
||||
.map(
|
||||
(image) => <String, dynamic>{
|
||||
'path': image.path,
|
||||
'mimeType': 'image/*',
|
||||
'mimeType': image.mimeType ?? 'image/jpeg',
|
||||
'uploading': true,
|
||||
},
|
||||
)
|
||||
.toList();
|
||||
final userMessage = TextMessageItem(
|
||||
id: 'user-${DateTime.now().millisecondsSinceEpoch}',
|
||||
id: messageId,
|
||||
content: content,
|
||||
timestamp: DateTime.now(),
|
||||
sender: MessageSender.user,
|
||||
@@ -367,8 +402,13 @@ class ChatBloc extends Cubit<ChatState> {
|
||||
),
|
||||
);
|
||||
try {
|
||||
await _service.sendMessage(content, images: images);
|
||||
final sendResult = await _service.sendMessage(content, images: images);
|
||||
_syncUploadedAttachments(
|
||||
messageId: messageId,
|
||||
uploadedAttachments: sendResult.uploadedAttachments,
|
||||
);
|
||||
} catch (error) {
|
||||
_markAttachmentUploadDone(messageId);
|
||||
emit(
|
||||
state.copyWith(
|
||||
isSending: false,
|
||||
@@ -381,6 +421,63 @@ class ChatBloc extends Cubit<ChatState> {
|
||||
}
|
||||
}
|
||||
|
||||
void _syncUploadedAttachments({
|
||||
required String messageId,
|
||||
required List<UploadedAttachment> uploadedAttachments,
|
||||
}) {
|
||||
if (uploadedAttachments.isEmpty) {
|
||||
_markAttachmentUploadDone(messageId);
|
||||
return;
|
||||
}
|
||||
final items = state.items.map((item) {
|
||||
if (item is! TextMessageItem || item.id != messageId) {
|
||||
return item;
|
||||
}
|
||||
final synced = item.attachments.map((attachment) {
|
||||
final localPath = attachment['path'];
|
||||
if (localPath is! String || localPath.isEmpty) {
|
||||
return <String, dynamic>{...attachment, 'uploading': false};
|
||||
}
|
||||
UploadedAttachment? matched;
|
||||
for (final candidate in uploadedAttachments) {
|
||||
if (candidate.localPath == localPath) {
|
||||
matched = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matched == null) {
|
||||
return <String, dynamic>{...attachment, 'uploading': false};
|
||||
}
|
||||
return <String, dynamic>{
|
||||
...attachment,
|
||||
'url': matched.url,
|
||||
'mimeType': matched.mimeType,
|
||||
'uploading': false,
|
||||
};
|
||||
}).toList();
|
||||
return item.copyWith(attachments: synced);
|
||||
}).toList();
|
||||
emit(state.copyWith(items: items));
|
||||
}
|
||||
|
||||
void _markAttachmentUploadDone(String messageId) {
|
||||
final items = state.items.map((item) {
|
||||
if (item is! TextMessageItem || item.id != messageId) {
|
||||
return item;
|
||||
}
|
||||
final done = item.attachments
|
||||
.map(
|
||||
(attachment) => <String, dynamic>{
|
||||
...attachment,
|
||||
'uploading': false,
|
||||
},
|
||||
)
|
||||
.toList();
|
||||
return item.copyWith(attachments: done);
|
||||
}).toList();
|
||||
emit(state.copyWith(items: items));
|
||||
}
|
||||
|
||||
Future<void> loadHistory() async {
|
||||
if (state.isLoadingHistory) return;
|
||||
emit(state.copyWith(isLoadingHistory: true));
|
||||
|
||||
Reference in New Issue
Block a user