This commit is contained in:
2026-05-11 16:15:48 +08:00
parent 0c9254502c
commit e30b733b17
87 changed files with 3527 additions and 1261 deletions

View File

@@ -19,6 +19,7 @@ type CreationAgentClientOptions = {
apiBase: string;
messages: CreationAgentClientMessages;
createSessionTimeoutMs?: number;
executeActionTimeoutMs?: number;
readRetry?: ApiRetryOptions;
writeRetry?: ApiRetryOptions;
};
@@ -84,6 +85,7 @@ export function createCreationAgentClient<
apiBase,
messages,
createSessionTimeoutMs = 15000,
executeActionTimeoutMs,
readRetry = DEFAULT_CREATION_AGENT_READ_RETRY,
writeRetry = DEFAULT_CREATION_AGENT_WRITE_RETRY,
}: CreationAgentClientOptions) {
@@ -152,6 +154,7 @@ export function createCreationAgentClient<
messages.executeAction,
{
retry: writeRetry,
timeoutMs: executeActionTimeoutMs,
},
);

View File

@@ -10,6 +10,7 @@ import type { TextStreamOptions } from '../aiTypes';
import { createCreationAgentClient } from '../creation-agent';
const MATCH3D_AGENT_API_BASE = '/api/creation/match3d/sessions';
const MATCH3D_EXECUTE_ACTION_TIMEOUT_MS = 20 * 60 * 1000;
const match3dAgentHttpClient = createCreationAgentClient<
CreateMatch3DSessionRequest,
@@ -29,6 +30,7 @@ const match3dAgentHttpClient = createCreationAgentClient<
streamIncomplete: '抓大鹅共创消息流式结果不完整',
executeAction: '执行抓大鹅共创操作失败',
},
executeActionTimeoutMs: MATCH3D_EXECUTE_ACTION_TIMEOUT_MS,
});
/**

View File

@@ -1,5 +1,6 @@
export {
deleteMatch3DWork,
generateMatch3DWorkTags,
getMatch3DWorkDetail,
listMatch3DGallery,
listMatch3DWorks,

View File

@@ -1,4 +1,6 @@
import type {
GenerateMatch3DWorkTagsRequest,
GenerateMatch3DWorkTagsResponse,
Match3DWorkDetailResponse,
Match3DWorkMutationResponse,
Match3DWorksResponse,
@@ -79,6 +81,22 @@ export function updateMatch3DWork(
);
}
/**
* 根据当前作品名称与题材生成发布标签。
*/
export function generateMatch3DWorkTags(payload: GenerateMatch3DWorkTagsRequest) {
return requestJson<GenerateMatch3DWorkTagsResponse>(
`${MATCH3D_WORKS_API_BASE}/tags`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
},
'生成抓大鹅作品标签失败',
{ retry: MATCH3D_WORKS_WRITE_RETRY },
);
}
/**
* 发布抓大鹅作品。发布门槛由后端最终确认。
*/
@@ -105,6 +123,7 @@ export function deleteMatch3DWork(profileId: string) {
export const match3dWorksClient = {
delete: deleteMatch3DWork,
generateTags: generateMatch3DWorkTags,
getDetail: getMatch3DWorkDetail,
listGallery: listMatch3DGallery,
list: listMatch3DWorks,

View File

@@ -161,14 +161,29 @@ describe('miniGameDraftGenerationProgress', () => {
);
expect(progress?.steps.map((step) => step.id)).toEqual([
'match3d-work-title',
'match3d-item-names',
'match3d-material-sheet',
'match3d-slice-images',
'match3d-upload-images',
'match3d-generate-models',
]);
expect(progress?.phaseId).toBe('match3d-material-sheet');
expect(progress?.phaseLabel).toBe('生成素材图');
expect(progress?.estimatedRemainingMs).toBe(103_000);
expect(progress?.estimatedRemainingMs).toBe(583_000);
});
test('match3d draft generation starts from title generation', () => {
const state = createMiniGameDraftGenerationState('match3d');
const progress = buildMiniGameDraftGenerationProgress(
state,
state.startedAtMs + 1_000,
);
expect(progress?.phaseId).toBe('match3d-work-title');
expect(progress?.phaseLabel).toBe('生成游戏名称');
expect(progress?.steps[0]?.detail).toBe('根据题材设定生成作品名称与标签。');
});
test('match3d generation anchors show theme and fixed three items', () => {

View File

@@ -30,10 +30,12 @@ export type MiniGameDraftGenerationPhase =
| 'square-hole-cover'
| 'square-hole-shapes'
| 'square-hole-ready'
| 'match3d-work-title'
| 'match3d-item-names'
| 'match3d-material-sheet'
| 'match3d-slice-images'
| 'match3d-upload-images'
| 'match3d-generate-models'
| 'match3d-ready'
| 'puzzle-images'
| 'puzzle-select-image'
@@ -140,29 +142,41 @@ const SQUARE_HOLE_STEPS = [
] as const satisfies ReadonlyArray<MiniGameStepDefinition>;
const MATCH3D_STEPS = [
{
id: 'match3d-work-title',
label: '生成游戏名称',
detail: '根据题材设定生成作品名称与标签。',
weight: 8,
},
{
id: 'match3d-item-names',
label: '生成物品名称',
detail: '根据题材生成本局的 3 个物品名称。',
weight: 16,
weight: 8,
},
{
id: 'match3d-material-sheet',
label: '生成素材图',
detail: '生成一张 1:1 的网格素材图。',
weight: 30,
weight: 18,
},
{
id: 'match3d-slice-images',
label: '切割独立图片',
detail: '把素材图切成独立物品参考图。',
weight: 14,
weight: 8,
},
{
id: 'match3d-upload-images',
label: '上传图片资产',
detail: '写入切割图片并准备进入草稿页。',
weight: 40,
detail: '写入素材图和独立物品参考图。',
weight: 8,
},
{
id: 'match3d-generate-models',
label: '生成3D模型',
detail: '调用 Hyper3D Rodin 生成 GLB 模型并转存。',
weight: 50,
},
] as const satisfies ReadonlyArray<MiniGameStepDefinition>;
@@ -234,7 +248,7 @@ export function createMiniGameDraftGenerationState(
: kind === 'square-hole'
? 'square-hole-draft'
: kind === 'match3d'
? 'match3d-item-names'
? 'match3d-work-title'
: 'compile',
startedAtMs: Date.now(),
completedAssetCount: 0,
@@ -270,6 +284,9 @@ function resolveSquareHolePhaseByElapsedMs(
function resolveMatch3DPhaseByElapsedMs(
elapsedMs: number,
): MiniGameDraftGenerationPhase {
if (elapsedMs >= 92_000) {
return 'match3d-generate-models';
}
if (elapsedMs >= 72_000) {
return 'match3d-upload-images';
}
@@ -279,7 +296,10 @@ function resolveMatch3DPhaseByElapsedMs(
if (elapsedMs >= 16_000) {
return 'match3d-material-sheet';
}
return 'match3d-item-names';
if (elapsedMs >= 4_000) {
return 'match3d-item-names';
}
return 'match3d-work-title';
}
function resolvePuzzleTimelineByElapsedMs(elapsedMs: number) {
@@ -422,7 +442,7 @@ export function buildMiniGameDraftGenerationProgress(
: normalizedState.kind === 'square-hole'
? Math.max(0, 12_000 - elapsedMs)
: normalizedState.kind === 'match3d'
? Math.max(0, 120_000 - elapsedMs)
? Math.max(0, 10 * 60_000 - elapsedMs)
: null,
activeStepIndex,
steps: buildMiniGameProgressSteps(

View File

@@ -395,6 +395,7 @@ describe('puzzleLocalRuntime', () => {
rank: 1,
nickname: '本地玩家',
elapsedMs: clearedRun.currentLevel?.elapsedMs ?? 0,
visibleTags: [],
isCurrentPlayer: true,
},
]);