Preserve partial creation replies on stream failure
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
kdletters
2026-05-05 11:31:50 +08:00
parent 100fee7e7a
commit 995661e7cc
299 changed files with 13805 additions and 1429 deletions

View File

@@ -1,4 +1,4 @@
import { useCallback, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import type { TextStreamOptions } from '../../services/aiTypes';
import type { SelectionStage } from './platformEntryTypes';
@@ -104,6 +104,16 @@ function buildOptimisticMessage<TMessagePayload extends CreationAgentMessageLike
};
}
function buildInterruptedAssistantMessage(text: string) {
return {
id: `assistant-interrupted-${Date.now().toString(36)}`,
role: 'assistant',
kind: 'warning',
text: text.trim(),
createdAt: new Date().toISOString(),
};
}
/**
* 轻量作品 Agent 创作流程的通用前端控制器。
* 这里只处理跨玩法一致的会话、流式消息、忙碌态与草稿恢复,玩法结果页和运行态动作留给外层。
@@ -130,6 +140,18 @@ export function usePlatformCreationAgentFlowController<
const [isBusy, setIsBusy] = useState(false);
const [streamingReplyText, setStreamingReplyText] = useState('');
const [isStreamingReply, setIsStreamingReply] = useState(false);
const latestStreamingReplyTextRef = useRef('');
const updateStreamingReplyText = useCallback((text: string) => {
latestStreamingReplyTextRef.current = text;
setStreamingReplyText(text);
}, []);
const resetStreamingReply = useCallback(() => {
latestStreamingReplyTextRef.current = '';
setStreamingReplyText('');
setIsStreamingReply(false);
}, []);
const openWorkspace = useCallback(async (createPayload?: TCreatePayload) => {
if (isBusy) {
@@ -138,8 +160,7 @@ export function usePlatformCreationAgentFlowController<
setIsBusy(true);
setError(null);
setStreamingReplyText('');
setIsStreamingReply(false);
resetStreamingReply();
try {
const response = await options.client.createSession(
@@ -159,7 +180,7 @@ export function usePlatformCreationAgentFlowController<
} finally {
setIsBusy(false);
}
}, [isBusy, options]);
}, [isBusy, options, resetStreamingReply]);
const restoreDraft = useCallback(
async (sessionId: string | null | undefined) => {
@@ -171,8 +192,7 @@ export function usePlatformCreationAgentFlowController<
setIsBusy(true);
setError(null);
setStreamingReplyText('');
setIsStreamingReply(false);
resetStreamingReply();
try {
const response = await options.client.getSession(normalizedSessionId);
@@ -194,7 +214,7 @@ export function usePlatformCreationAgentFlowController<
setIsBusy(false);
}
},
[options],
[options, resetStreamingReply],
);
const submitMessage = useCallback(
@@ -206,7 +226,7 @@ export function usePlatformCreationAgentFlowController<
const optimisticMessage = buildOptimisticMessage(payload);
setError(null);
setStreamingReplyText('');
updateStreamingReplyText('');
setIsStreamingReply(true);
setSession((current) =>
current
@@ -223,12 +243,28 @@ export function usePlatformCreationAgentFlowController<
session.sessionId,
payload,
{
onUpdate: setStreamingReplyText,
onUpdate: updateStreamingReplyText,
},
);
setSession(nextSession);
setStreamingReplyText('');
updateStreamingReplyText('');
} catch (caughtError) {
const interruptedReplyText =
latestStreamingReplyTextRef.current.trim();
// 上游流可能在已经吐出可读回复后才失败;把这段回复落进本地消息列表,避免 UI 收尾时突然消失。
if (interruptedReplyText) {
const interruptedMessage =
buildInterruptedAssistantMessage(interruptedReplyText);
setSession((current) =>
current
? {
...current,
messages: [...current.messages, interruptedMessage],
updatedAt: interruptedMessage.createdAt,
}
: current,
);
}
setError(
options.resolveErrorMessage(caughtError, options.errorMessages.submit),
);
@@ -236,7 +272,7 @@ export function usePlatformCreationAgentFlowController<
setIsStreamingReply(false);
}
},
[isStreamingReply, options, session],
[isStreamingReply, options, session, updateStreamingReplyText],
);
const executeAction = useCallback(
@@ -284,17 +320,15 @@ export function usePlatformCreationAgentFlowController<
const leaveFlow = useCallback(() => {
setError(null);
setStreamingReplyText('');
setIsStreamingReply(false);
resetStreamingReply();
options.enterCreateTab();
options.setSelectionStage(options.platformStage);
}, [options]);
}, [options, resetStreamingReply]);
const resetTransientState = useCallback(() => {
setError(null);
setStreamingReplyText('');
setIsStreamingReply(false);
}, []);
resetStreamingReply();
}, [resetStreamingReply]);
return {
session,