修复拼图文字直创过早完成

修正拼图文字直创 compile 回包未出图时继续保持生成中

补充文字直创无正式图的回归测试

更新玩法链路文档和 Hermes 踩坑记录
This commit is contained in:
2026-06-07 15:55:15 +08:00
parent 3965f34b02
commit c810e255a5
4 changed files with 158 additions and 12 deletions

View File

@@ -2620,26 +2620,33 @@ function mergePuzzleSessionProgressIntoGenerationState(
const isCompiledGenerationSession = Boolean(
session.draft && !session.draft.formDraft,
);
if (!isCompiledGenerationSession) {
return state;
}
const nextPhaseId = isCompiledGenerationSession
? resolvePuzzlePhaseFromSessionProgress(state, session)
: state.metadata?.puzzleActivePhaseId;
const nextPhaseId = resolvePuzzlePhaseFromSessionProgress(state, session);
const shouldResetActiveStepStart =
isCompiledGenerationSession &&
nextPhaseId != null &&
nextPhaseId !== state.metadata?.puzzleActivePhaseId;
const nextActiveStepStartedAtMs = shouldResetActiveStepStart
? resolveMiniGameDraftGenerationStartedAtMs(session.updatedAt)
: state.metadata?.puzzleActiveStepStartedAtMs;
if (
state.metadata?.puzzleActivePhaseId === nextPhaseId &&
state.metadata?.puzzleActiveStepStartedAtMs === nextActiveStepStartedAtMs &&
state.metadata?.puzzleProgressPercent === session.progressPercent
) {
return state;
}
return {
...state,
metadata: {
...state.metadata,
puzzleActivePhaseId: nextPhaseId,
puzzleActiveStepStartedAtMs: shouldResetActiveStepStart
? resolveMiniGameDraftGenerationStartedAtMs(session.updatedAt)
: state.metadata?.puzzleActiveStepStartedAtMs,
puzzleProgressPercent: isCompiledGenerationSession
? session.progressPercent
: state.metadata?.puzzleProgressPercent,
puzzleActiveStepStartedAtMs: nextActiveStepStartedAtMs,
puzzleProgressPercent: session.progressPercent,
},
};
}
@@ -7977,7 +7984,49 @@ export function PlatformEntryFlowShellImpl({
actionPayload,
);
setPuzzleOperation(response.operation);
const openResult = isViewingPuzzleGeneration(nextSession.sessionId);
const openResult =
isViewingPuzzleGeneration(nextSession.sessionId) ||
isViewingPuzzleGeneration(response.session.sessionId);
if (!isPuzzleCompileActionReady(response.session)) {
// 中文注释:文字直创的同步 action 回包只代表后台生图任务已启动;
// 未拿到正式图前继续保持生成中,避免误弹完成或启动空草稿试玩。
const nextGenerationState = mergePuzzleSessionProgressIntoGenerationState(
generationState,
response.session,
);
activePuzzleGenerationSessionIdRef.current = response.session.sessionId;
setPuzzleBackgroundCompileTasks((current) => {
const next = { ...current };
if (nextSession.sessionId !== response.session.sessionId) {
delete next[nextSession.sessionId];
}
next[response.session.sessionId] = {
session: response.session,
payload,
generationState: nextGenerationState,
error: null,
};
return next;
});
puzzleFlow.setSession(response.session);
if (openResult) {
setPuzzleGenerationState(nextGenerationState);
}
markDraftGenerating('puzzle', [
response.session.sessionId,
buildPuzzleResultWorkId(response.session.sessionId),
response.session.publishedProfileId,
buildPuzzleResultProfileId(response.session.sessionId),
]);
markPendingDraftGenerating(
'puzzle',
response.session.sessionId,
buildPendingPuzzleDraftMetadata(payload),
);
void refreshPuzzleShelf();
return;
}
const readyGenerationState =
resolveFinishedMiniGameDraftGenerationState(
generationState,

View File

@@ -5217,6 +5217,95 @@ test('running puzzle draft opens generation progress from draft tab', async () =
});
});
test('puzzle text-only form stays generating when compile starts background image without cover', async () => {
const user = userEvent.setup();
const initialSession = buildMockPuzzleAgentSession({
sessionId: 'puzzle-session-text-only',
stage: 'collecting_anchors',
progressPercent: 0,
draft: null,
});
const generatingDraft = buildReadyPuzzleDraft({
workTitle: '文字直创拼图',
workDescription: '只输入文字后后台继续生成图片。',
candidates: [],
selectedCandidateId: null,
coverImageSrc: null,
coverAssetId: null,
generationStatus: 'generating',
levels: [
{
...buildReadyPuzzleDraft().levels![0]!,
candidates: [],
selectedCandidateId: null,
coverImageSrc: null,
coverAssetId: null,
generationStatus: 'generating',
},
],
});
const generatingSession = buildMockPuzzleAgentSession({
sessionId: 'puzzle-session-text-only',
stage: 'image_refining',
progressPercent: 88,
draft: generatingDraft,
lastAssistantReply: '已编译首关草稿,并启动首关画面和 UI 资产后台生成。',
resultPreview: {
draft: generatingDraft,
blockers: [
{
id: 'missing-cover-image-puzzle-level-1',
code: 'MISSING_COVER_IMAGE',
message: '正式拼图图片尚未确定',
},
],
qualityFindings: [],
publishReady: false,
},
});
vi.mocked(createPuzzleAgentSession).mockResolvedValueOnce({
session: initialSession,
});
vi.mocked(executePuzzleAgentAction).mockResolvedValueOnce({
operation: {
operationId: 'compile-puzzle-text-only',
type: 'compile_puzzle_draft',
status: 'completed',
phaseLabel: '首关拼图草稿',
phaseDetail: '已编译首关草稿,并启动首关画面和 UI 资产后台生成。',
progress: 0.88,
},
session: generatingSession,
});
vi.mocked(getPuzzleAgentSession).mockResolvedValue({
session: generatingSession,
});
render(<TestWrapper withAuth />);
await openCreateTemplateHub(user);
await user.click(await findCreationTypeButton('拼图'));
await user.click(await screen.findByRole('button', { name: '生成草稿' }));
expect(
await screen.findByRole('progressbar', {
name: '拼图图片生成进度',
}),
).toBeTruthy();
await waitFor(() => {
expect(executePuzzleAgentAction).toHaveBeenCalledWith(
'puzzle-session-text-only',
expect.objectContaining({ action: 'compile_puzzle_draft' }),
);
});
expect(screen.queryByRole('dialog', { name: '生成完成' })).toBeNull();
expect(screen.queryByText('请先选择一张正式拼图图片。')).toBeNull();
expect(screen.queryByText('拼图结果页')).toBeNull();
expect(updatePuzzleWork).not.toHaveBeenCalled();
expect(startLocalPuzzleRun).not.toHaveBeenCalled();
});
test('puzzle form checks mud points before creating a draft', async () => {
const user = userEvent.setup();
vi.mocked(getProfileDashboard).mockResolvedValue({