refactor: 收口小玩法生成 action payload

This commit is contained in:
2026-06-04 05:41:44 +08:00
parent 991efb2eed
commit b037ce1e32
9 changed files with 344 additions and 96 deletions

View File

@@ -525,6 +525,7 @@ import {
resolveFinishedMiniGameDraftGenerationState,
} from './platformMiniGameDraftGenerationStateModel';
import {
buildJumpHopDraftActionPayload,
buildMatch3DFormPayloadFromSession,
buildMatch3DFormPayloadFromWork,
buildPendingMatch3DDraftMetadata,
@@ -534,6 +535,7 @@ import {
buildPuzzleFormPayloadFromSession,
buildPuzzleFormPayloadFromWork,
buildPuzzleWorkUpdatePayloadFromDraft,
buildWoodenFishDraftActionPayload,
isEmptyPuzzleFormOnlyDraft,
isPuzzleFormOnlyDraft,
} from './platformMiniGameDraftPayloadModel';
@@ -542,6 +544,7 @@ import {
buildPuzzleRuntimeWorkFromSession,
buildSquareHoleProfileFromSession,
buildVisualNovelSessionFromWorkDetail,
buildWoodenFishGeneratingWorkSummary,
buildWoodenFishPendingSession,
buildWoodenFishSessionFromWorkDetail,
} from './platformMiniGameSessionMappingModel';
@@ -6828,25 +6831,10 @@ export function PlatformEntryFlowShellImpl({
try {
const response = await jumpHopClient.executeAction(
created.session.sessionId,
{
actionType: 'compile-draft',
workTitle: payload?.workTitle ?? created.session.draft?.workTitle,
workDescription:
payload?.workDescription ??
created.session.draft?.workDescription,
themeTags: payload?.themeTags ?? created.session.draft?.themeTags,
difficulty:
payload?.difficulty ?? created.session.draft?.difficulty,
stylePreset:
payload?.stylePreset ?? created.session.draft?.stylePreset,
characterPrompt:
payload?.characterPrompt ??
created.session.draft?.characterPrompt,
tilePrompt:
payload?.tilePrompt ?? created.session.draft?.tilePrompt,
endMoodPrompt:
payload?.endMoodPrompt ?? created.session.draft?.endMoodPrompt,
},
buildJumpHopDraftActionPayload('compile-draft', {
payload,
draft: created.session.draft,
}),
);
const readyState = createReadyJumpHopGenerationState(generationState);
setJumpHopSession(response.session);
@@ -6958,17 +6946,9 @@ export function PlatformEntryFlowShellImpl({
try {
const response = await jumpHopClient.executeAction(
jumpHopSession.sessionId,
{
actionType,
workTitle: jumpHopSession.draft?.workTitle,
workDescription: jumpHopSession.draft?.workDescription,
themeTags: jumpHopSession.draft?.themeTags,
difficulty: jumpHopSession.draft?.difficulty,
stylePreset: jumpHopSession.draft?.stylePreset,
characterPrompt: jumpHopSession.draft?.characterPrompt,
tilePrompt: jumpHopSession.draft?.tilePrompt,
endMoodPrompt: jumpHopSession.draft?.endMoodPrompt,
},
buildJumpHopDraftActionPayload(actionType, {
draft: jumpHopSession.draft,
}),
);
setJumpHopSession(response.session);
setJumpHopWork(response.work ?? jumpHopWork);
@@ -7190,30 +7170,8 @@ export function PlatformEntryFlowShellImpl({
setSelectionStage('wooden-fish-generating');
markDraftGenerating('wooden-fish', [created.session.sessionId]);
markPendingDraftGenerating('wooden-fish', created.session.sessionId);
const createdAt = created.session.updatedAt ?? created.session.createdAt;
setWoodenFishWorks((current) => [
{
runtimeKind: 'wooden-fish',
workId: created.session.sessionId,
profileId: created.session.sessionId,
ownerUserId: created.session.ownerUserId,
sourceSessionId: created.session.sessionId,
workTitle:
payload?.workTitle ?? created.session.draft?.workTitle ?? '敲木鱼',
workDescription:
payload?.workDescription ??
created.session.draft?.workDescription ??
'',
themeTags: payload?.themeTags ??
created.session.draft?.themeTags ?? ['敲木鱼'],
coverImageSrc: created.session.draft?.coverImageSrc ?? null,
publicationStatus: 'draft',
playCount: 0,
updatedAt: createdAt,
publishedAt: null,
publishReady: false,
generationStatus: 'generating',
},
buildWoodenFishGeneratingWorkSummary(created.session, payload),
...current.filter(
(item) => item.sourceSessionId !== created.session.sessionId,
),
@@ -7222,24 +7180,10 @@ export function PlatformEntryFlowShellImpl({
try {
const response = await woodenFishClient.executeAction(
created.session.sessionId,
{
actionType: 'compile-draft',
workTitle: payload?.workTitle ?? created.session.draft?.workTitle,
workDescription:
payload?.workDescription ??
created.session.draft?.workDescription,
themeTags: payload?.themeTags ?? created.session.draft?.themeTags,
hitObjectPrompt:
payload?.hitObjectPrompt ??
created.session.draft?.hitObjectPrompt,
hitObjectReferenceImageSrc:
payload?.hitObjectReferenceImageSrc ??
created.session.draft?.hitObjectReferenceImageSrc,
hitSoundAsset:
payload?.hitSoundAsset ?? created.session.draft?.hitSoundAsset,
floatingWords:
payload?.floatingWords ?? created.session.draft?.floatingWords,
},
buildWoodenFishDraftActionPayload('compile-draft', {
payload,
draft: created.session.draft,
}),
);
setWoodenFishSession(response.session);
setWoodenFishWork(response.work ?? null);
@@ -7375,17 +7319,9 @@ export function PlatformEntryFlowShellImpl({
try {
const response = await woodenFishClient.executeAction(
woodenFishSession.sessionId,
{
actionType,
workTitle: woodenFishSession.draft?.workTitle,
workDescription: woodenFishSession.draft?.workDescription,
themeTags: woodenFishSession.draft?.themeTags,
hitObjectPrompt: woodenFishSession.draft?.hitObjectPrompt,
hitObjectReferenceImageSrc:
woodenFishSession.draft?.hitObjectReferenceImageSrc,
hitSoundAsset: woodenFishSession.draft?.hitSoundAsset,
floatingWords: woodenFishSession.draft?.floatingWords,
},
buildWoodenFishDraftActionPayload(actionType, {
draft: woodenFishSession.draft,
}),
);
setWoodenFishSession(response.session);
setWoodenFishWork(response.work ?? woodenFishWork);

View File

@@ -1,5 +1,9 @@
import { describe, expect, test } from 'vitest';
import type {
JumpHopSessionSnapshotResponse,
JumpHopWorkspaceCreateRequest,
} from '../../../packages/shared/src/contracts/jumpHop';
import type {
Match3DAgentSessionSnapshot,
Match3DAnchorPackResponse,
@@ -15,7 +19,12 @@ import type {
PuzzleAgentSessionSnapshot,
} from '../../../packages/shared/src/contracts/puzzleAgentSession';
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
import type {
WoodenFishSessionSnapshotResponse,
WoodenFishWorkspaceCreateRequest,
} from '../../../packages/shared/src/contracts/woodenFish';
import {
buildJumpHopDraftActionPayload,
buildMatch3DFormPayloadFromSession,
buildMatch3DFormPayloadFromWork,
buildPendingMatch3DDraftMetadata,
@@ -25,6 +34,7 @@ import {
buildPuzzleFormPayloadFromSession,
buildPuzzleFormPayloadFromWork,
buildPuzzleWorkUpdatePayloadFromDraft,
buildWoodenFishDraftActionPayload,
isEmptyPuzzleFormOnlyDraft,
isPuzzleFormOnlyDraft,
} from './platformMiniGameDraftPayloadModel';
@@ -196,6 +206,91 @@ function buildMatch3DWork(
};
}
function buildJumpHopDraft(
overrides: Partial<NonNullable<JumpHopSessionSnapshotResponse['draft']>> = {},
): NonNullable<JumpHopSessionSnapshotResponse['draft']> {
return {
templateId: 'jump-hop',
templateName: '跳一跳',
profileId: 'jump-hop-profile-1',
workTitle: '草稿跳一跳',
workDescription: '从草稿恢复。',
themeTags: ['草稿'],
difficulty: 'standard',
stylePreset: 'paper-toy',
characterPrompt: '草稿角色',
tilePrompt: '草稿平台',
endMoodPrompt: '草稿终点',
characterAsset: null,
tileAtlasAsset: null,
tileAssets: [],
path: null,
coverComposite: null,
generationStatus: 'draft',
...overrides,
};
}
function buildJumpHopPayload(
overrides: Partial<JumpHopWorkspaceCreateRequest> = {},
): JumpHopWorkspaceCreateRequest {
return {
templateId: 'jump-hop',
workTitle: '表单跳一跳',
workDescription: '从表单提交。',
themeTags: ['表单'],
difficulty: 'advanced',
stylePreset: 'neon-glass',
characterPrompt: '表单角色',
tilePrompt: '表单平台',
endMoodPrompt: '表单终点',
...overrides,
};
}
function buildWoodenFishDraft(
overrides: Partial<
NonNullable<WoodenFishSessionSnapshotResponse['draft']>
> = {},
): NonNullable<WoodenFishSessionSnapshotResponse['draft']> {
return {
templateId: 'wooden-fish',
templateName: '敲木鱼',
profileId: 'wooden-fish-profile-1',
workTitle: '草稿木鱼',
workDescription: '从草稿恢复。',
themeTags: ['草稿'],
hitObjectPrompt: '草稿敲击物',
hitObjectReferenceImageSrc: '/draft-hit-ref.png',
hitSoundPrompt: null,
floatingWords: ['草稿 +1'],
hitObjectAsset: null,
backgroundAsset: null,
backButtonAsset: null,
hitSoundAsset: null,
coverImageSrc: null,
generationStatus: 'draft',
...overrides,
};
}
function buildWoodenFishPayload(
overrides: Partial<WoodenFishWorkspaceCreateRequest> = {},
): WoodenFishWorkspaceCreateRequest {
return {
templateId: 'wooden-fish',
workTitle: '表单木鱼',
workDescription: '从表单提交。',
themeTags: ['表单'],
hitObjectPrompt: '表单敲击物',
hitObjectReferenceImageSrc: '/form-hit-ref.png',
hitSoundPrompt: null,
hitSoundAsset: null,
floatingWords: ['表单 +1'],
...overrides,
};
}
describe('platformMiniGameDraftPayloadModel', () => {
test('builds puzzle form payload from work with fallback description priority', () => {
expect(
@@ -242,6 +337,64 @@ describe('platformMiniGameDraftPayloadModel', () => {
).toEqual([]);
});
test('builds jump hop draft action payload from payload or draft', () => {
expect(
buildJumpHopDraftActionPayload('compile-draft', {
payload: buildJumpHopPayload(),
draft: buildJumpHopDraft(),
}),
).toEqual({
actionType: 'compile-draft',
workTitle: '表单跳一跳',
workDescription: '从表单提交。',
themeTags: ['表单'],
difficulty: 'advanced',
stylePreset: 'neon-glass',
characterPrompt: '表单角色',
tilePrompt: '表单平台',
endMoodPrompt: '表单终点',
});
expect(
buildJumpHopDraftActionPayload('regenerate-tiles', {
draft: buildJumpHopDraft(),
}),
).toMatchObject({
actionType: 'regenerate-tiles',
workTitle: '草稿跳一跳',
tilePrompt: '草稿平台',
});
});
test('builds wooden fish draft action payload from payload or draft', () => {
expect(
buildWoodenFishDraftActionPayload('compile-draft', {
payload: buildWoodenFishPayload(),
draft: buildWoodenFishDraft(),
}),
).toEqual({
actionType: 'compile-draft',
workTitle: '表单木鱼',
workDescription: '从表单提交。',
themeTags: ['表单'],
hitObjectPrompt: '表单敲击物',
hitObjectReferenceImageSrc: '/form-hit-ref.png',
hitSoundAsset: null,
floatingWords: ['表单 +1'],
});
expect(
buildWoodenFishDraftActionPayload('regenerate-hit-object', {
draft: buildWoodenFishDraft(),
}),
).toMatchObject({
actionType: 'regenerate-hit-object',
workTitle: '草稿木鱼',
hitObjectPrompt: '草稿敲击物',
floatingWords: ['草稿 +1'],
});
});
test('builds puzzle form payload from session form draft and fallbacks', () => {
expect(buildPuzzleFormPayloadFromSession(buildPuzzleSession())).toEqual({
seedText: '表单画面',

View File

@@ -1,3 +1,8 @@
import type {
JumpHopActionRequest,
JumpHopSessionSnapshotResponse,
JumpHopWorkspaceCreateRequest,
} from '../../../packages/shared/src/contracts/jumpHop';
import type {
CreateMatch3DSessionRequest,
Match3DAgentSessionSnapshot,
@@ -13,6 +18,11 @@ import type {
PuzzleAgentSessionSnapshot,
} from '../../../packages/shared/src/contracts/puzzleAgentSession';
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
import type {
WoodenFishActionRequest,
WoodenFishSessionSnapshotResponse,
WoodenFishWorkspaceCreateRequest,
} from '../../../packages/shared/src/contracts/woodenFish';
export type PuzzleWorkUpdatePayload = {
workTitle?: string;
@@ -65,6 +75,49 @@ export function buildPuzzleWorkUpdatePayloadFromDraft(
};
}
export function buildJumpHopDraftActionPayload(
actionType: 'compile-draft' | 'regenerate-character' | 'regenerate-tiles',
input: {
payload?: JumpHopWorkspaceCreateRequest | null;
draft?: JumpHopSessionSnapshotResponse['draft'] | null;
},
): JumpHopActionRequest {
const { payload, draft } = input;
return {
actionType,
workTitle: payload?.workTitle ?? draft?.workTitle,
workDescription: payload?.workDescription ?? draft?.workDescription,
themeTags: payload?.themeTags ?? draft?.themeTags,
difficulty: payload?.difficulty ?? draft?.difficulty,
stylePreset: payload?.stylePreset ?? draft?.stylePreset,
characterPrompt: payload?.characterPrompt ?? draft?.characterPrompt,
tilePrompt: payload?.tilePrompt ?? draft?.tilePrompt,
endMoodPrompt: payload?.endMoodPrompt ?? draft?.endMoodPrompt,
};
}
export function buildWoodenFishDraftActionPayload(
actionType: 'compile-draft' | 'regenerate-hit-object',
input: {
payload?: WoodenFishWorkspaceCreateRequest | null;
draft?: WoodenFishSessionSnapshotResponse['draft'] | null;
},
): WoodenFishActionRequest {
const { payload, draft } = input;
return {
actionType,
workTitle: payload?.workTitle ?? draft?.workTitle,
workDescription: payload?.workDescription ?? draft?.workDescription,
themeTags: payload?.themeTags ?? draft?.themeTags,
hitObjectPrompt: payload?.hitObjectPrompt ?? draft?.hitObjectPrompt,
hitObjectReferenceImageSrc:
payload?.hitObjectReferenceImageSrc ??
draft?.hitObjectReferenceImageSrc,
hitSoundAsset: payload?.hitSoundAsset ?? draft?.hitSoundAsset,
floatingWords: payload?.floatingWords ?? draft?.floatingWords,
};
}
function parseOptionalFiniteNumber(value: string | number | null | undefined) {
if (typeof value === 'number') {
return Number.isFinite(value) ? value : undefined;

View File

@@ -19,7 +19,9 @@ import type {
import type {
WoodenFishAudioAsset,
WoodenFishImageAsset,
WoodenFishSessionSnapshotResponse,
WoodenFishWorkProfileResponse,
WoodenFishWorkspaceCreateRequest,
WoodenFishWorkSummaryResponse,
} from '../../../packages/shared/src/contracts/woodenFish';
import {
@@ -27,6 +29,7 @@ import {
buildPuzzleRuntimeWorkFromSession,
buildSquareHoleProfileFromSession,
buildVisualNovelSessionFromWorkDetail,
buildWoodenFishGeneratingWorkSummary,
buildWoodenFishPendingSession,
buildWoodenFishSessionFromWorkDetail,
} from './platformMiniGameSessionMappingModel';
@@ -423,6 +426,37 @@ function buildWoodenFishWorkProfile(
};
}
function buildWoodenFishSession(
overrides: Partial<WoodenFishSessionSnapshotResponse> = {},
): WoodenFishSessionSnapshotResponse {
const summary = buildWoodenFishSummary();
return {
sessionId: 'wooden-fish-session-1',
ownerUserId: 'user-1',
status: 'generating',
draft: buildWoodenFishWorkProfile({ summary }).draft,
createdAt: '2026-06-01T11:59:00.000Z',
updatedAt: '2026-06-01T12:00:00.000Z',
...overrides,
};
}
function buildWoodenFishCreatePayload(
overrides: Partial<WoodenFishWorkspaceCreateRequest> = {},
): WoodenFishWorkspaceCreateRequest {
return {
templateId: 'wooden-fish',
workTitle: '表单星灯木鱼',
workDescription: '表单里敲亮星灯。',
themeTags: ['表单星灯'],
hitObjectPrompt: '星灯',
hitObjectReferenceImageSrc: null,
hitSoundPrompt: null,
floatingWords: ['功德 +1'],
...overrides,
};
}
describe('platformMiniGameSessionMappingModel', () => {
test('builds a draft puzzle runtime work from a session', () => {
expect(
@@ -607,6 +641,47 @@ describe('platformMiniGameSessionMappingModel', () => {
});
});
test('builds wooden fish generating work summary from session and payload', () => {
expect(
buildWoodenFishGeneratingWorkSummary(
buildWoodenFishSession(),
buildWoodenFishCreatePayload(),
),
).toEqual({
runtimeKind: 'wooden-fish',
workId: 'wooden-fish-session-1',
profileId: 'wooden-fish-session-1',
ownerUserId: 'user-1',
sourceSessionId: 'wooden-fish-session-1',
workTitle: '表单星灯木鱼',
workDescription: '表单里敲亮星灯。',
themeTags: ['表单星灯'],
coverImageSrc: '/wooden-fish-cover.png',
publicationStatus: 'draft',
playCount: 0,
updatedAt: '2026-06-01T12:00:00.000Z',
publishedAt: null,
publishReady: false,
generationStatus: 'generating',
});
expect(
buildWoodenFishGeneratingWorkSummary(
buildWoodenFishSession({
draft: null,
createdAt: '2026-06-01T11:59:00.000Z',
}),
null,
),
).toMatchObject({
workTitle: '敲木鱼',
workDescription: '',
themeTags: ['敲木鱼'],
coverImageSrc: null,
updatedAt: '2026-06-01T12:00:00.000Z',
});
});
test('builds wooden fish recovered session with summary, fallback and profile id priority', () => {
expect(
buildWoodenFishSessionFromWorkDetail(

View File

@@ -10,6 +10,7 @@ import type {
import type {
WoodenFishSessionSnapshotResponse,
WoodenFishWorkProfileResponse,
WoodenFishWorkspaceCreateRequest,
WoodenFishWorkSummaryResponse,
} from '../../../packages/shared/src/contracts/woodenFish';
import { normalizeCreationUrlValue } from './platformCreationUrlStateModel';
@@ -159,6 +160,31 @@ export function buildWoodenFishSessionFromWorkDetail(
};
}
export function buildWoodenFishGeneratingWorkSummary(
session: WoodenFishSessionSnapshotResponse,
payload?: WoodenFishWorkspaceCreateRequest | null,
): WoodenFishWorkSummaryResponse {
const updatedAt = session.updatedAt ?? session.createdAt;
return {
runtimeKind: 'wooden-fish',
workId: session.sessionId,
profileId: session.sessionId,
ownerUserId: session.ownerUserId,
sourceSessionId: session.sessionId,
workTitle: payload?.workTitle ?? session.draft?.workTitle ?? '敲木鱼',
workDescription:
payload?.workDescription ?? session.draft?.workDescription ?? '',
themeTags: payload?.themeTags ?? session.draft?.themeTags ?? ['敲木鱼'],
coverImageSrc: session.draft?.coverImageSrc ?? null,
publicationStatus: 'draft',
playCount: 0,
updatedAt,
publishedAt: null,
publishReady: false,
generationStatus: 'generating',
};
}
export function buildWoodenFishPendingSession(
item: WoodenFishWorkSummaryResponse,
): WoodenFishSessionSnapshotResponse {