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 '开启智能创作工作区超时,请确认运行时后端已启动后重试。'; }