1
This commit is contained in:
90
src/services/creation-audio/creationAudioGenerationClient.ts
Normal file
90
src/services/creation-audio/creationAudioGenerationClient.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import type {
|
||||
AudioGenerationTaskResponse,
|
||||
CreateBackgroundMusicRequest,
|
||||
CreateSoundEffectRequest,
|
||||
GeneratedAudioAssetResponse,
|
||||
PublishGeneratedAudioAssetRequest,
|
||||
} from '../../../packages/shared/src/contracts/creationAudio';
|
||||
import { type ApiRetryOptions, requestJson } from '../apiClient';
|
||||
|
||||
const CREATION_AUDIO_API_BASE = '/api/creation/audio';
|
||||
|
||||
const CREATION_AUDIO_RETRY: ApiRetryOptions = {
|
||||
maxRetries: 1,
|
||||
baseDelayMs: 500,
|
||||
maxDelayMs: 1200,
|
||||
retryUnsafeMethods: true,
|
||||
};
|
||||
|
||||
export function createBackgroundMusicTask(payload: CreateBackgroundMusicRequest) {
|
||||
return requestJson<AudioGenerationTaskResponse>(
|
||||
`${CREATION_AUDIO_API_BASE}/background-music`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
'提交背景音乐生成失败',
|
||||
{ retry: CREATION_AUDIO_RETRY, timeoutMs: 20000 },
|
||||
);
|
||||
}
|
||||
|
||||
export function publishBackgroundMusicAsset(
|
||||
taskId: string,
|
||||
payload: PublishGeneratedAudioAssetRequest,
|
||||
) {
|
||||
return requestJson<GeneratedAudioAssetResponse>(
|
||||
`${CREATION_AUDIO_API_BASE}/background-music/${encodeURIComponent(taskId)}/asset`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
'生成背景音乐素材失败',
|
||||
{ retry: CREATION_AUDIO_RETRY, timeoutMs: 30000 },
|
||||
);
|
||||
}
|
||||
|
||||
export function createSoundEffectTask(payload: CreateSoundEffectRequest) {
|
||||
return requestJson<AudioGenerationTaskResponse>(
|
||||
`${CREATION_AUDIO_API_BASE}/sound-effect`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
'提交音效生成失败',
|
||||
{ retry: CREATION_AUDIO_RETRY, timeoutMs: 20000 },
|
||||
);
|
||||
}
|
||||
|
||||
export function publishSoundEffectAsset(
|
||||
taskId: string,
|
||||
payload: PublishGeneratedAudioAssetRequest,
|
||||
) {
|
||||
return requestJson<GeneratedAudioAssetResponse>(
|
||||
`${CREATION_AUDIO_API_BASE}/sound-effect/${encodeURIComponent(taskId)}/asset`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
'生成音效素材失败',
|
||||
{ retry: CREATION_AUDIO_RETRY, timeoutMs: 30000 },
|
||||
);
|
||||
}
|
||||
|
||||
export async function waitForGeneratedAudioAsset(
|
||||
taskId: string,
|
||||
publish: () => Promise<GeneratedAudioAssetResponse>,
|
||||
) {
|
||||
let latestAsset: GeneratedAudioAssetResponse | null = null;
|
||||
for (let attempt = 0; attempt < 40; attempt += 1) {
|
||||
latestAsset = await publish();
|
||||
if (latestAsset.audioSrc?.trim()) {
|
||||
return latestAsset;
|
||||
}
|
||||
await new Promise((resolve) => window.setTimeout(resolve, 3000));
|
||||
}
|
||||
throw new Error(latestAsset?.status || `音频生成超时:${taskId}`);
|
||||
}
|
||||
1
src/services/creation-audio/index.ts
Normal file
1
src/services/creation-audio/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './creationAudioGenerationClient';
|
||||
@@ -6,5 +6,7 @@ export {
|
||||
listMatch3DWorks,
|
||||
match3dWorksClient,
|
||||
publishMatch3DWork,
|
||||
updateMatch3DAudioAssets,
|
||||
updateMatch3DGeneratedItemAssets,
|
||||
updateMatch3DWork,
|
||||
} from './match3dWorksClient';
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
Match3DWorkDetailResponse,
|
||||
Match3DWorkMutationResponse,
|
||||
Match3DWorksResponse,
|
||||
PutMatch3DAudioAssetsRequest,
|
||||
PutMatch3DWorkRequest,
|
||||
} from '../../../packages/shared/src/contracts/match3dWorks';
|
||||
import { type ApiRetryOptions, requestJson } from '../apiClient';
|
||||
@@ -81,6 +82,27 @@ export function updateMatch3DWork(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存抓大鹅结果页生成的素材快照。
|
||||
*/
|
||||
export function updateMatch3DGeneratedItemAssets(
|
||||
profileId: string,
|
||||
payload: PutMatch3DAudioAssetsRequest,
|
||||
) {
|
||||
return requestJson<Match3DWorkMutationResponse>(
|
||||
`${MATCH3D_WORKS_API_BASE}/${encodeURIComponent(profileId)}/audio-assets`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
'更新抓大鹅生成素材失败',
|
||||
{ retry: MATCH3D_WORKS_WRITE_RETRY },
|
||||
);
|
||||
}
|
||||
|
||||
export const updateMatch3DAudioAssets = updateMatch3DGeneratedItemAssets;
|
||||
|
||||
/**
|
||||
* 根据当前作品名称与题材生成发布标签。
|
||||
*/
|
||||
@@ -128,5 +150,7 @@ export const match3dWorksClient = {
|
||||
listGallery: listMatch3DGallery,
|
||||
list: listMatch3DWorks,
|
||||
publish: publishMatch3DWork,
|
||||
updateAudioAssets: updateMatch3DAudioAssets,
|
||||
updateGeneratedItemAssets: updateMatch3DGeneratedItemAssets,
|
||||
update: updateMatch3DWork,
|
||||
};
|
||||
|
||||
@@ -186,6 +186,24 @@ describe('miniGameDraftGenerationProgress', () => {
|
||||
expect(progress?.steps[0]?.detail).toBe('根据题材设定生成作品名称与标签。');
|
||||
});
|
||||
|
||||
test('match3d draft generation keeps backend observed model phase', () => {
|
||||
const state = {
|
||||
...createMiniGameDraftGenerationState('match3d'),
|
||||
phase: 'match3d-generate-models' as const,
|
||||
completedAssetCount: 1,
|
||||
totalAssetCount: 3,
|
||||
};
|
||||
|
||||
const progress = buildMiniGameDraftGenerationProgress(
|
||||
state,
|
||||
state.startedAtMs + 20_000,
|
||||
);
|
||||
|
||||
expect(progress?.phaseId).toBe('match3d-generate-models');
|
||||
expect(progress?.steps.at(-1)?.completed).toBe(1);
|
||||
expect(progress?.steps.at(-1)?.total).toBe(3);
|
||||
});
|
||||
|
||||
test('match3d generation anchors show theme and fixed three items', () => {
|
||||
const entries = buildMatch3DGenerationAnchorEntries(null, {
|
||||
themeText: '水果',
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { BigFishSessionSnapshotResponse } from '../../packages/shared/src/contracts/bigFish';
|
||||
import type {
|
||||
CreatePuzzleAgentSessionRequest,
|
||||
PuzzleAgentSessionSnapshot,
|
||||
} from '../../packages/shared/src/contracts/puzzleAgentSession';
|
||||
import type {
|
||||
CreateMatch3DSessionRequest,
|
||||
Match3DAgentSessionSnapshot,
|
||||
} from '../../packages/shared/src/contracts/match3dAgent';
|
||||
import type {
|
||||
CreatePuzzleAgentSessionRequest,
|
||||
PuzzleAgentSessionSnapshot,
|
||||
} from '../../packages/shared/src/contracts/puzzleAgentSession';
|
||||
import type {
|
||||
CustomWorldGenerationProgress,
|
||||
CustomWorldGenerationStep,
|
||||
@@ -180,6 +180,17 @@ const MATCH3D_STEPS = [
|
||||
},
|
||||
] as const satisfies ReadonlyArray<MiniGameStepDefinition>;
|
||||
|
||||
const MATCH3D_PHASE_ORDER: Partial<
|
||||
Record<MiniGameDraftGenerationPhase, number>
|
||||
> = {
|
||||
'match3d-work-title': 0,
|
||||
'match3d-item-names': 1,
|
||||
'match3d-material-sheet': 2,
|
||||
'match3d-slice-images': 3,
|
||||
'match3d-upload-images': 4,
|
||||
'match3d-generate-models': 5,
|
||||
};
|
||||
|
||||
function clampProgress(value: number) {
|
||||
return Math.max(0, Math.min(100, Math.round(value)));
|
||||
}
|
||||
@@ -283,23 +294,23 @@ function resolveSquareHolePhaseByElapsedMs(
|
||||
|
||||
function resolveMatch3DPhaseByElapsedMs(
|
||||
elapsedMs: number,
|
||||
currentPhase: MiniGameDraftGenerationPhase,
|
||||
): MiniGameDraftGenerationPhase {
|
||||
if (elapsedMs >= 92_000) {
|
||||
return 'match3d-generate-models';
|
||||
}
|
||||
if (elapsedMs >= 72_000) {
|
||||
return 'match3d-upload-images';
|
||||
}
|
||||
if (elapsedMs >= 58_000) {
|
||||
return 'match3d-slice-images';
|
||||
}
|
||||
if (elapsedMs >= 16_000) {
|
||||
return 'match3d-material-sheet';
|
||||
}
|
||||
if (elapsedMs >= 4_000) {
|
||||
return 'match3d-item-names';
|
||||
}
|
||||
return 'match3d-work-title';
|
||||
const elapsedPhase =
|
||||
elapsedMs >= 92_000
|
||||
? 'match3d-generate-models'
|
||||
: elapsedMs >= 72_000
|
||||
? 'match3d-upload-images'
|
||||
: elapsedMs >= 58_000
|
||||
? 'match3d-slice-images'
|
||||
: elapsedMs >= 16_000
|
||||
? 'match3d-material-sheet'
|
||||
: elapsedMs >= 4_000
|
||||
? 'match3d-item-names'
|
||||
: 'match3d-work-title';
|
||||
const elapsedOrder = MATCH3D_PHASE_ORDER[elapsedPhase] ?? 0;
|
||||
const currentOrder = MATCH3D_PHASE_ORDER[currentPhase] ?? -1;
|
||||
return currentOrder > elapsedOrder ? currentPhase : elapsedPhase;
|
||||
}
|
||||
|
||||
function resolvePuzzleTimelineByElapsedMs(elapsedMs: number) {
|
||||
@@ -367,7 +378,7 @@ export function buildMiniGameDraftGenerationProgress(
|
||||
state.phase !== 'ready'
|
||||
? {
|
||||
...state,
|
||||
phase: resolveMatch3DPhaseByElapsedMs(elapsedMs),
|
||||
phase: resolveMatch3DPhaseByElapsedMs(elapsedMs, state.phase),
|
||||
}
|
||||
: state;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user