diff --git a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
index 19c1fecd..8b43f4c4 100644
--- a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
+++ b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
@@ -3404,6 +3404,7 @@ export function PlatformEntryFlowShellImpl({
})
| null
>(null);
+ const [profileTaskRefreshKey, setProfileTaskRefreshKey] = useState(0);
const [initialCreationUrlState] = useState(() => readCreationUrlState());
const handledInitialCreationUrlStateRef = useRef(false);
const [initialPuzzleRuntimeUrlState] = useState(() =>
@@ -3549,15 +3550,14 @@ export function PlatformEntryFlowShellImpl({
'ready',
viewedImmediately,
);
- if (!viewedImmediately) {
- const completedAtMs = Date.now();
- setPendingPlatformTaskCompletionDialog({
- key: `${kind}:${collectDraftNoticeKeys(kind, ids).join('|')}:${completedAtMs}`,
- source: buildDraftCompletionDialogSource(kind, ids),
- message: '生成任务已完成,可以继续查看草稿。',
- completedAtMs,
- });
- }
+ setProfileTaskRefreshKey((current) => current + 1);
+ const completedAtMs = Date.now();
+ setPendingPlatformTaskCompletionDialog({
+ key: `${kind}:${collectDraftNoticeKeys(kind, ids).join('|')}:${completedAtMs}`,
+ source: buildDraftCompletionDialogSource(kind, ids),
+ message: '生成任务已完成,可以继续查看草稿。',
+ completedAtMs,
+ });
},
[setPendingPlatformTaskCompletionDialog, updateDraftGenerationNotices],
);
@@ -14755,6 +14755,7 @@ export function PlatformEntryFlowShellImpl({
isLoadingPlatform={platformBootstrap.isLoadingPlatform}
isLoadingDashboard={platformBootstrap.isLoadingDashboard}
hasUnreadDraftUpdate={hasUnreadDraftUpdates}
+ profileTaskRefreshKey={profileTaskRefreshKey}
isDesktopLayout={isDesktopLayout}
isResumingSaveWorldKey={platformBootstrap.isResumingSaveWorldKey}
platformError={
diff --git a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx
index d6941800..1d0ab9ae 100644
--- a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx
+++ b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx
@@ -7137,6 +7137,48 @@ test('persisted generating puzzle draft keeps session polling on the same sessio
expect(getPuzzleAgentSession).toHaveBeenCalledTimes(2);
});
+test('puzzle compile timeout shows failure dialog when reread session is still generating', async () => {
+ const user = userEvent.setup();
+ const runningSession = buildMockPuzzleAgentSession({
+ sessionId: 'puzzle-session-timeout',
+ draft: null,
+ stage: 'collecting_anchors',
+ progressPercent: 88,
+ lastAssistantReply: '正在生成拼图草稿。',
+ });
+ vi.mocked(createPuzzleAgentSession).mockResolvedValueOnce({
+ session: runningSession,
+ });
+ vi.mocked(executePuzzleAgentAction).mockRejectedValueOnce(
+ Object.assign(new Error('请求超时:1800000ms'), {
+ name: 'TimeoutError',
+ }),
+ );
+ vi.mocked(getPuzzleAgentSession).mockResolvedValue({
+ session: runningSession,
+ });
+
+ render();
+
+ await openCreateTemplateHub(user);
+ await user.click(await findCreationTypeButton('拼图'));
+ await user.click(await screen.findByRole('button', { name: '生成草稿' }));
+
+ const dialog = await screen.findByRole('dialog', { name: '发生错误' });
+ expect(within(dialog).getByText('拼图草稿 puzzle-session-timeout')).toBeTruthy();
+ expect(
+ within(dialog).getByText(
+ '拼图共创操作超时,请确认运行时后端已启动后重试。',
+ ),
+ ).toBeTruthy();
+ expect(within(dialog).getByRole('button', { name: '复制报错' })).toBeTruthy();
+ expect(
+ await screen.findByRole('progressbar', {
+ name: '拼图草稿生成进度',
+ }),
+ ).toBeTruthy();
+});
+
test('published puzzle work card restores its source session for editing', async () => {
const user = userEvent.setup();
diff --git a/src/components/rpg-entry/RpgEntryHomeView.tsx b/src/components/rpg-entry/RpgEntryHomeView.tsx
index ab961e97..851e1ca1 100644
--- a/src/components/rpg-entry/RpgEntryHomeView.tsx
+++ b/src/components/rpg-entry/RpgEntryHomeView.tsx
@@ -216,6 +216,7 @@ export interface RpgEntryHomeViewProps {
onOpenPlayedWork?: (work: ProfilePlayedWorkSummary) => void;
onOpenFeedback?: () => void;
onRechargeSuccess?: () => void | Promise;
+ profileTaskRefreshKey?: number;
createTabContent?: ReactNode;
draftTabContent?: ReactNode;
hasUnreadDraftUpdate?: boolean;
@@ -3983,6 +3984,7 @@ export function RpgEntryHomeView({
onOpenPlayedWork,
onOpenFeedback,
onRechargeSuccess,
+ profileTaskRefreshKey = 0,
createTabContent,
draftTabContent,
hasUnreadDraftUpdate = false,
@@ -4798,7 +4800,7 @@ export function RpgEntryHomeView({
}
loadTaskCenter();
- }, [activeTab, isAuthenticated, loadTaskCenter]);
+ }, [activeTab, isAuthenticated, loadTaskCenter, profileTaskRefreshKey]);
const openTaskCenterPanel = () => {
setIsTaskCenterOpen(true);
diff --git a/src/components/rpg-entry/rpgEntryShared.ts b/src/components/rpg-entry/rpgEntryShared.ts
index 55537e39..2193bb8e 100644
--- a/src/components/rpg-entry/rpgEntryShared.ts
+++ b/src/components/rpg-entry/rpgEntryShared.ts
@@ -9,6 +9,9 @@ import type { CustomWorldProfile } from '../../types';
export function resolveRpgEntryErrorMessage(error: unknown, fallback: string) {
if (isTimeoutError(error)) {
+ if (/拼图/u.test(fallback) && /操作|执行|编译|生成草稿/u.test(fallback)) {
+ return '拼图共创操作超时,请确认运行时后端已启动后重试。';
+ }
if (/智能创作/u.test(fallback)) {
return '开启智能创作工作区超时,请确认运行时后端已启动后重试。';
}