Add generationStatus and match3d/runtime fixes
Introduce persistent generationStatus to work summaries (puzzle & match3d) and propagate generation recovery rules across docs and frontend/backends so "generating" is restored from server-side work summary rather than ephemeral front-end notices. Update API server image/asset handling (improve match3d material sheet green/alpha decontamination and promote generatedItemAssets background fields) and add runtime improvements: alpha-based hotspot hit-testing, tray insertion/three-match animation behavior, and session re-read on client-side VectorEngine timeouts/lock-screen interruptions. Many docs, tests and related frontend modules updated/added to reflect these contract and behavior changes.
This commit is contained in:
@@ -188,6 +188,7 @@ import {
|
||||
buildPuzzleGenerationAnchorEntries,
|
||||
buildSquareHoleGenerationAnchorEntries,
|
||||
createMiniGameDraftGenerationState,
|
||||
type MiniGameDraftGenerationKind,
|
||||
type MiniGameDraftGenerationState,
|
||||
} from '../../services/miniGameDraftGenerationProgress';
|
||||
import { getPlatformProfileDashboard } from '../../services/platform-entry/platformProfileClient';
|
||||
@@ -629,7 +630,9 @@ function resolveVisiblePuzzleDetailCoverCount(
|
||||
function mapMatch3DWorkToPublicWorkDetail(
|
||||
item: Match3DWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapMatch3DWorkToPlatformGalleryCard(item);
|
||||
return mapMatch3DWorkToPlatformGalleryCard(
|
||||
normalizeMatch3DWorkForRuntimeUi(item),
|
||||
);
|
||||
}
|
||||
|
||||
function mapSquareHoleWorkToPublicWorkDetail(
|
||||
@@ -753,6 +756,23 @@ function promoteMatch3DGeneratedBackgroundAsset<
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeMatch3DWorkForRuntimeUi<T extends Match3DWorkSummary>(
|
||||
profile: T,
|
||||
): T {
|
||||
return promoteMatch3DGeneratedBackgroundAsset({
|
||||
...profile,
|
||||
generatedItemAssets: normalizeMatch3DGeneratedItemAssetsForRuntime(
|
||||
profile.generatedItemAssets,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
function mapMatch3DWorksForRuntimeUi<T extends Match3DWorkSummary>(
|
||||
profiles: readonly T[],
|
||||
): T[] {
|
||||
return profiles.map(normalizeMatch3DWorkForRuntimeUi);
|
||||
}
|
||||
|
||||
function buildMatch3DProfileFromSession(
|
||||
session: Match3DAgentSessionSnapshot | null,
|
||||
): Match3DWorkProfile | null {
|
||||
@@ -1648,14 +1668,121 @@ function normalizeDraftNoticeId(id: string | null | undefined) {
|
||||
function createPendingDraftShelfState(
|
||||
status: DraftGenerationNoticeStatus,
|
||||
seen = false,
|
||||
updatedAt = new Date().toISOString(),
|
||||
): PendingDraftShelfState {
|
||||
return {
|
||||
status,
|
||||
seen,
|
||||
updatedAt: new Date().toISOString(),
|
||||
updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
function parseDraftGenerationStartedAtMs(value: string | null | undefined) {
|
||||
const parsedMs = value ? Date.parse(value) : Number.NaN;
|
||||
return Number.isFinite(parsedMs) ? parsedMs : Date.now();
|
||||
}
|
||||
|
||||
function createMiniGameDraftGenerationStateFromStartedAt(
|
||||
kind: MiniGameDraftGenerationKind,
|
||||
startedAtMs: number,
|
||||
): MiniGameDraftGenerationState {
|
||||
return {
|
||||
...createMiniGameDraftGenerationState(kind),
|
||||
startedAtMs,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveFinishedMiniGameDraftGenerationState(
|
||||
state: MiniGameDraftGenerationState,
|
||||
phase: 'ready' | 'failed',
|
||||
options: {
|
||||
error?: string | null;
|
||||
completedAssetCount?: number;
|
||||
totalAssetCount?: number;
|
||||
} = {},
|
||||
): MiniGameDraftGenerationState {
|
||||
return {
|
||||
...state,
|
||||
phase,
|
||||
finishedAtMs: Date.now(),
|
||||
error: options.error ?? state.error,
|
||||
completedAssetCount:
|
||||
options.completedAssetCount ?? state.completedAssetCount,
|
||||
totalAssetCount: options.totalAssetCount ?? state.totalAssetCount,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeRecoveredPuzzleDraftSession(
|
||||
session: PuzzleAgentSessionSnapshot,
|
||||
): PuzzleAgentSessionSnapshot {
|
||||
const draft = session.draft;
|
||||
if (!draft) {
|
||||
return session;
|
||||
}
|
||||
|
||||
const primaryLevel = draft.levels?.[0];
|
||||
const selectedCandidate =
|
||||
primaryLevel?.candidates.find((candidate) => candidate.selected) ??
|
||||
primaryLevel?.candidates[0] ??
|
||||
draft.candidates.find((candidate) => candidate.selected) ??
|
||||
draft.candidates[0] ??
|
||||
null;
|
||||
const coverImageSrc =
|
||||
draft.coverImageSrc?.trim() ||
|
||||
primaryLevel?.coverImageSrc?.trim() ||
|
||||
selectedCandidate?.imageSrc.trim() ||
|
||||
null;
|
||||
const coverAssetId =
|
||||
draft.coverAssetId?.trim() ||
|
||||
primaryLevel?.coverAssetId?.trim() ||
|
||||
selectedCandidate?.assetId.trim() ||
|
||||
null;
|
||||
const selectedCandidateId =
|
||||
draft.selectedCandidateId ??
|
||||
primaryLevel?.selectedCandidateId ??
|
||||
selectedCandidate?.candidateId ??
|
||||
null;
|
||||
|
||||
return {
|
||||
...session,
|
||||
draft: {
|
||||
...draft,
|
||||
coverImageSrc,
|
||||
coverAssetId,
|
||||
selectedCandidateId,
|
||||
generationStatus: 'ready',
|
||||
levels: draft.levels?.map((level, index) =>
|
||||
index === 0
|
||||
? {
|
||||
...level,
|
||||
coverImageSrc: level.coverImageSrc ?? coverImageSrc,
|
||||
coverAssetId: level.coverAssetId ?? coverAssetId,
|
||||
selectedCandidateId:
|
||||
level.selectedCandidateId ?? selectedCandidateId,
|
||||
generationStatus: 'ready',
|
||||
}
|
||||
: level,
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function hasRecoverableGeneratedPuzzleDraft(
|
||||
session: PuzzleAgentSessionSnapshot,
|
||||
) {
|
||||
const draft = session.draft;
|
||||
if (!draft) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const firstLevel = draft.levels?.[0];
|
||||
return Boolean(
|
||||
draft.coverImageSrc?.trim() ||
|
||||
firstLevel?.coverImageSrc?.trim() ||
|
||||
firstLevel?.candidates.some((candidate) => candidate.imageSrc.trim()),
|
||||
);
|
||||
}
|
||||
|
||||
function getGenerationNoticeShelfKeys(item: CreationWorkShelfItem) {
|
||||
switch (item.source.kind) {
|
||||
case 'rpg':
|
||||
@@ -1790,6 +1917,7 @@ function buildPendingMatch3DWorks(
|
||||
updatedAt: state.updatedAt,
|
||||
publishedAt: null,
|
||||
publishReady: false,
|
||||
generationStatus: state.status === 'generating' ? 'generating' : 'ready',
|
||||
generatedItemAssets: [],
|
||||
}));
|
||||
}
|
||||
@@ -1867,6 +1995,7 @@ function buildPendingPuzzleWorks(
|
||||
remixCount: 0,
|
||||
likeCount: 0,
|
||||
publishReady: false,
|
||||
generationStatus: state.status === 'generating' ? 'generating' : 'ready',
|
||||
levels: [],
|
||||
};
|
||||
});
|
||||
@@ -2885,7 +3014,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
|
||||
try {
|
||||
const worksResponse = await listMatch3DWorks();
|
||||
setMatch3DWorks(worksResponse.items);
|
||||
setMatch3DWorks(mapMatch3DWorksForRuntimeUi(worksResponse.items));
|
||||
match3DErrorSetterRef.current(null);
|
||||
} catch (error) {
|
||||
match3DErrorSetterRef.current(
|
||||
@@ -2899,8 +3028,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
const refreshMatch3DGallery = useCallback(async () => {
|
||||
try {
|
||||
const galleryResponse = await listMatch3DGallery();
|
||||
setMatch3DGalleryEntries(galleryResponse.items);
|
||||
return galleryResponse.items;
|
||||
const items = mapMatch3DWorksForRuntimeUi(galleryResponse.items);
|
||||
setMatch3DGalleryEntries(items);
|
||||
return items;
|
||||
} catch {
|
||||
// 中文注释:公开广场是首页展示数据,失败时只降级为空列表;
|
||||
// 不写入创作错误态,避免挡住抓大鹅共创入口。
|
||||
@@ -3247,7 +3377,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
.map(mapBabyObjectMatchDraftToPlatformGalleryCard)
|
||||
: [];
|
||||
const match3dPublicEntries = match3dGalleryEntries.map(
|
||||
mapMatch3DWorkToPlatformGalleryCard,
|
||||
mapMatch3DWorkToPublicWorkDetail,
|
||||
);
|
||||
const puzzlePublicEntries = puzzleGalleryEntries.map(
|
||||
mapPuzzleWorkToPlatformGalleryCard,
|
||||
@@ -3289,7 +3419,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
...(isBigFishCreationVisible
|
||||
? bigFishGalleryEntries.map(mapBigFishWorkToPlatformGalleryCard)
|
||||
: []),
|
||||
...match3dGalleryEntries.map(mapMatch3DWorkToPlatformGalleryCard),
|
||||
...match3dGalleryEntries.map(mapMatch3DWorkToPublicWorkDetail),
|
||||
...puzzleGalleryEntries.map(mapPuzzleWorkToPlatformGalleryCard),
|
||||
...squareHoleGalleryEntries.map(
|
||||
mapSquareHoleWorkToPlatformGalleryCard,
|
||||
@@ -3395,7 +3525,8 @@ export function PlatformEntryFlowShellImpl({
|
||||
getGenerationNoticeShelfKeys(item),
|
||||
);
|
||||
return {
|
||||
isGenerating: notice?.status === 'generating',
|
||||
isGenerating:
|
||||
notice?.status === 'generating' || item.isGenerating === true,
|
||||
hasUnreadUpdate: notice?.status === 'ready' && !notice.seen,
|
||||
};
|
||||
},
|
||||
@@ -3769,14 +3900,12 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
setBigFishGenerationState((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
phase: 'ready',
|
||||
? resolveFinishedMiniGameDraftGenerationState(current, 'ready', {
|
||||
completedAssetCount: response.session.assetSlots.filter(
|
||||
(slot) => slot.status === 'ready',
|
||||
).length,
|
||||
totalAssetCount: response.session.assetSlots.length,
|
||||
}
|
||||
})
|
||||
: current,
|
||||
);
|
||||
const openResult = selectionStageRef.current === 'big-fish-generating';
|
||||
@@ -3808,11 +3937,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
setBigFishGenerationState((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
phase: 'failed',
|
||||
? resolveFinishedMiniGameDraftGenerationState(current, 'failed', {
|
||||
error: errorMessage,
|
||||
}
|
||||
})
|
||||
: current,
|
||||
);
|
||||
},
|
||||
@@ -3864,14 +3991,12 @@ export function PlatformEntryFlowShellImpl({
|
||||
const openResult = selectionStageRef.current === 'match3d-generating';
|
||||
setMatch3DGenerationState((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
phase: 'ready',
|
||||
? resolveFinishedMiniGameDraftGenerationState(current, 'ready', {
|
||||
completedAssetCount:
|
||||
response.session.draft?.generatedItemAssets?.length ?? 5,
|
||||
totalAssetCount:
|
||||
response.session.draft?.generatedItemAssets?.length ?? 5,
|
||||
}
|
||||
})
|
||||
: current,
|
||||
);
|
||||
|
||||
@@ -3885,7 +4010,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
let runtimeProfile: Match3DWorkProfile | null = null;
|
||||
try {
|
||||
const { item } = await getMatch3DWorkDetail(profileId);
|
||||
runtimeProfile = promoteMatch3DGeneratedBackgroundAsset({
|
||||
runtimeProfile = normalizeMatch3DWorkForRuntimeUi({
|
||||
...item,
|
||||
generatedItemAssets: mergeMatch3DGeneratedItemAssetsForRuntime(
|
||||
response.session.draft?.generatedItemAssets,
|
||||
@@ -3949,11 +4074,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
setMatch3DGenerationState((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
phase: 'failed',
|
||||
? resolveFinishedMiniGameDraftGenerationState(current, 'failed', {
|
||||
error: errorMessage,
|
||||
}
|
||||
})
|
||||
: current,
|
||||
);
|
||||
try {
|
||||
@@ -3964,7 +4087,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
latestSession.draft?.profileId ?? latestSession.publishedProfileId;
|
||||
if (profileId) {
|
||||
const { item } = await getMatch3DWorkDetail(profileId);
|
||||
setMatch3DProfile(item);
|
||||
setMatch3DProfile(normalizeMatch3DWorkForRuntimeUi(item));
|
||||
}
|
||||
await refreshMatch3DShelf().catch(() => undefined);
|
||||
} catch {
|
||||
@@ -4101,15 +4224,19 @@ export function PlatformEntryFlowShellImpl({
|
||||
const { item } = await getSquareHoleWorkDetail(assetProfileId);
|
||||
const shouldOpenResult = shouldOpenSquareHoleResult();
|
||||
setSquareHoleProfile(item);
|
||||
setSquareHoleGenerationState((current) => ({
|
||||
...(current ?? createMiniGameDraftGenerationState('square-hole')),
|
||||
phase: 'ready',
|
||||
completedAssetCount:
|
||||
item.shapeOptions.length + item.holeOptions.length + 2,
|
||||
totalAssetCount:
|
||||
item.shapeOptions.length + item.holeOptions.length + 2,
|
||||
error: null,
|
||||
}));
|
||||
setSquareHoleGenerationState((current) =>
|
||||
resolveFinishedMiniGameDraftGenerationState(
|
||||
current ?? createMiniGameDraftGenerationState('square-hole'),
|
||||
'ready',
|
||||
{
|
||||
completedAssetCount:
|
||||
item.shapeOptions.length + item.holeOptions.length + 2,
|
||||
totalAssetCount:
|
||||
item.shapeOptions.length + item.holeOptions.length + 2,
|
||||
error: null,
|
||||
},
|
||||
),
|
||||
);
|
||||
await refreshSquareHoleShelf().catch(() => undefined);
|
||||
markPendingDraftReady(
|
||||
'square-hole',
|
||||
@@ -4131,11 +4258,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
'生成方洞挑战图片失败。',
|
||||
);
|
||||
setSquareHoleError(errorMessage);
|
||||
setSquareHoleGenerationState((current) => ({
|
||||
...(current ?? createMiniGameDraftGenerationState('square-hole')),
|
||||
phase: 'failed',
|
||||
error: errorMessage,
|
||||
}));
|
||||
setSquareHoleGenerationState((current) =>
|
||||
resolveFinishedMiniGameDraftGenerationState(
|
||||
current ?? createMiniGameDraftGenerationState('square-hole'),
|
||||
'failed',
|
||||
{ error: errorMessage },
|
||||
),
|
||||
);
|
||||
setSquareHoleProfile(
|
||||
buildSquareHoleProfileFromSession(response.session),
|
||||
);
|
||||
@@ -4150,15 +4279,19 @@ export function PlatformEntryFlowShellImpl({
|
||||
const { item } = await getSquareHoleWorkDetail(profileId);
|
||||
const shouldOpenResult = shouldOpenSquareHoleResult();
|
||||
setSquareHoleProfile(item);
|
||||
setSquareHoleGenerationState((current) => ({
|
||||
...(current ?? createMiniGameDraftGenerationState('square-hole')),
|
||||
phase: 'ready',
|
||||
completedAssetCount:
|
||||
item.shapeOptions.length + item.holeOptions.length + 2,
|
||||
totalAssetCount:
|
||||
item.shapeOptions.length + item.holeOptions.length + 2,
|
||||
error: null,
|
||||
}));
|
||||
setSquareHoleGenerationState((current) =>
|
||||
resolveFinishedMiniGameDraftGenerationState(
|
||||
current ?? createMiniGameDraftGenerationState('square-hole'),
|
||||
'ready',
|
||||
{
|
||||
completedAssetCount:
|
||||
item.shapeOptions.length + item.holeOptions.length + 2,
|
||||
totalAssetCount:
|
||||
item.shapeOptions.length + item.holeOptions.length + 2,
|
||||
error: null,
|
||||
},
|
||||
),
|
||||
);
|
||||
await refreshSquareHoleShelf().catch(() => undefined);
|
||||
markPendingDraftReady(
|
||||
'square-hole',
|
||||
@@ -4198,11 +4331,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
payload.action === 'square_hole_compile_draft' ||
|
||||
payload.action === 'square_hole_generate_visual_assets'
|
||||
) {
|
||||
setSquareHoleGenerationState((current) => ({
|
||||
...(current ?? createMiniGameDraftGenerationState('square-hole')),
|
||||
phase: 'failed',
|
||||
error: errorMessage,
|
||||
}));
|
||||
setSquareHoleGenerationState((current) =>
|
||||
resolveFinishedMiniGameDraftGenerationState(
|
||||
current ?? createMiniGameDraftGenerationState('square-hole'),
|
||||
'failed',
|
||||
{ error: errorMessage },
|
||||
),
|
||||
);
|
||||
if (selectionStageRef.current === 'square-hole-generating') {
|
||||
setSelectionStage('square-hole-generating');
|
||||
}
|
||||
@@ -4275,12 +4410,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
const openResult = selectionStageRef.current === 'puzzle-generating';
|
||||
setPuzzleGenerationState((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
phase: 'ready',
|
||||
? resolveFinishedMiniGameDraftGenerationState(current, 'ready', {
|
||||
completedAssetCount: 1,
|
||||
totalAssetCount: 1,
|
||||
}
|
||||
})
|
||||
: current,
|
||||
);
|
||||
const profileId =
|
||||
@@ -4385,19 +4518,56 @@ export function PlatformEntryFlowShellImpl({
|
||||
markPendingDraftGenerating('puzzle', session.sessionId);
|
||||
selectionStageRef.current = 'puzzle-generating';
|
||||
setSelectionStage('puzzle-generating');
|
||||
setPuzzleGenerationState(createMiniGameDraftGenerationState('puzzle'));
|
||||
const nextGenerationState = createMiniGameDraftGenerationState('puzzle');
|
||||
setPuzzleGenerationState(nextGenerationState);
|
||||
setPuzzleBackgroundCompileTasks((current) => ({
|
||||
...current,
|
||||
[session.sessionId]: {
|
||||
session,
|
||||
payload: formPayload ?? buildPuzzleFormPayloadFromSession(session),
|
||||
generationState: nextGenerationState,
|
||||
error: null,
|
||||
},
|
||||
}));
|
||||
},
|
||||
onActionError: ({ payload, errorMessage }) => {
|
||||
onActionError: async ({ payload, errorMessage, session, setSession }) => {
|
||||
if (payload.action !== 'compile_puzzle_draft') {
|
||||
return;
|
||||
}
|
||||
const generationState =
|
||||
puzzleBackgroundCompileTasks[session.sessionId]?.generationState ??
|
||||
puzzleGenerationState ??
|
||||
createMiniGameDraftGenerationState('puzzle');
|
||||
const formPayload =
|
||||
buildPuzzleFormPayloadFromAction(payload) ??
|
||||
puzzleBackgroundCompileTasks[session.sessionId]?.payload ??
|
||||
buildPuzzleFormPayloadFromSession(session);
|
||||
const recovered = await recoverCompletedPuzzleDraftGeneration({
|
||||
sessionId: session.sessionId,
|
||||
payload: formPayload,
|
||||
generationState,
|
||||
setSession,
|
||||
});
|
||||
if (recovered) {
|
||||
return;
|
||||
}
|
||||
const failedGenerationState = resolveFinishedMiniGameDraftGenerationState(
|
||||
generationState,
|
||||
'failed',
|
||||
{ error: errorMessage },
|
||||
);
|
||||
setPuzzleBackgroundCompileTasks((current) => ({
|
||||
...current,
|
||||
[session.sessionId]: {
|
||||
session,
|
||||
payload: formPayload,
|
||||
generationState: failedGenerationState,
|
||||
error: errorMessage,
|
||||
},
|
||||
}));
|
||||
setPuzzleGenerationState((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
phase: 'failed',
|
||||
error: errorMessage,
|
||||
}
|
||||
? failedGenerationState
|
||||
: current,
|
||||
);
|
||||
},
|
||||
@@ -4527,6 +4697,124 @@ export function PlatformEntryFlowShellImpl({
|
||||
setMatch3DError,
|
||||
);
|
||||
}, [ensureEnoughDraftGenerationPointsFromServer, setMatch3DError]);
|
||||
const recoverCompletedPuzzleDraftGeneration = useCallback(
|
||||
async ({
|
||||
sessionId,
|
||||
payload,
|
||||
generationState,
|
||||
setSession,
|
||||
}: {
|
||||
sessionId: string;
|
||||
payload: CreatePuzzleAgentSessionRequest;
|
||||
generationState: MiniGameDraftGenerationState;
|
||||
setSession?: (session: PuzzleAgentSessionSnapshot) => void;
|
||||
}) => {
|
||||
let latestSession: PuzzleAgentSessionSnapshot;
|
||||
try {
|
||||
const response = await getPuzzleAgentSession(sessionId);
|
||||
latestSession = normalizeRecoveredPuzzleDraftSession(response.session);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!hasRecoverableGeneratedPuzzleDraft(latestSession)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const readyGenerationState = resolveFinishedMiniGameDraftGenerationState(
|
||||
generationState,
|
||||
'ready',
|
||||
{
|
||||
completedAssetCount: 1,
|
||||
totalAssetCount: 1,
|
||||
error: null,
|
||||
},
|
||||
);
|
||||
const openResult = isViewingPuzzleGeneration(sessionId);
|
||||
const profileId =
|
||||
latestSession.publishedProfileId ??
|
||||
buildPuzzleResultProfileId(latestSession.sessionId);
|
||||
|
||||
setPuzzleBackgroundCompileTasks((current) => ({
|
||||
...current,
|
||||
[sessionId]: {
|
||||
session: latestSession,
|
||||
payload,
|
||||
generationState: readyGenerationState,
|
||||
error: null,
|
||||
},
|
||||
}));
|
||||
|
||||
setSession?.(latestSession);
|
||||
setPuzzleFormDraftPayload(payload);
|
||||
setPuzzleOperation(null);
|
||||
puzzleErrorSetterRef.current(null);
|
||||
if (isViewingPuzzleGeneration(sessionId)) {
|
||||
setPuzzleGenerationState(readyGenerationState);
|
||||
}
|
||||
|
||||
markPendingDraftReady('puzzle', latestSession.sessionId, openResult);
|
||||
markDraftReady(
|
||||
'puzzle',
|
||||
[
|
||||
latestSession.sessionId,
|
||||
buildPuzzleResultWorkId(latestSession.sessionId),
|
||||
profileId,
|
||||
],
|
||||
openResult,
|
||||
);
|
||||
await refreshPuzzleShelf().catch(() => undefined);
|
||||
|
||||
if (!openResult) {
|
||||
return { openResult };
|
||||
}
|
||||
|
||||
const draft = latestSession.draft;
|
||||
if (!draft?.coverImageSrc || !profileId) {
|
||||
puzzleErrorSetterRef.current(
|
||||
!draft?.coverImageSrc
|
||||
? '请先选择一张正式拼图图片。'
|
||||
: '这份拼图草稿缺少会话信息,请重新开始创作。',
|
||||
);
|
||||
setSelectionStage('puzzle-result');
|
||||
return { openResult: false };
|
||||
}
|
||||
|
||||
try {
|
||||
const { item } = await updatePuzzleWork(profileId, {
|
||||
workTitle: draft.workTitle,
|
||||
workDescription: draft.workDescription,
|
||||
levelName: draft.levelName,
|
||||
summary: draft.summary,
|
||||
themeTags: draft.themeTags,
|
||||
coverImageSrc: draft.coverImageSrc,
|
||||
coverAssetId: draft.coverAssetId,
|
||||
levels: draft.levels ?? [],
|
||||
});
|
||||
const run = startLocalPuzzleRun(item);
|
||||
setSelectedPuzzleDetail(item);
|
||||
setPuzzleRun(run);
|
||||
setPuzzleRuntimeAuthMode('default');
|
||||
setPuzzleRuntimeReturnStage('puzzle-result');
|
||||
setSelectionStage('puzzle-runtime');
|
||||
} catch (error) {
|
||||
puzzleErrorSetterRef.current(
|
||||
resolvePuzzleErrorMessage(error, '启动拼图试玩失败。'),
|
||||
);
|
||||
setSelectionStage('puzzle-result');
|
||||
}
|
||||
|
||||
return { openResult: false };
|
||||
},
|
||||
[
|
||||
isViewingPuzzleGeneration,
|
||||
markDraftReady,
|
||||
markPendingDraftReady,
|
||||
refreshPuzzleShelf,
|
||||
resolvePuzzleErrorMessage,
|
||||
setSelectionStage,
|
||||
],
|
||||
);
|
||||
|
||||
const activeMatch3DGenerationSessionId =
|
||||
selectionStage === 'match3d-generating'
|
||||
@@ -4612,11 +4900,12 @@ export function PlatformEntryFlowShellImpl({
|
||||
return;
|
||||
}
|
||||
|
||||
setMatch3DProfile(item);
|
||||
const normalizedItem = normalizeMatch3DWorkForRuntimeUi(item);
|
||||
setMatch3DProfile(normalizedItem);
|
||||
setMatch3DGenerationState((current) =>
|
||||
resolveMatch3DGenerationStateFromAssets(
|
||||
current,
|
||||
item.generatedItemAssets,
|
||||
normalizedItem.generatedItemAssets,
|
||||
),
|
||||
);
|
||||
} catch {
|
||||
@@ -4780,12 +5069,14 @@ export function PlatformEntryFlowShellImpl({
|
||||
);
|
||||
setPuzzleOperation(response.operation);
|
||||
const openResult = isViewingPuzzleGeneration(nextSession.sessionId);
|
||||
const readyGenerationState = {
|
||||
...generationState,
|
||||
phase: 'ready' as const,
|
||||
completedAssetCount: 1,
|
||||
totalAssetCount: 1,
|
||||
};
|
||||
const readyGenerationState = resolveFinishedMiniGameDraftGenerationState(
|
||||
generationState,
|
||||
'ready',
|
||||
{
|
||||
completedAssetCount: 1,
|
||||
totalAssetCount: 1,
|
||||
},
|
||||
);
|
||||
setPuzzleBackgroundCompileTasks((current) => ({
|
||||
...current,
|
||||
[nextSession.sessionId]: {
|
||||
@@ -4859,11 +5150,20 @@ export function PlatformEntryFlowShellImpl({
|
||||
error,
|
||||
'执行拼图操作失败。',
|
||||
);
|
||||
const failedGenerationState = {
|
||||
...generationState,
|
||||
phase: 'failed' as const,
|
||||
error: errorMessage,
|
||||
};
|
||||
const recovered = await recoverCompletedPuzzleDraftGeneration({
|
||||
sessionId: nextSession.sessionId,
|
||||
payload,
|
||||
generationState,
|
||||
setSession: puzzleFlow.setSession,
|
||||
});
|
||||
if (recovered) {
|
||||
return;
|
||||
}
|
||||
const failedGenerationState = resolveFinishedMiniGameDraftGenerationState(
|
||||
generationState,
|
||||
'failed',
|
||||
{ error: errorMessage },
|
||||
);
|
||||
setPuzzleBackgroundCompileTasks((current) => ({
|
||||
...current,
|
||||
[nextSession.sessionId]: {
|
||||
@@ -4888,6 +5188,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
preflightPuzzleDraftGeneration,
|
||||
puzzleFlow,
|
||||
refreshPuzzleShelf,
|
||||
recoverCompletedPuzzleDraftGeneration,
|
||||
resolvePuzzleErrorMessage,
|
||||
setPuzzleError,
|
||||
setSelectionStage,
|
||||
@@ -4950,14 +5251,16 @@ export function PlatformEntryFlowShellImpl({
|
||||
},
|
||||
);
|
||||
const openResult = isViewingMatch3DGeneration(nextSession.sessionId);
|
||||
const readyGenerationState = {
|
||||
...generationState,
|
||||
phase: 'ready' as const,
|
||||
completedAssetCount:
|
||||
response.session.draft?.generatedItemAssets?.length ?? 5,
|
||||
totalAssetCount:
|
||||
response.session.draft?.generatedItemAssets?.length ?? 5,
|
||||
};
|
||||
const readyGenerationState = resolveFinishedMiniGameDraftGenerationState(
|
||||
generationState,
|
||||
'ready',
|
||||
{
|
||||
completedAssetCount:
|
||||
response.session.draft?.generatedItemAssets?.length ?? 5,
|
||||
totalAssetCount:
|
||||
response.session.draft?.generatedItemAssets?.length ?? 5,
|
||||
},
|
||||
);
|
||||
setMatch3DBackgroundCompileTasks((current) => ({
|
||||
...current,
|
||||
[nextSession.sessionId]: {
|
||||
@@ -4984,7 +5287,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
let runtimeProfile: Match3DWorkProfile | null = null;
|
||||
try {
|
||||
const { item } = await getMatch3DWorkDetail(profileId);
|
||||
runtimeProfile = promoteMatch3DGeneratedBackgroundAsset({
|
||||
runtimeProfile = normalizeMatch3DWorkForRuntimeUi({
|
||||
...item,
|
||||
generatedItemAssets: mergeMatch3DGeneratedItemAssetsForRuntime(
|
||||
response.session.draft?.generatedItemAssets,
|
||||
@@ -5039,11 +5342,11 @@ export function PlatformEntryFlowShellImpl({
|
||||
error,
|
||||
'执行抓大鹅操作失败。',
|
||||
);
|
||||
const failedGenerationState = {
|
||||
...generationState,
|
||||
phase: 'failed' as const,
|
||||
error: errorMessage,
|
||||
};
|
||||
const failedGenerationState = resolveFinishedMiniGameDraftGenerationState(
|
||||
generationState,
|
||||
'failed',
|
||||
{ error: errorMessage },
|
||||
);
|
||||
setMatch3DBackgroundCompileTasks((current) => ({
|
||||
...current,
|
||||
[nextSession.sessionId]: {
|
||||
@@ -5075,7 +5378,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
latestSession.draft?.profileId ?? latestSession.publishedProfileId;
|
||||
if (profileId) {
|
||||
const { item } = await getMatch3DWorkDetail(profileId);
|
||||
setMatch3DProfile(item);
|
||||
setMatch3DProfile(normalizeMatch3DWorkForRuntimeUi(item));
|
||||
}
|
||||
}
|
||||
await refreshMatch3DShelf().catch(() => undefined);
|
||||
@@ -5203,12 +5506,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
setBabyObjectMatchGenerationPhase('ready');
|
||||
setBabyObjectMatchGenerationState((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
phase: 'ready',
|
||||
? resolveFinishedMiniGameDraftGenerationState(current, 'ready', {
|
||||
completedAssetCount: response.draft.itemAssets.length,
|
||||
totalAssetCount: response.draft.itemAssets.length,
|
||||
}
|
||||
})
|
||||
: current,
|
||||
);
|
||||
const openResult =
|
||||
@@ -5230,11 +5531,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
setBabyObjectMatchError(errorMessage);
|
||||
setBabyObjectMatchGenerationState((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
phase: 'failed',
|
||||
? resolveFinishedMiniGameDraftGenerationState(current, 'failed', {
|
||||
error: errorMessage,
|
||||
}
|
||||
})
|
||||
: current,
|
||||
);
|
||||
} finally {
|
||||
@@ -6702,13 +7001,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
) {
|
||||
try {
|
||||
const { item } = await getMatch3DWorkDetail(profile.profileId);
|
||||
runtimeProfile = {
|
||||
runtimeProfile = normalizeMatch3DWorkForRuntimeUi({
|
||||
...item,
|
||||
generatedItemAssets: mergeMatch3DGeneratedItemAssetsForRuntime(
|
||||
item.generatedItemAssets,
|
||||
profile.generatedItemAssets,
|
||||
),
|
||||
};
|
||||
});
|
||||
} catch {
|
||||
// 中文注释:详情补读只为拿完整生成素材;失败时继续按摘要开局,避免推荐流卡死。
|
||||
}
|
||||
@@ -7777,7 +8076,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
void deleteMatch3DWork(work.profileId)
|
||||
.then((response) => {
|
||||
markDraftNoticeSeen(noticeKeys);
|
||||
setMatch3DWorks(response.items);
|
||||
setMatch3DWorks(mapMatch3DWorksForRuntimeUi(response.items));
|
||||
void refreshMatch3DGallery();
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -8632,7 +8931,12 @@ export function PlatformEntryFlowShellImpl({
|
||||
);
|
||||
puzzleFlow.setSession(latestSession);
|
||||
setPuzzleFormDraftPayload(buildPuzzleFormPayloadFromSession(latestSession));
|
||||
setPuzzleGenerationState(createMiniGameDraftGenerationState('puzzle'));
|
||||
setPuzzleGenerationState(
|
||||
createMiniGameDraftGenerationStateFromStartedAt(
|
||||
'puzzle',
|
||||
parseDraftGenerationStartedAtMs(item.updatedAt),
|
||||
),
|
||||
);
|
||||
enterCreateTab();
|
||||
selectionStageRef.current = 'puzzle-generating';
|
||||
activePuzzleGenerationSessionIdRef.current = item.sourceSessionId;
|
||||
@@ -8734,13 +9038,14 @@ export function PlatformEntryFlowShellImpl({
|
||||
setMatch3DFormDraftPayload(null);
|
||||
const profileId = latestSession.draft?.profileId ?? item.profileId;
|
||||
const { item: profile } = await getMatch3DWorkDetail(profileId);
|
||||
const normalizedProfile = normalizeMatch3DWorkForRuntimeUi(profile);
|
||||
match3dFlow.setIsBusy(false);
|
||||
const started = await startMatch3DRunFromProfile(
|
||||
profile,
|
||||
normalizedProfile,
|
||||
'match3d-result',
|
||||
);
|
||||
if (!started) {
|
||||
setMatch3DProfile(profile);
|
||||
setMatch3DProfile(normalizedProfile);
|
||||
enterCreateTab();
|
||||
setSelectionStage('match3d-result');
|
||||
}
|
||||
@@ -8823,7 +9128,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
try {
|
||||
const profileId = restoredSession.draft?.profileId ?? item.profileId;
|
||||
const { item: profile } = await getMatch3DWorkDetail(profileId);
|
||||
setMatch3DProfile(profile);
|
||||
setMatch3DProfile(normalizeMatch3DWorkForRuntimeUi(profile));
|
||||
} catch (error) {
|
||||
setMatch3DProfile(buildMatch3DProfileFromSession(restoredSession));
|
||||
setMatch3DError(
|
||||
@@ -11538,29 +11843,33 @@ export function PlatformEntryFlowShellImpl({
|
||||
returnToCreationCenterFromGeneration();
|
||||
}}
|
||||
onSaved={(profile) => {
|
||||
setMatch3DProfile(profile);
|
||||
setMatch3DProfile(normalizeMatch3DWorkForRuntimeUi(profile));
|
||||
}}
|
||||
onPublished={(profile) => {
|
||||
setMatch3DProfile(profile);
|
||||
const normalizedProfile =
|
||||
normalizeMatch3DWorkForRuntimeUi(profile);
|
||||
setMatch3DProfile(normalizedProfile);
|
||||
void Promise.allSettled([
|
||||
refreshMatch3DShelf(),
|
||||
refreshMatch3DGallery(),
|
||||
]);
|
||||
openPublicWorkDetail(
|
||||
mapMatch3DWorkToPublicWorkDetail(profile),
|
||||
mapMatch3DWorkToPublicWorkDetail(normalizedProfile),
|
||||
);
|
||||
openPublishShareModal({
|
||||
title: profile.gameName,
|
||||
title: normalizedProfile.gameName,
|
||||
publicWorkCode: buildMatch3DPublicWorkCode(
|
||||
profile.profileId,
|
||||
normalizedProfile.profileId,
|
||||
),
|
||||
stage: 'work-detail',
|
||||
});
|
||||
}}
|
||||
onStartTestRun={(profile, options) => {
|
||||
setMatch3DProfile(profile);
|
||||
const normalizedProfile =
|
||||
normalizeMatch3DWorkForRuntimeUi(profile);
|
||||
setMatch3DProfile(normalizedProfile);
|
||||
void startMatch3DRunFromProfile(
|
||||
profile,
|
||||
normalizedProfile,
|
||||
'match3d-result',
|
||||
false,
|
||||
options,
|
||||
|
||||
Reference in New Issue
Block a user