合并主分支

解决合并冲突
This commit is contained in:
2026-08-08 10:47:31 +08:00
140 changed files with 11920 additions and 2046 deletions
@@ -17,6 +17,7 @@ import {
generateEditorCharacterAnimation,
generateEditorIconSpritesheet,
generateEditorImage,
generateEditorScene,
generateEditorSoundEffect,
generateEditorVideo,
listEditorProjects,
@@ -966,6 +967,60 @@ describe('editorProjectClient', () => {
);
});
it('generates editor scenes through the structured endpoint without automatic retries', async () => {
requestJsonMock.mockResolvedValueOnce({
queueState: { jobId: 'scene-job-1', status: 'pending' },
});
await generateEditorScene({
sceneContent: '雨夜中的欧洲小镇街道',
stylePreset: 'anime',
model: 'gemini-3.1-flash-image-preview',
aspectRatio: '16:9',
imageSize: '1K',
referenceImageSrcs: ['editor-resource-reference'],
});
expect(requestJsonMock).toHaveBeenCalledWith(
'/api/editor/scenes/generations',
expect.objectContaining({
method: 'POST',
body: JSON.stringify({
sceneContent: '雨夜中的欧洲小镇街道',
stylePreset: 'anime',
model: 'gemini-3.1-flash-image-preview',
aspectRatio: '16:9',
imageSize: '1K',
referenceImageSrcs: ['editor-resource-reference'],
}),
}),
'生成游戏场景失败',
expect.objectContaining({
timeoutMs: 1_200_000,
retry: {
maxRetries: 0,
retryUnsafeMethods: false,
},
}),
);
});
it('rejects scene reference overflow without sending an HTTP request', async () => {
await expect(
generateEditorScene({
sceneContent: '雨夜中的欧洲小镇街道',
stylePreset: 'anime',
referenceImageSrcs: Array.from(
{ length: 6 },
(_, index) =>
`/generated-images/editor/scene-reference-${index + 1}.png`,
),
}),
).rejects.toThrow('场景参考图最多允许 5 张');
expect(requestJsonMock).not.toHaveBeenCalled();
});
it('rejects inline media before submitting editor generation requests', async () => {
await expect(
generateEditorImage({
@@ -2,6 +2,7 @@ import type {
BackgroundMusicPromptAssistRequest,
BackgroundMusicPromptAssistResponse,
} from '../../../packages/shared/src/contracts/editorAudio';
import type { EditorSceneGenerationRequest } from '../../../packages/shared/src/contracts/editorScene';
import type { ExternalGenerationJobStatusRecord } from '../../../packages/shared/src/contracts/externalGeneration';
import { requestJson } from '../apiClient';
import { EDITOR_GENERATION_REQUEST_RETRY_OPTIONS } from './editorRetryOptions';
@@ -12,6 +13,7 @@ const EDITOR_SHOWCASE_RESOURCE_API = '/api/editor/showcase/resources';
const EDITOR_SHOWCASE_ASSET_API_BASE = '/api/editor/showcase/assets';
const EDITOR_PROJECT_RESOURCE_API_BASE = '/api/editor/project-resources';
const EDITOR_IMAGE_GENERATION_API = '/api/editor/images/generations';
const EDITOR_SCENE_GENERATION_API = '/api/editor/scenes/generations';
const EDITOR_IMAGE_EDIT_API = '/api/editor/images/edits';
const EDITOR_BACKGROUND_REMOVAL_API = '/api/editor/images/background-removals';
const EDITOR_PIXEL_ART_SNAP_API = '/api/editor/images/pixel-art-snaps';
@@ -1036,6 +1038,45 @@ export async function toggleEditorShowcaseAssetLike(
return response.showcaseAsset;
}
export type EditorSceneGenerationInput = EditorSceneGenerationRequest;
export async function generateEditorScene(input: EditorSceneGenerationInput) {
assertStableEditorMediaReferences(input.referenceImageSrcs, '场景参考图');
assertEditorReferenceLimit(
input.referenceImageSrcs,
EDITOR_IMAGE_REFERENCE_LIMIT,
'场景参考图',
);
return requestJson<EditorImageGenerationResponse>(
EDITOR_SCENE_GENERATION_API,
jsonRequest('POST', {
sceneContent: input.sceneContent,
stylePreset: input.stylePreset,
...(input.customStyle ? { customStyle: input.customStyle } : {}),
...(input.model ? { model: input.model } : {}),
...(input.aspectRatio ? { aspectRatio: input.aspectRatio } : {}),
...(input.imageSize ? { imageSize: input.imageSize } : {}),
...(input.referenceImageSrcs?.length
? { referenceImageSrcs: input.referenceImageSrcs }
: {}),
...(input.projectId ? { projectId: input.projectId } : {}),
...(input.generationInputs
? { generationInputs: input.generationInputs }
: {}),
...(input.assetFolderId ? { assetFolderId: input.assetFolderId } : {}),
...(input.assetLabel ? { assetLabel: input.assetLabel } : {}),
...(input.canvasCompletion
? { canvasCompletion: input.canvasCompletion }
: {}),
}),
'生成游戏场景失败',
{
timeoutMs: 1_200_000,
retry: EDITOR_GENERATION_REQUEST_RETRY_OPTIONS,
},
);
}
export async function generateEditorImage(input: EditorImageGenerationInput) {
assertStableEditorMediaReferences(input.referenceImageSrcs, '生成参考图');
const referenceLimit =