fix: 支持模型异常后继续文档对话

- 将本轮文档上下文纳入可持久化的 Agent 用户消息

=- 在异常提示中提供请重试动作并自动发送继续
This commit is contained in:
2026-08-26 22:55:20 +08:00
parent 4de8cc5bd0
commit 6248e2c7b8
11 changed files with 311 additions and 49 deletions

View File

@@ -2,6 +2,7 @@
import type {
ChatDocumentAttachment,
ChatImageAttachment,
ChatTimelineErrorItem,
ChatTimelineItem,
ChatTimelineMessageItem,
ChatTimelineToolApprovalPayload,
@@ -108,6 +109,7 @@ const loadingKnowledges = ref(false);
const savingExtraKnowledges = ref(false);
const sending = ref(false);
const runtimeRunning = ref(false);
const retryableErrorRoundId = ref('');
const approvalLoadingKey = ref('');
const knowledgeOptions = ref<{ label: string; value: string }[]>([]);
const knowledgeMap = ref(new Map<string, { id: string; title: string }>());
@@ -463,12 +465,19 @@ function syncRuntimeSnapshot(sessionId = currentSessionId.value) {
: undefined;
if (!snapshot) {
sending.value = false;
retryableErrorRoundId.value = '';
return false;
}
currentSessionId.value = snapshot.sessionId;
selectedAgentId.value = String(snapshot.agentId);
timelineItems.value = snapshot.items;
sending.value = snapshot.sending;
retryableErrorRoundId.value =
snapshot.terminalOutcome === 'failed' &&
snapshot.retryContextReady &&
!snapshot.sending
? snapshot.roundId
: '';
maybeRefreshCompletedRuntimeSession(snapshot);
if (snapshot.prompt && !currentSession.value) {
upsertSessionRecord(
@@ -492,15 +501,20 @@ async function loadConversation(sessionId: string) {
if (!sessionId) {
timelineItems.value = [];
sending.value = false;
retryableErrorRoundId.value = '';
return;
}
const runtimeSnapshot = agentChatRuntimeManager.getSnapshot(sessionId);
if (runtimeSnapshot?.sending) {
if (
runtimeSnapshot?.sending ||
runtimeSnapshot?.terminalOutcome === 'failed'
) {
syncRuntimeSnapshot(sessionId);
await syncSessionRoute(sessionId);
return;
}
loadingConversation.value = true;
retryableErrorRoundId.value = '';
try {
const detailRes = await getAgentSession(sessionId);
const res = await getAgentConversation(sessionId);
@@ -660,12 +674,25 @@ function buildCapabilities() {
];
}
async function sendContent(rawContent: string) {
interface SendContentOptions {
includeComposer?: boolean;
retryContextReady?: boolean;
}
async function sendContent(
rawContent: string,
options: SendContentOptions = {},
) {
const content = rawContent.trim();
const includeComposer = options.includeComposer !== false;
const readyImageCount = includeComposer
? composer.images.readyItems.value.length
: 0;
const readyDocumentCount = includeComposer
? composer.documents.readyItems.value.length
: 0;
if (
(!content &&
composer.images.readyItems.value.length === 0 &&
composer.documents.readyItems.value.length === 0) ||
(!content && readyImageCount === 0 && readyDocumentCount === 0) ||
!selectedAgentId.value ||
sending.value
) {
@@ -675,44 +702,54 @@ async function sendContent(rawContent: string) {
ElMessage.warning('当前回复完成后再发送新消息');
return;
}
if (composer.images.uploading.value) {
if (includeComposer && composer.images.uploading.value) {
ElMessage.warning('图片上传完成后再发送');
return;
}
if (composer.documents.processing.value) {
if (includeComposer && composer.documents.processing.value) {
ElMessage.warning('文档读取完成后再发送');
return;
}
const failedImage = composer.images.items.value.find(
(item) => item.status === 'error',
);
const failedImage = includeComposer
? composer.images.items.value.find((item) => item.status === 'error')
: undefined;
if (failedImage) {
ElMessage.error(failedImage.error || '请处理上传失败的图片');
return;
}
const failedDocument = composer.documents.items.value.find(
(item) => item.status === 'error',
);
const failedDocument = includeComposer
? composer.documents.items.value.find((item) => item.status === 'error')
: undefined;
if (failedDocument) {
ElMessage.error(failedDocument.error || '请处理读取失败的文档');
return;
}
await composer.flush();
if (includeComposer) {
await composer.flush();
}
sending.value = true;
retryableErrorRoundId.value = '';
try {
const sessionId = await agentChatRuntimeManager.start({
agentId: selectedAgentId.value,
agentName: selectedAgent.value?.name,
baseItems: timelineItems.value,
capabilities: buildCapabilities(),
documentUploadIds: composer.documents.uploadIds.value,
documents: composer.documents.readyItems.value.map((item) => ({
...item,
})),
imageUploadIds: composer.images.uploadIds.value,
images: composer.images.readyItems.value.map((item) => ({ ...item })),
onInputAccepted: markComposerInputAccepted,
documentUploadIds: includeComposer
? composer.documents.uploadIds.value
: undefined,
documents: includeComposer
? composer.documents.readyItems.value.map((item) => ({ ...item }))
: undefined,
imageUploadIds: includeComposer
? composer.images.uploadIds.value
: undefined,
images: includeComposer
? composer.images.readyItems.value.map((item) => ({ ...item }))
: undefined,
onInputAccepted: includeComposer ? markComposerInputAccepted : undefined,
prompt: content,
retryContextReady: Boolean(options.retryContextReady),
sessionId: composer.sessionId.value,
});
await bindCreatedSession(sessionId, content);
@@ -725,6 +762,24 @@ async function sendContent(rawContent: string) {
}
}
function modelErrorAction(item: ChatTimelineErrorItem) {
return item.roundId === retryableErrorRoundId.value ? '请重试' : undefined;
}
async function handleModelErrorRetry(item: ChatTimelineErrorItem) {
if (
item.roundId !== retryableErrorRoundId.value ||
sending.value ||
runtimeRunning.value
) {
return;
}
await sendContent('继续', {
includeComposer: false,
retryContextReady: true,
});
}
async function handleSend() {
await sendContent(promptText.value);
}
@@ -1266,12 +1321,15 @@ onBeforeUnmount(() => {
:artifact-loader="loadCurrentAgentArtifact"
:items="timelineItems"
:document-loader="loadAgentChatDocument"
:error-action="modelErrorAction"
:error-action-disabled="sending || runtimeRunning"
:image-loader="loadAgentChatImage"
empty-text="选择智能体后开始对话"
:approval-loading="Boolean(approvalLoadingKey)"
:copy-action="handleCopyMessage"
:copyable="canCopyMessage"
@approve="handleApprove"
@error-action="handleModelErrorRetry"
@reject="handleReject"
@select-next-variant="() => undefined"
@select-previous-variant="() => undefined"