1
This commit is contained in:
@@ -7,6 +7,46 @@ import {
|
||||
} from './miniGameDraftGenerationProgress';
|
||||
|
||||
describe('miniGameDraftGenerationProgress', () => {
|
||||
test('puzzle draft generation follows picture-only creation steps', () => {
|
||||
const state: MiniGameDraftGenerationState = {
|
||||
kind: 'puzzle',
|
||||
phase: 'compile',
|
||||
startedAtMs: 1000,
|
||||
completedAssetCount: 0,
|
||||
totalAssetCount: 0,
|
||||
error: null,
|
||||
};
|
||||
|
||||
const progress = buildMiniGameDraftGenerationProgress(state, 1500);
|
||||
|
||||
expect(progress?.steps.map((step) => step.label)).toEqual([
|
||||
'编译首关草稿',
|
||||
'生成首关画面',
|
||||
'写入正式草稿',
|
||||
]);
|
||||
expect(progress?.phaseLabel).toBe('编译首关草稿');
|
||||
expect(progress?.steps[0]?.detail).toBe(
|
||||
'根据画面描述生成首关名称和结果页草稿。',
|
||||
);
|
||||
});
|
||||
|
||||
test('puzzle ready copy points to result page work info completion', () => {
|
||||
const state: MiniGameDraftGenerationState = {
|
||||
kind: 'puzzle',
|
||||
phase: 'ready',
|
||||
startedAtMs: 1000,
|
||||
completedAssetCount: 1,
|
||||
totalAssetCount: 1,
|
||||
error: null,
|
||||
};
|
||||
|
||||
const progress = buildMiniGameDraftGenerationProgress(state, 2000);
|
||||
|
||||
expect(progress?.phaseDetail).toBe(
|
||||
'首关草稿与正式图已准备完成,可进入结果页补作品信息。',
|
||||
);
|
||||
});
|
||||
|
||||
test('big fish draft generation exposes multiple draft steps', () => {
|
||||
const state: MiniGameDraftGenerationState = {
|
||||
kind: 'big-fish',
|
||||
@@ -111,24 +151,12 @@ describe('miniGameDraftGenerationProgress', () => {
|
||||
resultPreview: null,
|
||||
updatedAt: '2026-04-29T00:00:00.000Z',
|
||||
}, {
|
||||
seedText: '表单作品名',
|
||||
workTitle: '暖灯猫街',
|
||||
workDescription: '一套雨夜猫街主题拼图。',
|
||||
seedText: '一只猫在雨夜灯牌下回头。',
|
||||
pictureDescription: '一只猫在雨夜灯牌下回头。',
|
||||
referenceImageSrc: null,
|
||||
});
|
||||
|
||||
expect(entries).toEqual([
|
||||
{
|
||||
id: 'puzzle-title',
|
||||
label: '作品名称',
|
||||
value: '暖灯猫街',
|
||||
},
|
||||
{
|
||||
id: 'work-description',
|
||||
label: '作品描述',
|
||||
value: '一套雨夜猫街主题拼图。',
|
||||
},
|
||||
{
|
||||
id: 'picture-description',
|
||||
label: '画面描述',
|
||||
|
||||
@@ -47,20 +47,20 @@ type MiniGameAnchorSource = {
|
||||
const PUZZLE_STEPS = [
|
||||
{
|
||||
id: 'compile',
|
||||
label: '编译拼图草稿',
|
||||
detail: '整理主题、主体、构图与标签。',
|
||||
label: '编译首关草稿',
|
||||
detail: '根据画面描述生成首关名称和结果页草稿。',
|
||||
weight: 34,
|
||||
},
|
||||
{
|
||||
id: 'puzzle-images',
|
||||
label: '生成拼图图片',
|
||||
detail: '根据草稿生成候选图。',
|
||||
label: '生成首关画面',
|
||||
detail: '按画面描述和参考图生成第一张拼图图。',
|
||||
weight: 33,
|
||||
},
|
||||
{
|
||||
id: 'puzzle-select-image',
|
||||
label: '确认正式图片',
|
||||
detail: '选择候选图写入结果页。',
|
||||
label: '写入正式草稿',
|
||||
detail: '把首图设为正式图并同步到结果页。',
|
||||
weight: 33,
|
||||
},
|
||||
] as const satisfies ReadonlyArray<MiniGameStepDefinition>;
|
||||
@@ -211,7 +211,7 @@ export function buildMiniGameDraftGenerationProgress(
|
||||
(normalizedState.phase === 'ready'
|
||||
? normalizedState.kind === 'big-fish'
|
||||
? '玩法草稿已准备完成,可进入结果页继续生成主图、动作和背景。'
|
||||
: '完整草稿与资产已准备完成。'
|
||||
: '首关草稿与正式图已准备完成,可进入结果页补作品信息。'
|
||||
: activeStep.detail),
|
||||
batchLabel: activeStep.label,
|
||||
overallProgress: clampProgress(overallProgress),
|
||||
@@ -238,28 +238,12 @@ export function buildPuzzleGenerationAnchorEntries(
|
||||
}
|
||||
|
||||
const entries: Array<MiniGameAnchorSource | null> = [
|
||||
{
|
||||
key: 'puzzle-title',
|
||||
label: '作品名称',
|
||||
value:
|
||||
formPayload?.workTitle?.trim() ||
|
||||
formPayload?.seedText?.trim() ||
|
||||
session.draft?.workTitle ||
|
||||
session.anchorPack.themePromise.value,
|
||||
},
|
||||
{
|
||||
key: 'work-description',
|
||||
label: '作品描述',
|
||||
value:
|
||||
formPayload?.workDescription?.trim() ||
|
||||
session.draft?.workDescription ||
|
||||
'',
|
||||
},
|
||||
{
|
||||
key: 'picture-description',
|
||||
label: '画面描述',
|
||||
value:
|
||||
formPayload?.pictureDescription?.trim() ||
|
||||
formPayload?.seedText?.trim() ||
|
||||
session.draft?.levels?.[0]?.pictureDescription ||
|
||||
session.anchorPack.visualSubject.value,
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ const { requestJsonMock } = vi.hoisted(() => ({
|
||||
}));
|
||||
|
||||
import {
|
||||
generateRpgWorldOpeningCg,
|
||||
generateRpgWorldLandmark,
|
||||
generateRpgWorldSceneImage,
|
||||
generateRpgWorldSceneNpc,
|
||||
@@ -23,6 +24,11 @@ describe('rpgCreationAssetClient', () => {
|
||||
entity: { id: 'landmark-1', name: '雾港' },
|
||||
imageSrc: '/generated-custom-world-scenes/profile/scene/image.webp',
|
||||
npc: { id: 'npc-1', name: '守灯人' },
|
||||
openingCg: {
|
||||
id: 'opening-cg-1',
|
||||
status: 'ready',
|
||||
videoSrc: '/generated-custom-world-scenes/profile/opening.mp4',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -89,4 +95,24 @@ describe('rpgCreationAssetClient', () => {
|
||||
'生成场景 NPC 失败',
|
||||
);
|
||||
});
|
||||
|
||||
it('posts opening cg generation to the runtime custom world asset route', async () => {
|
||||
const openingCg = await generateRpgWorldOpeningCg({
|
||||
profile: {
|
||||
id: 'profile-1',
|
||||
name: '雾海群岛',
|
||||
} as never,
|
||||
});
|
||||
|
||||
expect(openingCg.videoSrc).toBe(
|
||||
'/generated-custom-world-scenes/profile/opening.mp4',
|
||||
);
|
||||
expect(requestJsonMock).toHaveBeenCalledWith(
|
||||
'/api/runtime/custom-world/opening-cg',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
}),
|
||||
'生成开局 CG 失败',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ASSET_API_PATHS } from '../../editor/shared/editorApiClient';
|
||||
import type {
|
||||
CustomWorldLandmark,
|
||||
CustomWorldNpc,
|
||||
CustomWorldOpeningCgProfile,
|
||||
CustomWorldPlayableNpc,
|
||||
CustomWorldProfile,
|
||||
} from '../../types';
|
||||
@@ -132,6 +133,20 @@ export async function generateRpgWorldLandmark(payload: {
|
||||
return response.entity;
|
||||
}
|
||||
|
||||
export async function generateRpgWorldOpeningCg(payload: {
|
||||
profile: CustomWorldProfile;
|
||||
}) {
|
||||
const response = await requestRpgCreationPostJson<{
|
||||
openingCg: CustomWorldOpeningCgProfile;
|
||||
}>(
|
||||
`${RPG_CREATION_ASSET_API_BASE}/opening-cg`,
|
||||
payload,
|
||||
'生成开局 CG 失败',
|
||||
);
|
||||
|
||||
return response.openingCg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作包 D 把结果页与编辑器依赖的资产请求迁入 RPG 创作域 client,
|
||||
* 保留封面资产服务的既有边界,不把逻辑重新塞回 `aiService.ts`。
|
||||
@@ -143,6 +158,7 @@ export const rpgCreationAssetClient = {
|
||||
generatePlayableNpc: generateRpgWorldPlayableNpc,
|
||||
generateStoryNpc: generateRpgWorldStoryNpc,
|
||||
generateLandmark: generateRpgWorldLandmark,
|
||||
generateOpeningCg: generateRpgWorldOpeningCg,
|
||||
generateCoverImage: generateCustomWorldCoverImage,
|
||||
uploadCoverImage: uploadCustomWorldCoverImage,
|
||||
};
|
||||
|
||||
@@ -65,7 +65,8 @@ function createRuntimeProjection(
|
||||
overrides: RuntimeProjectionOverrides = {},
|
||||
): StoryRuntimeProjectionResponse {
|
||||
const storySession = createStorySession(overrides.storySession);
|
||||
const serverVersion = overrides.serverVersion ?? storySession.version;
|
||||
const serverVersion =
|
||||
overrides.serverVersion ?? storySession.version ?? 1;
|
||||
|
||||
return {
|
||||
storySession,
|
||||
|
||||
Reference in New Issue
Block a user