收紧编辑器媒体引用契约

禁止当前画布与外部编辑器接口接收 Data URL 和 Blob URL
生成请求统一先上传 OSS 并使用稳定资源引用
角色动画和视频签名前校验资源归属并支持资源与素材 ID
同步前后端 256KB 引用限制及相关文档测试
This commit is contained in:
2026-07-17 16:44:01 +08:00
parent 93b27a2307
commit 18af49c27f
18 changed files with 697 additions and 393 deletions
@@ -1,5 +1,5 @@
import { requestJson } from '../apiClient';
import type { ExternalGenerationJobStatusRecord } from '../../../packages/shared/src/contracts/externalGeneration';
import { requestJson } from '../apiClient';
import { EDITOR_REQUEST_RETRY_OPTIONS } from './editorRetryOptions';
const EDITOR_PROJECT_API_BASE = '/api/editor/projects';
@@ -25,8 +25,38 @@ const EDITOR_BACKGROUND_MUSIC_GENERATION_API =
'/api/editor/audios/background-music/generations';
const EDITOR_GENERATION_PRICING_API = '/api/editor/generation-pricing';
const EDITOR_IMAGE_MODEL_NANOBANANA2 = 'gemini-3.1-flash-image-preview';
const EDITOR_VIDEO_REFERENCE_REQUEST_LIMIT_BYTES = 256 * 1024;
const DEFAULT_PROJECT_TITLE = '未命名画布';
function assertStableEditorMediaReference(value: string, fieldLabel: string) {
const normalized = value.trimStart().toLowerCase();
if (normalized.startsWith('data:') || normalized.startsWith('blob:')) {
throw new Error(
`${fieldLabel}必须先上传 OSS,并使用 objectKey、项目资源、素材 ID 或支持的 URL 引用。`,
);
}
}
function assertStableEditorMediaReferences(
values: readonly string[] | undefined,
fieldLabel: string,
) {
values?.forEach((value) => assertStableEditorMediaReference(value, fieldLabel));
}
function assertStableEditorVideoReferences(input: EditorVideoGenerationInput) {
const references = [
...(input.referenceImageSrcs ?? []),
...(input.referenceVideoSrcs ?? []),
...(input.referenceAudioSrcs ?? []),
];
assertStableEditorMediaReferences(references, '视频参考素材');
const requestBytes = new TextEncoder().encode(references.join('')).byteLength;
if (requestBytes > EDITOR_VIDEO_REFERENCE_REQUEST_LIMIT_BYTES) {
throw new Error('视频参考素材稳定引用字段总长度不能超过 256KB');
}
}
export type EditorCanvasViewport = {
x: number;
y: number;
@@ -874,6 +904,7 @@ export async function toggleEditorShowcaseAssetLike(
}
export async function generateEditorImage(input: EditorImageGenerationInput) {
assertStableEditorMediaReferences(input.referenceImageSrcs, '生成参考图');
return requestJson<EditorImageGenerationResponse>(
EDITOR_IMAGE_GENERATION_API,
jsonRequest('POST', {
@@ -913,6 +944,11 @@ export async function generateEditorImage(input: EditorImageGenerationInput) {
export async function generateEditorIconSpritesheet(
input: EditorIconSpritesheetGenerationInput,
) {
assertStableEditorMediaReference(input.referenceImageSrc, '图标素材规范');
assertStableEditorMediaReferences(
input.referenceImageSrcs,
'图标素材参考图',
);
return requestJson<EditorIconSpritesheetGenerationResponse>(
EDITOR_ICON_SPRITESHEET_GENERATION_API,
jsonRequest('POST', {
@@ -962,6 +998,8 @@ export async function splitEditorIconSpritesheet(
export async function extractEditorUiDesignAssets(
input: EditorUiDesignAssetExtractionInput,
) {
assertStableEditorMediaReference(input.sourceImageSrc, 'UI设计图');
assertStableEditorMediaReferences(input.referenceImageSrcs, 'UI素材参考图');
return requestJson<EditorIconSpritesheetGenerationResponse>(
EDITOR_UI_DESIGN_ASSET_EXTRACTION_API,
jsonRequest('POST', {
@@ -995,6 +1033,8 @@ export async function extractEditorUiDesignAssets(
}
export async function editEditorImage(input: EditorImageEditInput) {
assertStableEditorMediaReference(input.sourceImageSrc, '待修改图片');
assertStableEditorMediaReferences(input.referenceImageSrcs, '修改参考图');
return requestJson<EditorImageGenerationResponse>(
EDITOR_IMAGE_EDIT_API,
jsonRequest('POST', {
@@ -1033,6 +1073,7 @@ export async function editEditorImage(input: EditorImageEditInput) {
export async function removeEditorImageBackground(
input: EditorBackgroundRemovalInput,
) {
assertStableEditorMediaReference(input.sourceImageSrc, '待去除背景图片');
return requestJson<EditorBackgroundRemovalResponse>(
EDITOR_BACKGROUND_REMOVAL_API,
jsonRequest('POST', {
@@ -1064,6 +1105,7 @@ export async function removeEditorImageBackground(
export async function generateEditorCharacterAnimation(
input: EditorCharacterAnimationGenerationInput,
) {
assertStableEditorMediaReference(input.sourceImageSrc, '角色动画源图');
return requestJson<EditorCharacterAnimationGenerationResult>(
EDITOR_CHARACTER_ANIMATION_GENERATION_API,
jsonRequest('POST', {
@@ -1088,6 +1130,7 @@ export async function generateEditorCharacterAnimation(
}
export async function generateEditorVideo(input: EditorVideoGenerationInput) {
assertStableEditorVideoReferences(input);
return requestJson<EditorVideoGenerationResponse>(
EDITOR_VIDEO_GENERATION_API,
jsonRequest('POST', {