refactor: 收口小游戏生成状态模型
This commit is contained in:
@@ -213,8 +213,6 @@ import {
|
||||
buildSquareHoleGenerationAnchorEntries,
|
||||
buildWoodenFishGenerationAnchorEntries,
|
||||
createMiniGameDraftGenerationState,
|
||||
type MiniGameDraftGenerationKind,
|
||||
type MiniGameDraftGenerationPhase,
|
||||
type MiniGameDraftGenerationState,
|
||||
resolveMiniGameDraftGenerationStartedAtMs,
|
||||
} from '../../services/miniGameDraftGenerationProgress';
|
||||
@@ -513,6 +511,17 @@ import {
|
||||
resolveMatch3DRuntimeGeneratedBackgroundAsset,
|
||||
resolveMatch3DRuntimeGeneratedItemAssets,
|
||||
} from './platformMatch3DRuntimeProfile';
|
||||
import {
|
||||
createFailedMiniGameDraftGenerationStateForRestoredDraft,
|
||||
createMiniGameDraftGenerationStateForRestoredDraft,
|
||||
createPuzzleDraftGenerationStateFromPayload,
|
||||
isMiniGameDraftGenerating,
|
||||
isMiniGameDraftReady,
|
||||
mergePuzzleSessionProgressIntoGenerationState,
|
||||
rebaseMiniGameDraftBackgroundCompileTaskForDisplay,
|
||||
rebaseMiniGameDraftGenerationStateForDisplay,
|
||||
resolveFinishedMiniGameDraftGenerationState,
|
||||
} from './platformMiniGameDraftGenerationStateModel';
|
||||
import {
|
||||
buildJumpHopPendingSession,
|
||||
buildPuzzleRuntimeWorkFromSession,
|
||||
@@ -1026,35 +1035,6 @@ function openPuzzleRuntimeStage(
|
||||
writePuzzleRuntimeUrlState(state);
|
||||
}
|
||||
|
||||
/** 为恢复的小游戏草稿重建生成态,保留后端开始时间作为进度事实源。 */
|
||||
function createMiniGameDraftGenerationStateForRestoredDraft(
|
||||
kind: MiniGameDraftGenerationKind,
|
||||
metadata?: MiniGameDraftGenerationState['metadata'],
|
||||
startedAtMs = Date.now(),
|
||||
): MiniGameDraftGenerationState {
|
||||
return {
|
||||
...createMiniGameDraftGenerationState(kind, startedAtMs),
|
||||
...(metadata ? { metadata } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function createFailedMiniGameDraftGenerationStateForRestoredDraft(
|
||||
kind: MiniGameDraftGenerationKind,
|
||||
updatedAt: string | null | undefined,
|
||||
error: string,
|
||||
metadata?: MiniGameDraftGenerationState['metadata'],
|
||||
): MiniGameDraftGenerationState {
|
||||
return resolveFinishedMiniGameDraftGenerationState(
|
||||
createMiniGameDraftGenerationStateForRestoredDraft(
|
||||
kind,
|
||||
metadata,
|
||||
resolveMiniGameDraftGenerationStartedAtMs(updatedAt),
|
||||
),
|
||||
'failed',
|
||||
{ error },
|
||||
);
|
||||
}
|
||||
|
||||
function buildPuzzleFormPayloadFromWork(
|
||||
item: PuzzleWorkSummary,
|
||||
): CreatePuzzleAgentSessionRequest {
|
||||
@@ -1138,122 +1118,6 @@ function buildMatch3DFormPayloadFromWork(
|
||||
};
|
||||
}
|
||||
|
||||
/** 清理生成态完成时间,避免返回生成页后继续沿用结束态计时。 */
|
||||
function rebaseMiniGameDraftGenerationStateForDisplay(
|
||||
state: MiniGameDraftGenerationState,
|
||||
): MiniGameDraftGenerationState {
|
||||
return {
|
||||
...state,
|
||||
finishedAtMs: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function rebaseMiniGameDraftBackgroundCompileTaskForDisplay<
|
||||
T extends PuzzleBackgroundCompileTask | Match3DBackgroundCompileTask,
|
||||
>(task: T): T {
|
||||
return {
|
||||
...task,
|
||||
generationState: rebaseMiniGameDraftGenerationStateForDisplay(
|
||||
task.generationState,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function createPuzzleDraftGenerationStateFromPayload(
|
||||
payload: CreatePuzzleAgentSessionRequest | null | undefined,
|
||||
session: PuzzleAgentSessionSnapshot | null | undefined = null,
|
||||
): MiniGameDraftGenerationState {
|
||||
const puzzleProgressPercent =
|
||||
session?.draft && !session.draft.formDraft
|
||||
? session.progressPercent
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...createMiniGameDraftGenerationState(
|
||||
'puzzle',
|
||||
resolveMiniGameDraftGenerationStartedAtMs(session?.updatedAt),
|
||||
),
|
||||
metadata: {
|
||||
puzzleAiRedraw: payload?.aiRedraw ?? true,
|
||||
puzzleActivePhaseId:
|
||||
typeof puzzleProgressPercent === 'number' ? 'compile' : undefined,
|
||||
puzzleActiveStepStartedAtMs:
|
||||
typeof puzzleProgressPercent === 'number' ? Date.now() : undefined,
|
||||
puzzleProgressPercent,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function resolvePuzzlePhaseFromSessionProgress(
|
||||
state: MiniGameDraftGenerationState,
|
||||
session: PuzzleAgentSessionSnapshot,
|
||||
): MiniGameDraftGenerationPhase {
|
||||
if (session.progressPercent >= 96) {
|
||||
return 'puzzle-select-image';
|
||||
}
|
||||
if (session.progressPercent >= 94) {
|
||||
return 'puzzle-ui-assets';
|
||||
}
|
||||
if (session.progressPercent >= 88) {
|
||||
return state.metadata?.puzzleAiRedraw === false
|
||||
? 'puzzle-level-scene'
|
||||
: 'puzzle-cover-image';
|
||||
}
|
||||
|
||||
return 'compile';
|
||||
}
|
||||
|
||||
function mergePuzzleSessionProgressIntoGenerationState(
|
||||
state: MiniGameDraftGenerationState,
|
||||
session: PuzzleAgentSessionSnapshot,
|
||||
): MiniGameDraftGenerationState {
|
||||
const isCompiledGenerationSession = Boolean(
|
||||
session.draft && !session.draft.formDraft,
|
||||
);
|
||||
|
||||
const nextPhaseId = isCompiledGenerationSession
|
||||
? resolvePuzzlePhaseFromSessionProgress(state, session)
|
||||
: state.metadata?.puzzleActivePhaseId;
|
||||
const shouldResetActiveStepStart =
|
||||
isCompiledGenerationSession &&
|
||||
nextPhaseId != null &&
|
||||
nextPhaseId !== state.metadata?.puzzleActivePhaseId;
|
||||
|
||||
return {
|
||||
...state,
|
||||
metadata: {
|
||||
...state.metadata,
|
||||
puzzleActivePhaseId: nextPhaseId,
|
||||
puzzleActiveStepStartedAtMs: shouldResetActiveStepStart
|
||||
? resolveMiniGameDraftGenerationStartedAtMs(session.updatedAt)
|
||||
: state.metadata?.puzzleActiveStepStartedAtMs,
|
||||
puzzleProgressPercent: isCompiledGenerationSession
|
||||
? session.progressPercent
|
||||
: state.metadata?.puzzleProgressPercent,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -1325,14 +1189,6 @@ function hasRecoverableGeneratedPuzzleDraft(
|
||||
);
|
||||
}
|
||||
|
||||
function isMiniGameDraftReady(state: MiniGameDraftGenerationState | null) {
|
||||
return state?.phase === 'ready';
|
||||
}
|
||||
|
||||
function isMiniGameDraftGenerating(state: MiniGameDraftGenerationState | null) {
|
||||
return Boolean(state && state.phase !== 'ready' && state.phase !== 'failed');
|
||||
}
|
||||
|
||||
function resolveProfileWalletBalance(
|
||||
dashboard: { walletBalance?: number | null } | null | undefined,
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import type { PuzzleAnchorPack } from '../../../packages/shared/src/contracts/puzzleAgentDraft';
|
||||
import type {
|
||||
CreatePuzzleAgentSessionRequest,
|
||||
PuzzleAgentSessionSnapshot,
|
||||
} from '../../../packages/shared/src/contracts/puzzleAgentSession';
|
||||
import type { MiniGameDraftGenerationState } from '../../services/miniGameDraftGenerationProgress';
|
||||
import {
|
||||
createFailedMiniGameDraftGenerationStateForRestoredDraft,
|
||||
createMiniGameDraftGenerationStateForRestoredDraft,
|
||||
createPuzzleDraftGenerationStateFromPayload,
|
||||
isMiniGameDraftGenerating,
|
||||
isMiniGameDraftReady,
|
||||
mergePuzzleSessionProgressIntoGenerationState,
|
||||
rebaseMiniGameDraftBackgroundCompileTaskForDisplay,
|
||||
rebaseMiniGameDraftGenerationStateForDisplay,
|
||||
resolveFinishedMiniGameDraftGenerationState,
|
||||
resolvePuzzlePhaseFromSessionProgress,
|
||||
} from './platformMiniGameDraftGenerationStateModel';
|
||||
|
||||
const NOW = Date.parse('2026-06-04T03:00:00.000Z');
|
||||
const SESSION_UPDATED_AT = '2026-06-01T10:00:00.000Z';
|
||||
const SESSION_UPDATED_AT_MS = Date.parse(SESSION_UPDATED_AT);
|
||||
|
||||
function buildAnchorPack(): PuzzleAnchorPack {
|
||||
const item = {
|
||||
key: 'theme',
|
||||
label: '主题',
|
||||
value: '星桥机关',
|
||||
status: 'confirmed' as const,
|
||||
};
|
||||
return {
|
||||
themePromise: item,
|
||||
visualSubject: item,
|
||||
visualMood: item,
|
||||
compositionHooks: item,
|
||||
tagsAndForbidden: item,
|
||||
};
|
||||
}
|
||||
|
||||
function buildPuzzleSession(
|
||||
overrides: Partial<PuzzleAgentSessionSnapshot> = {},
|
||||
): PuzzleAgentSessionSnapshot {
|
||||
const anchorPack = buildAnchorPack();
|
||||
return {
|
||||
sessionId: 'puzzle-session-1',
|
||||
seedText: '星桥',
|
||||
currentTurn: 1,
|
||||
progressPercent: 90,
|
||||
stage: 'draft_ready',
|
||||
anchorPack,
|
||||
draft: {
|
||||
workTitle: '星桥拼图',
|
||||
workDescription: '修复星桥机关。',
|
||||
levelName: '星桥机关',
|
||||
summary: '把星桥碎片拼回原位。',
|
||||
themeTags: ['星桥'],
|
||||
forbiddenDirectives: [],
|
||||
creatorIntent: null,
|
||||
anchorPack,
|
||||
candidates: [],
|
||||
selectedCandidateId: null,
|
||||
coverImageSrc: null,
|
||||
coverAssetId: null,
|
||||
generationStatus: 'generating',
|
||||
levels: [],
|
||||
},
|
||||
messages: [],
|
||||
lastAssistantReply: null,
|
||||
publishedProfileId: null,
|
||||
suggestedActions: [],
|
||||
resultPreview: null,
|
||||
updatedAt: SESSION_UPDATED_AT,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildState(
|
||||
overrides: Partial<MiniGameDraftGenerationState> = {},
|
||||
): MiniGameDraftGenerationState {
|
||||
return {
|
||||
kind: 'puzzle',
|
||||
phase: 'compile',
|
||||
startedAtMs: 100,
|
||||
completedAssetCount: 0,
|
||||
totalAssetCount: 0,
|
||||
error: null,
|
||||
metadata: {
|
||||
puzzleAiRedraw: true,
|
||||
puzzleActivePhaseId: 'compile',
|
||||
puzzleActiveStepStartedAtMs: 200,
|
||||
puzzleProgressPercent: 20,
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(NOW);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe('platformMiniGameDraftGenerationStateModel', () => {
|
||||
test('creates restored generation state with metadata and explicit start time', () => {
|
||||
expect(
|
||||
createMiniGameDraftGenerationStateForRestoredDraft(
|
||||
'match3d',
|
||||
{ puzzleAiRedraw: false },
|
||||
123,
|
||||
),
|
||||
).toMatchObject({
|
||||
kind: 'match3d',
|
||||
phase: 'match3d-work-title',
|
||||
startedAtMs: 123,
|
||||
metadata: {
|
||||
puzzleAiRedraw: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('creates failed restored state from backend updated time', () => {
|
||||
expect(
|
||||
createFailedMiniGameDraftGenerationStateForRestoredDraft(
|
||||
'puzzle',
|
||||
SESSION_UPDATED_AT,
|
||||
'生成失败',
|
||||
{ puzzleAiRedraw: true },
|
||||
),
|
||||
).toMatchObject({
|
||||
kind: 'puzzle',
|
||||
phase: 'failed',
|
||||
startedAtMs: SESSION_UPDATED_AT_MS,
|
||||
finishedAtMs: NOW,
|
||||
error: '生成失败',
|
||||
metadata: {
|
||||
puzzleAiRedraw: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('rebases finished state for display without changing other fields', () => {
|
||||
const state = buildState({
|
||||
phase: 'ready',
|
||||
finishedAtMs: 300,
|
||||
completedAssetCount: 2,
|
||||
totalAssetCount: 3,
|
||||
});
|
||||
|
||||
expect(rebaseMiniGameDraftGenerationStateForDisplay(state)).toEqual({
|
||||
...state,
|
||||
finishedAtMs: undefined,
|
||||
});
|
||||
expect(
|
||||
rebaseMiniGameDraftBackgroundCompileTaskForDisplay({
|
||||
sessionId: 'task-1',
|
||||
generationState: state,
|
||||
}),
|
||||
).toEqual({
|
||||
sessionId: 'task-1',
|
||||
generationState: {
|
||||
...state,
|
||||
finishedAtMs: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('creates puzzle generation state from payload and compiled session', () => {
|
||||
const payload: CreatePuzzleAgentSessionRequest = {
|
||||
seedText: '星桥',
|
||||
aiRedraw: false,
|
||||
};
|
||||
|
||||
expect(createPuzzleDraftGenerationStateFromPayload(payload)).toMatchObject({
|
||||
kind: 'puzzle',
|
||||
phase: 'compile',
|
||||
startedAtMs: NOW,
|
||||
metadata: {
|
||||
puzzleAiRedraw: false,
|
||||
puzzleActivePhaseId: undefined,
|
||||
puzzleActiveStepStartedAtMs: undefined,
|
||||
puzzleProgressPercent: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
createPuzzleDraftGenerationStateFromPayload(payload, buildPuzzleSession()),
|
||||
).toMatchObject({
|
||||
kind: 'puzzle',
|
||||
phase: 'compile',
|
||||
startedAtMs: SESSION_UPDATED_AT_MS,
|
||||
metadata: {
|
||||
puzzleAiRedraw: false,
|
||||
puzzleActivePhaseId: 'compile',
|
||||
puzzleActiveStepStartedAtMs: NOW,
|
||||
puzzleProgressPercent: 90,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('resolves puzzle phase from backend progress thresholds', () => {
|
||||
const state = buildState();
|
||||
expect(
|
||||
resolvePuzzlePhaseFromSessionProgress(
|
||||
state,
|
||||
buildPuzzleSession({ progressPercent: 96 }),
|
||||
),
|
||||
).toBe('puzzle-select-image');
|
||||
expect(
|
||||
resolvePuzzlePhaseFromSessionProgress(
|
||||
state,
|
||||
buildPuzzleSession({ progressPercent: 94 }),
|
||||
),
|
||||
).toBe('puzzle-ui-assets');
|
||||
expect(
|
||||
resolvePuzzlePhaseFromSessionProgress(
|
||||
buildState({ metadata: { puzzleAiRedraw: false } }),
|
||||
buildPuzzleSession({ progressPercent: 88 }),
|
||||
),
|
||||
).toBe('puzzle-level-scene');
|
||||
expect(
|
||||
resolvePuzzlePhaseFromSessionProgress(
|
||||
state,
|
||||
buildPuzzleSession({ progressPercent: 88 }),
|
||||
),
|
||||
).toBe('puzzle-cover-image');
|
||||
expect(
|
||||
resolvePuzzlePhaseFromSessionProgress(
|
||||
state,
|
||||
buildPuzzleSession({ progressPercent: 20 }),
|
||||
),
|
||||
).toBe('compile');
|
||||
});
|
||||
|
||||
test('merges compiled puzzle session progress into generation state', () => {
|
||||
expect(
|
||||
mergePuzzleSessionProgressIntoGenerationState(
|
||||
buildState({
|
||||
metadata: {
|
||||
puzzleAiRedraw: false,
|
||||
puzzleActivePhaseId: 'compile',
|
||||
puzzleActiveStepStartedAtMs: 200,
|
||||
puzzleProgressPercent: 20,
|
||||
},
|
||||
}),
|
||||
buildPuzzleSession({ progressPercent: 90 }),
|
||||
),
|
||||
).toMatchObject({
|
||||
metadata: {
|
||||
puzzleAiRedraw: false,
|
||||
puzzleActivePhaseId: 'puzzle-level-scene',
|
||||
puzzleActiveStepStartedAtMs: SESSION_UPDATED_AT_MS,
|
||||
puzzleProgressPercent: 90,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
mergePuzzleSessionProgressIntoGenerationState(
|
||||
buildState(),
|
||||
buildPuzzleSession({
|
||||
draft: {
|
||||
...buildPuzzleSession().draft!,
|
||||
formDraft: {
|
||||
pictureDescription: '星桥',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).metadata,
|
||||
).toMatchObject({
|
||||
puzzleActivePhaseId: 'compile',
|
||||
puzzleActiveStepStartedAtMs: 200,
|
||||
puzzleProgressPercent: 20,
|
||||
});
|
||||
});
|
||||
|
||||
test('finishes generation state and resolves ready/generating flags', () => {
|
||||
const failedState = resolveFinishedMiniGameDraftGenerationState(
|
||||
buildState({ error: '旧错误' }),
|
||||
'failed',
|
||||
{
|
||||
completedAssetCount: 1,
|
||||
totalAssetCount: 2,
|
||||
},
|
||||
);
|
||||
|
||||
expect(failedState).toMatchObject({
|
||||
phase: 'failed',
|
||||
finishedAtMs: NOW,
|
||||
error: '旧错误',
|
||||
completedAssetCount: 1,
|
||||
totalAssetCount: 2,
|
||||
});
|
||||
expect(isMiniGameDraftReady(failedState)).toBe(false);
|
||||
expect(isMiniGameDraftGenerating(failedState)).toBe(false);
|
||||
expect(isMiniGameDraftReady({ ...failedState, phase: 'ready' })).toBe(true);
|
||||
expect(isMiniGameDraftGenerating(buildState())).toBe(true);
|
||||
expect(isMiniGameDraftGenerating(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,167 @@
|
||||
import type {
|
||||
CreatePuzzleAgentSessionRequest,
|
||||
PuzzleAgentSessionSnapshot,
|
||||
} from '../../../packages/shared/src/contracts/puzzleAgentSession';
|
||||
import {
|
||||
createMiniGameDraftGenerationState,
|
||||
type MiniGameDraftGenerationKind,
|
||||
type MiniGameDraftGenerationPhase,
|
||||
type MiniGameDraftGenerationState,
|
||||
resolveMiniGameDraftGenerationStartedAtMs,
|
||||
} from '../../services/miniGameDraftGenerationProgress';
|
||||
|
||||
export function createMiniGameDraftGenerationStateForRestoredDraft(
|
||||
kind: MiniGameDraftGenerationKind,
|
||||
metadata?: MiniGameDraftGenerationState['metadata'],
|
||||
startedAtMs = Date.now(),
|
||||
): MiniGameDraftGenerationState {
|
||||
return {
|
||||
...createMiniGameDraftGenerationState(kind, startedAtMs),
|
||||
...(metadata ? { metadata } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function createFailedMiniGameDraftGenerationStateForRestoredDraft(
|
||||
kind: MiniGameDraftGenerationKind,
|
||||
updatedAt: string | null | undefined,
|
||||
error: string,
|
||||
metadata?: MiniGameDraftGenerationState['metadata'],
|
||||
): MiniGameDraftGenerationState {
|
||||
return resolveFinishedMiniGameDraftGenerationState(
|
||||
createMiniGameDraftGenerationStateForRestoredDraft(
|
||||
kind,
|
||||
metadata,
|
||||
resolveMiniGameDraftGenerationStartedAtMs(updatedAt),
|
||||
),
|
||||
'failed',
|
||||
{ error },
|
||||
);
|
||||
}
|
||||
|
||||
/** 清理生成态完成时间,避免返回生成页后继续沿用结束态计时。 */
|
||||
export function rebaseMiniGameDraftGenerationStateForDisplay(
|
||||
state: MiniGameDraftGenerationState,
|
||||
): MiniGameDraftGenerationState {
|
||||
return {
|
||||
...state,
|
||||
finishedAtMs: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function rebaseMiniGameDraftBackgroundCompileTaskForDisplay<
|
||||
T extends { generationState: MiniGameDraftGenerationState },
|
||||
>(task: T): T {
|
||||
return {
|
||||
...task,
|
||||
generationState: rebaseMiniGameDraftGenerationStateForDisplay(
|
||||
task.generationState,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function createPuzzleDraftGenerationStateFromPayload(
|
||||
payload: CreatePuzzleAgentSessionRequest | null | undefined,
|
||||
session: PuzzleAgentSessionSnapshot | null | undefined = null,
|
||||
): MiniGameDraftGenerationState {
|
||||
const puzzleProgressPercent =
|
||||
session?.draft && !session.draft.formDraft
|
||||
? session.progressPercent
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...createMiniGameDraftGenerationState(
|
||||
'puzzle',
|
||||
resolveMiniGameDraftGenerationStartedAtMs(session?.updatedAt),
|
||||
),
|
||||
metadata: {
|
||||
puzzleAiRedraw: payload?.aiRedraw ?? true,
|
||||
puzzleActivePhaseId:
|
||||
typeof puzzleProgressPercent === 'number' ? 'compile' : undefined,
|
||||
puzzleActiveStepStartedAtMs:
|
||||
typeof puzzleProgressPercent === 'number' ? Date.now() : undefined,
|
||||
puzzleProgressPercent,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function resolvePuzzlePhaseFromSessionProgress(
|
||||
state: MiniGameDraftGenerationState,
|
||||
session: PuzzleAgentSessionSnapshot,
|
||||
): MiniGameDraftGenerationPhase {
|
||||
if (session.progressPercent >= 96) {
|
||||
return 'puzzle-select-image';
|
||||
}
|
||||
if (session.progressPercent >= 94) {
|
||||
return 'puzzle-ui-assets';
|
||||
}
|
||||
if (session.progressPercent >= 88) {
|
||||
return state.metadata?.puzzleAiRedraw === false
|
||||
? 'puzzle-level-scene'
|
||||
: 'puzzle-cover-image';
|
||||
}
|
||||
|
||||
return 'compile';
|
||||
}
|
||||
|
||||
export function mergePuzzleSessionProgressIntoGenerationState(
|
||||
state: MiniGameDraftGenerationState,
|
||||
session: PuzzleAgentSessionSnapshot,
|
||||
): MiniGameDraftGenerationState {
|
||||
const isCompiledGenerationSession = Boolean(
|
||||
session.draft && !session.draft.formDraft,
|
||||
);
|
||||
|
||||
const nextPhaseId = isCompiledGenerationSession
|
||||
? resolvePuzzlePhaseFromSessionProgress(state, session)
|
||||
: state.metadata?.puzzleActivePhaseId;
|
||||
const shouldResetActiveStepStart =
|
||||
isCompiledGenerationSession &&
|
||||
nextPhaseId != null &&
|
||||
nextPhaseId !== state.metadata?.puzzleActivePhaseId;
|
||||
|
||||
return {
|
||||
...state,
|
||||
metadata: {
|
||||
...state.metadata,
|
||||
puzzleActivePhaseId: nextPhaseId,
|
||||
puzzleActiveStepStartedAtMs: shouldResetActiveStepStart
|
||||
? resolveMiniGameDraftGenerationStartedAtMs(session.updatedAt)
|
||||
: state.metadata?.puzzleActiveStepStartedAtMs,
|
||||
puzzleProgressPercent: isCompiledGenerationSession
|
||||
? session.progressPercent
|
||||
: state.metadata?.puzzleProgressPercent,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export 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,
|
||||
};
|
||||
}
|
||||
|
||||
export function isMiniGameDraftReady(
|
||||
state: MiniGameDraftGenerationState | null,
|
||||
) {
|
||||
return state?.phase === 'ready';
|
||||
}
|
||||
|
||||
export function isMiniGameDraftGenerating(
|
||||
state: MiniGameDraftGenerationState | null,
|
||||
) {
|
||||
return Boolean(state && state.phase !== 'ready' && state.phase !== 'failed');
|
||||
}
|
||||
Reference in New Issue
Block a user