Files
Genarrative/src/services/image-editor/editorProjectClient.ts
T
lhk229 3de9341a9c
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 4m30s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 4m44s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m37s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m6s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 5m9s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m26s
Project CI / Native shell tests (pull_request) Successful in 19m40s
Project CI / Frontend tests (pull_request) Successful in 21m8s
Project CI / Repository checks (pull_request) Successful in 21m29s
Project CI / Backend tests (pull_request) Successful in 27m41s
Project CI / AI game creator shell web tests (pull_request) Successful in 19m50s
合并最新主分支并保留策划退役清理
合并 origin/master 的最新功能与测试变更

保留已删除的策划 Agent V1/V2 运行时和旧 GDD 入口

仅解决资源预览类型冲突,不恢复退役策划调用
2026-09-17 14:10:46 +00:00

1702 lines
54 KiB
TypeScript

import {
type BackgroundMusicPromptAssistRequest,
type BackgroundMusicPromptAssistResponse,
type EditorSoundEffectGenerationMetadataV2,
type EditorSoundEffectGenerationRequest,
SOUND_EFFECT_DURATION_MAX_SECONDS,
SOUND_EFFECT_DURATION_MIN_SECONDS,
type SoundEffectPromptOptimizeRequest,
type SoundEffectPromptOptimizeResponse,
} 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';
const EDITOR_PROJECT_API_BASE = '/api/editor/projects';
const EDITOR_ASSET_API_BASE = '/api/editor/assets';
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_ICON_SPEC_GENERATION_API = '/api/editor/icon-specs/generations';
const EDITOR_ICON_SPEC_REFINE_GAME_PLAY_API =
'/api/editor/llm/icon-specs/refine-game-play';
const EDITOR_ICON_SPEC_REFINE_ART_STYLE_API =
'/api/editor/llm/icon-specs/refine-art-style';
export const EDITOR_ICON_SPEC_PROMPT_MAX_LENGTH = 200;
export const EDITOR_ICON_DESCRIPTION_LIMIT = 100;
export const EDITOR_ICON_DESCRIPTION_MAX_CHARS = 200;
export const EDITOR_ICON_DESCRIPTIONS_MAX_TOTAL_CHARS = 2_000;
export const EDITOR_ICON_DESCRIPTIONS_MAX_TOTAL_UTF8_BYTES = 6 * 1024;
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';
const EDITOR_ICON_SPRITESHEET_GENERATION_API =
'/api/editor/icon-spritesheets/generations';
const EDITOR_ICON_SPRITESHEET_SLICE_API =
'/api/editor/icon-spritesheets/slices';
const EDITOR_UI_DESIGN_ASSET_EXTRACTION_API =
'/api/editor/ui-designs/assets/extractions';
const EDITOR_CHARACTER_ANIMATION_GENERATION_API =
'/api/editor/character-animations/generations';
const EDITOR_VIDEO_GENERATION_API = '/api/editor/videos/generations';
const EDITOR_SOUND_EFFECT_GENERATION_API =
'/api/editor/audios/sound-effects/generations';
const EDITOR_BACKGROUND_MUSIC_GENERATION_API =
'/api/editor/audios/background-music/generations';
const EDITOR_BACKGROUND_MUSIC_PROMPT_COMPLETION_API =
'/api/editor/audios/background-music/prompts/completions';
const EDITOR_BACKGROUND_MUSIC_PROMPT_SIMPLIFICATION_API =
'/api/editor/audios/background-music/prompts/simplifications';
const EDITOR_SOUND_EFFECT_PROMPT_OPTIMIZATION_API =
'/api/editor/audios/sound-effects/prompts/optimizations';
/**
* 覆盖简化两个业务语义轮加单轮 transport retry 的服务端预算,同时为浏览器到 BFF 的
* 悬挂连接提供有界退出;超时后走既有失败路径恢复 idle,不写回候选。
*/
export const EDITOR_BACKGROUND_MUSIC_PROMPT_ASSIST_TIMEOUT_MS = 180_000;
export const EDITOR_SOUND_EFFECT_PROMPT_OPTIMIZE_TIMEOUT_MS = 180_000;
const EDITOR_GENERATION_PRICING_API = '/api/editor/generation-pricing';
const EDITOR_IMAGE_MODEL_NANOBANANA2 = 'gemini-3.1-flash-image-preview';
const EDITOR_IMAGE_REFERENCE_LIMIT = 5;
const EDITOR_QUICK_EDIT_REFERENCE_LIMIT = 9;
const EDITOR_IMAGE_EDIT_EXTRA_REFERENCE_LIMIT = 8;
const EDITOR_ICON_EXTRA_REFERENCE_LIMIT = 8;
const EDITOR_UI_EXTRA_REFERENCE_LIMIT = 5;
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 requireEditorIconSpecPromptLength(value: string, fieldLabel: string) {
const normalized = value.trim();
if (!normalized) {
throw new Error(`${fieldLabel}不能为空`);
}
if (Array.from(normalized).length > EDITOR_ICON_SPEC_PROMPT_MAX_LENGTH) {
throw new Error(
`${fieldLabel}不能超过 ${EDITOR_ICON_SPEC_PROMPT_MAX_LENGTH} 个字符`,
);
}
return normalized;
}
function resolveEditorProviderReferenceLimit(model: string | null | undefined) {
const normalized = model?.trim();
return normalized === EDITOR_IMAGE_MODEL_NANOBANANA2 ||
normalized === 'nanobanana2' ||
normalized === 'nano-banana'
? 14
: 5;
}
function assertEditorReferenceLimit(
values: readonly string[] | undefined,
limit: number,
fieldLabel: string,
) {
const count = values?.filter((value) => value.trim()).length ?? 0;
if (count > limit) {
throw new Error(`${fieldLabel}最多允许 ${limit} 张,当前选择 ${count} 张`);
}
}
function normalizeEditorIconDescriptions(values: readonly string[]) {
const normalized = values.map((value) => value.trim()).filter(Boolean);
if (
normalized.length < 1 ||
normalized.length > EDITOR_ICON_DESCRIPTION_LIMIT
) {
throw new Error(
`图标素材描述数量必须在 1 到 ${EDITOR_ICON_DESCRIPTION_LIMIT} 个之间`,
);
}
normalized.forEach((description, index) => {
const actualLength = Array.from(description).length;
if (actualLength > EDITOR_ICON_DESCRIPTION_MAX_CHARS) {
throw new Error(
`第 ${index + 1} 条图标素材描述不能超过 ${EDITOR_ICON_DESCRIPTION_MAX_CHARS} 个字符`,
);
}
});
const prompt = normalized.join('\n');
const totalLength = Array.from(prompt).length;
if (totalLength > EDITOR_ICON_DESCRIPTIONS_MAX_TOTAL_CHARS) {
throw new Error(
`图标素材描述合计不能超过 ${EDITOR_ICON_DESCRIPTIONS_MAX_TOTAL_CHARS} 个字符`,
);
}
const totalUtf8Bytes = new TextEncoder().encode(prompt).byteLength;
if (totalUtf8Bytes > EDITOR_ICON_DESCRIPTIONS_MAX_TOTAL_UTF8_BYTES) {
throw new Error(
`图标素材描述合计不能超过 ${EDITOR_ICON_DESCRIPTIONS_MAX_TOTAL_UTF8_BYTES} 个 UTF-8 字节`,
);
}
return normalized;
}
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;
scale: number;
};
export type EditorProjectLayerSnapshot = Record<string, unknown> & {
layerId: string;
resourceId: string;
assetKindOverride?: string | null;
};
export type EditorAssetGenerationInputField = {
id?: string;
title: string;
value: string | number | boolean;
};
export type EditorAssetGenerationInputReference = {
id?: string;
title: string;
label?: string;
refType: 'project-resource' | 'asset';
refId: string;
};
export type EditorAssetGenerationInputs = {
version?: 2;
action?:
| 'image.generate'
| 'scene.generate'
| 'spec.generate'
| 'character.generate'
| 'icon.generate'
| 'ui-design.generate'
| 'publication.generate'
| 'video.generate'
| 'audio.sound-effect.generate'
| 'audio.background-music.generate'
| 'character-animation.generate'
| 'image.edit'
| 'ui-design.extract-assets'
| 'image.perfect-pixel'
| 'spritesheet.split'
| 'image.remove-background'
| 'image.crop-expand';
fields: EditorAssetGenerationInputField[];
references: EditorAssetGenerationInputReference[];
soundEffect?: EditorSoundEffectGenerationMetadataV2;
[key: string]: unknown;
};
export type EditorProjectResourceSourceType =
'uploaded' | 'generated' | 'mock_generated';
export type EditorProjectResourceSnapshot = {
resourceId: string;
showcaseId?: string | null;
assetId?: string | null;
label?: string | null;
projectId: string;
ownerUserId?: string | null;
authorDisplayName?: string | null;
authorPublicUserCode?: string | null;
imageSrc: string;
objectKey?: string | null;
assetObjectId?: string | null;
width: number;
height: number;
sourceType: EditorProjectResourceSourceType;
prompt?: string | null;
actualPrompt?: string | null;
model?: string | null;
provider?: string | null;
taskId?: string | null;
imageSequenceFrames?: EditorImageSequenceFrameResult[] | null;
imageSequenceDurationMs?: number | null;
sourceResourceId?: string | null;
assetKind?: string | null;
showcaseCategory?: string | null;
generationInputs?: EditorAssetGenerationInputs | null;
publicShowcaseEnabled?: boolean;
reviewStatus?: 'pending' | 'approved' | 'rejected' | string | null;
displayEnabled?: boolean | null;
likeCount?: number | null;
generationCostMudPoints?: number;
refundMudPoints?: number | null;
createdAt?: string;
updatedAt?: string;
};
export type EditorPublicShowcaseResourceSnapshot =
EditorProjectResourceSnapshot & {
viewerLiked: boolean;
};
export type EditorAssetFolderSnapshot = {
folderId: string;
label: string;
sortOrder: number;
collapsed: boolean;
systemDefault: boolean;
createdAt?: string;
updatedAt?: string;
};
export type EditorAssetSnapshot = {
assetId: string;
folderId: string;
label: string;
imageSrc: string;
thumbnailSrc?: string | null;
objectKey?: string | null;
assetObjectId?: string | null;
width: number;
height: number;
sourceType: EditorProjectResourceSourceType;
prompt?: string | null;
actualPrompt?: string | null;
model?: string | null;
provider?: string | null;
taskId?: string | null;
imageSequenceFrames?: EditorImageSequenceFrameResult[] | null;
imageSequenceDurationMs?: number | null;
sourceResourceId?: string | null;
assetKind?: string | null;
showcaseCategory?: string | null;
generationInputs?: EditorAssetGenerationInputs | null;
publicShowcaseEnabled?: boolean | null;
generationCostMudPoints?: number;
showcaseId?: string | null;
showcaseReviewStatus?: 'pending' | 'approved' | 'rejected' | string | null;
showcaseDisplayEnabled?: boolean | null;
showcaseLikeCount?: number | null;
createdAt?: string;
updatedAt?: string;
};
export type EditorShowcaseCampaignSnapshot = {
enabled: boolean;
title: string;
imageSrc: string;
imageObjectKey?: string | null;
imageWidth?: number | null;
imageHeight?: number | null;
prompt: string;
author: string;
costText: string;
updatedAt?: string;
};
export type EditorAssetLibrarySnapshot = {
folders: EditorAssetFolderSnapshot[];
assets: EditorAssetSnapshot[];
};
export type EditorImageGenerationStyle = 'none' | 'pixelArt';
export type EditorImageGenerationInput = {
prompt: string;
size?: string;
kind?:
'spec' | 'character' | 'quick-edit' | 'ui-design' | 'publication-material';
model?: string;
screenColor?: string;
segModel?: string;
style?: EditorImageGenerationStyle;
aspectRatio?: string;
imageSize?: string;
referenceImageSrcs?: string[];
projectId?: string | null;
assetKind?: string | null;
generationInputs?: EditorAssetGenerationInputs | null;
assetFolderId?: string | null;
assetLabel?: string | null;
sourceResourceId?: string | null;
canvasCompletion?: EditorCanvasGenerationCompletionInput | null;
};
export type EditorIconSpecGenerationInput = {
playSetting: string;
artStyle: string;
referenceId?: string;
projectId?: string | null;
generationInputs?: EditorAssetGenerationInputs | null;
assetFolderId?: string | null;
assetLabel?: string | null;
sourceResourceId?: string | null;
canvasCompletion?: EditorCanvasGenerationCompletionInput | null;
};
export type EditorCanvasGenerationCompletionInput = {
dialogId?: string;
title: string;
placeholder: {
x: number;
y: number;
width: number;
height: number;
originalWidth: number;
originalHeight: number;
};
};
export type EditorIconSpritesheetGenerationInput = {
referenceId: string;
referenceImageSrcs?: string[];
iconDescriptions: string[];
/** 必填且没有默认值:必须显式声明连通域或网格切分。 */
sliceMode: 'connected-components' | 'grid';
gridX?: number;
gridY?: number;
model?: string;
screenColor?: string;
segModel?: string;
style?: EditorImageGenerationStyle;
aspectRatio?: string;
imageSize?: string;
projectId?: string | null;
generationInputs?: EditorAssetGenerationInputs | null;
assetFolderId?: string | null;
assetLabel?: string | null;
canvasCompletion?: EditorCanvasGenerationCompletionInput | null;
};
export type EditorIconSpritesheetSliceInput = {
projectId: string;
sourceLayerId: string;
sourceResourceId: string;
assetFolderId?: string | null;
canvasCompletion: EditorCanvasGenerationCompletionInput;
};
export type EditorUiDesignAssetExtractionInput = {
sourceImageSrc: string;
model?: string;
screenColor?: string;
segModel?: string;
referenceImageSrcs?: string[];
aspectRatio?: string;
imageSize?: string;
projectId?: string | null;
generationInputs?: EditorAssetGenerationInputs | null;
assetFolderId?: string | null;
spritesheetLabel?: string | null;
canvasCompletion?: EditorCanvasGenerationCompletionInput | null;
};
export type EditorImageEditInput = {
prompt: string;
sourceReferenceId: string;
size?: string;
model?: string;
aspectRatio?: string;
imageSize?: string;
referenceImageSrcs?: string[];
projectId?: string | null;
generationInputs?: EditorAssetGenerationInputs | null;
assetFolderId?: string | null;
assetLabel?: string | null;
targetLayerId?: string | null;
canvasCompletion?: EditorCanvasGenerationCompletionInput | null;
};
export type EditorBackgroundRemovalInput = {
sourceImageSrc: string;
projectId?: string | null;
targetLayerId?: string | null;
assetKind?: string | null;
generationInputs?: EditorAssetGenerationInputs | null;
assetFolderId?: string | null;
assetLabel?: string | null;
sourceResourceId?: string | null;
taskId?: string | null;
canvasCompletion?: EditorCanvasGenerationCompletionInput | null;
};
export type EditorPixelArtSnapInput = {
sourceImageSrc: string;
projectId: string;
sourceResourceId?: string | null;
assetKind?: string | null;
generationInputs?: EditorAssetGenerationInputs | null;
assetFolderId?: string | null;
assetLabel?: string | null;
canvasCompletion: EditorCanvasGenerationCompletionInput & {
dialogId: string;
};
};
export type EditorImageGenerationResult = {
imageSrc: string;
objectKey?: string | null;
assetObjectId?: string | null;
width: number;
height: number;
sourceType: 'generated';
prompt: string;
actualPrompt?: string | null;
model: string;
taskId: string;
resource?: EditorProjectResourceSnapshot | null;
asset?: EditorAssetSnapshot | null;
project?: EditorProjectSnapshot | null;
warning?: EditorGenerationWarning | null;
queueState?: ExternalGenerationJobStatusRecord | null;
};
export type EditorBackgroundRemovalResult = {
queueState: ExternalGenerationJobStatusRecord;
};
export type EditorPixelArtSnapResult = {
imageSrc: string;
objectKey: string;
assetObjectId: string;
width: number;
height: number;
sourceType: 'generated';
taskId: string;
elapsedMs: number;
provider: 'Genarrative';
resource: EditorProjectResourceSnapshot;
asset: EditorAssetSnapshot;
project: EditorProjectSnapshot | null;
};
export type EditorIconSpritesheetIconResult = {
name: string;
imageSrc: string;
width: number;
height: number;
resource?: EditorProjectResourceSnapshot | null;
asset?: EditorAssetSnapshot | null;
};
export type EditorGenerationWarning = {
code: string;
reason: string;
};
export type EditorIconSpritesheetSliceWarning = EditorGenerationWarning;
export type EditorIconSpritesheetGenerationResult = {
spritesheetImageSrc: string;
spritesheetWidth: number;
spritesheetHeight: number;
iconImageSrcs: EditorIconSpritesheetIconResult[];
sliceMode?: 'connected-components' | 'grid';
gridX?: number;
gridY?: number;
sliceWarning?: EditorIconSpritesheetSliceWarning | null;
prompt: string;
actualPrompt?: string | null;
model: string;
taskId: string;
priceMudPoints: number;
spritesheetResource?: EditorProjectResourceSnapshot | null;
spritesheetAsset?: EditorAssetSnapshot | null;
project?: EditorProjectSnapshot | null;
warning?: EditorGenerationWarning | null;
queueState?: ExternalGenerationJobStatusRecord | null;
};
export type EditorIconSpritesheetSliceResult = {
iconImageSrcs: EditorIconSpritesheetIconResult[];
project: EditorProjectSnapshot;
};
export type EditorCharacterAnimationResolution = '480p' | '720p';
export type EditorCharacterAnimationRatio =
'same' | '1:1' | '4:3' | '16:9' | '9:16' | '3:4';
export type EditorCharacterAnimationFrameCount = 32 | 40 | 48;
export type EditorCharacterAnimationDurationSeconds = 4 | 5 | 6;
export type EditorCharacterAnimationGenerationInput = {
sourceLayerId: string;
sourceImageSrc: string;
sourceWidth: number;
sourceHeight: number;
promptText: string;
/** 纯色抠像背景色(hex 或 'auto'),继承生图的背景色选项行为。 */
screenColor?: string;
resolution: EditorCharacterAnimationResolution;
ratio: EditorCharacterAnimationRatio;
frameCount: EditorCharacterAnimationFrameCount;
durationSeconds: EditorCharacterAnimationDurationSeconds;
model: 'seedance2.0-fast';
projectId?: string | null;
canvasCompletion?: EditorCanvasGenerationCompletionInput | null;
generationInputs?: EditorAssetGenerationInputs | null;
sourceResourceId?: string | null;
assetFolderId?: string | null;
assetLabel?: string | null;
};
export type EditorImageSequenceFrameResult = {
imageSrc: string;
objectKey?: string | null;
assetObjectId?: string | null;
width: number;
height: number;
};
export type EditorCharacterAnimationGenerationResult = {
taskId: string;
model: 'seedance2.0-fast';
prompt: string;
previewVideoPath: string;
frames: EditorImageSequenceFrameResult[];
frameCount: number;
durationSeconds: number;
fps: number;
priceMudPoints: number;
project?: EditorProjectSnapshot | null;
resource?: EditorProjectResourceSnapshot | null;
asset?: EditorAssetSnapshot | null;
queueState?: ExternalGenerationJobStatusRecord | null;
};
export type EditorVideoModel =
| 'seedance2.0'
| 'seedance2.0-fast'
| 'kling3.0'
| 'kling3.0-omni'
| 'veo3.1'
| 'veo3.1-fast';
export type EditorVideoResolution = '480p' | '720p' | '1080p';
export type EditorVideoSoundMode = 'on' | 'off';
export type EditorVideoAspectRatio =
'16:9' | '9:16' | '1:1' | '4:3' | '3:4' | '21:9';
export type EditorVideoGenerationInput = {
prompt: string;
model: EditorVideoModel;
aspectRatio: EditorVideoAspectRatio;
durationSeconds: number;
resolution: EditorVideoResolution;
mode: 'std';
sound: EditorVideoSoundMode;
webSearchEnabled?: boolean;
referenceImageSrcs?: string[];
referenceVideoSrcs?: string[];
referenceAudioSrcs?: string[];
projectId?: string | null;
canvasCompletion?: EditorCanvasGenerationCompletionInput | null;
generationInputs?: EditorAssetGenerationInputs | null;
sourceResourceId?: string | null;
assetKind?: string | null;
assetFolderId?: string | null;
assetLabel?: string | null;
};
export type EditorVideoGenerationResult = {
videoSrc: string;
thumbnailSrc?: string | null;
objectKey?: string | null;
assetObjectId?: string | null;
width: number;
height: number;
sourceType: 'generated';
prompt: string;
actualPrompt?: string | null;
model: string;
taskId: string;
durationSeconds: number;
priceMudPoints?: number;
resource?: EditorProjectResourceSnapshot | null;
asset?: EditorAssetSnapshot | null;
project?: EditorProjectSnapshot | null;
queueState?: ExternalGenerationJobStatusRecord | null;
};
export type EditorSoundEffectGenerationInput =
EditorSoundEffectGenerationRequest & {
projectId?: string | null;
canvasCompletion?: EditorCanvasGenerationCompletionInput | null;
generationInputs?: EditorAssetGenerationInputs | null;
assetFolderId?: string | null;
assetLabel?: string | null;
};
export type EditorBackgroundMusicGenerationInput = {
gptDescriptionPrompt: string;
makeInstrumental: true;
projectId?: string | null;
canvasCompletion?: EditorCanvasGenerationCompletionInput | null;
generationInputs?: EditorAssetGenerationInputs | null;
assetFolderId?: string | null;
assetLabel?: string | null;
};
export type EditorBackgroundMusicPromptAssistOptions = {
signal?: AbortSignal;
};
export type EditorSoundEffectPromptOptimizeOptions = {
signal?: AbortSignal;
};
export type EditorAudioGenerationResult = {
audioSrc: string;
objectKey?: string | null;
assetObjectId?: string | null;
width: number;
height: number;
sourceType: 'generated';
prompt: string;
actualPrompt?: string | null;
model: string;
taskId: string;
priceMudPoints: number;
audioKind: 'sound-effect' | 'background-music';
durationSeconds?: number | null;
loop?: boolean | null;
resource?: EditorProjectResourceSnapshot | null;
asset?: EditorAssetSnapshot | null;
project?: EditorProjectSnapshot | null;
queueState?: ExternalGenerationJobStatusRecord | null;
};
export type EditorGenerationPricingUnit = 'perGeneration' | 'perSecond';
export type EditorGenerationModelPricing = {
unit: EditorGenerationPricingUnit;
price?: number;
prices?: Record<string, number>;
};
export type EditorGenerationPricingConfig = {
models: Record<string, EditorGenerationModelPricing>;
};
export type EditorProjectSnapshot = {
projectId: string;
title: string;
canvas?: EditorCanvasSnapshot;
viewport: EditorCanvasViewport;
layers: EditorProjectLayerSnapshot[];
resources: EditorProjectResourceSnapshot[];
updatedAt: string;
};
export type EditorCanvasSnapshot = {
canvasId: string;
projectId: string;
title: string;
viewport: EditorCanvasViewport;
layers: EditorProjectLayerSnapshot[];
revision?: number;
layoutStorageVersion?: number;
backgroundColor?: string | null;
createdAt?: string;
updatedAt: string;
};
export type EditorProjectLoadOptions = {
signal?: AbortSignal;
timeoutMs?: number;
deadlineAt?: number;
};
export type EditorProjectCreateInput = {
title?: string;
};
export type EditorProjectLayoutSaveInput = {
viewport: EditorCanvasViewport;
layers: EditorProjectLayerSnapshot[];
expectedRevision: number;
};
export type EditorProjectLayoutSaveResult = {
projectId: string;
canvasId: string;
revision: number;
updatedAt: string;
};
export type EditorProjectResourceCreateInput = {
imageSrc: string;
objectKey?: string | null;
assetObjectId?: string | null;
width: number;
height: number;
sourceType: EditorProjectResourceSourceType;
prompt?: string | null;
actualPrompt?: string | null;
model?: string | null;
provider?: string | null;
taskId?: string | null;
imageSequenceFrames?: EditorImageSequenceFrameResult[] | null;
imageSequenceDurationMs?: number | null;
sourceResourceId?: string | null;
assetKind?: string | null;
generationInputs?: EditorAssetGenerationInputs | null;
};
export type EditorAssetCreateInput = {
folderId: string;
label: string;
imageSrc: string;
objectKey?: string | null;
assetObjectId?: string | null;
width: number;
height: number;
sourceType: EditorProjectResourceSourceType;
prompt?: string | null;
actualPrompt?: string | null;
model?: string | null;
provider?: string | null;
taskId?: string | null;
imageSequenceFrames?: EditorImageSequenceFrameResult[] | null;
imageSequenceDurationMs?: number | null;
assetKind?: string | null;
generationInputs?: EditorAssetGenerationInputs | null;
};
export type EditorAssetUpdateInput = {
label?: string;
folderId?: string;
};
type EditorProjectResponse = {
project: EditorProjectSnapshot;
};
type EditorProjectLayoutSaveResponse = EditorProjectLayoutSaveResult;
type EditorProjectRecentResponse = {
project: EditorProjectSnapshot | null;
};
type EditorProjectResourceResponse = {
resource: EditorProjectResourceSnapshot;
};
type EditorProjectResourceListResponse = {
resources: EditorPublicShowcaseResourceSnapshot[];
nextCursor?: string | null;
campaign?: EditorShowcaseCampaignSnapshot | null;
};
export type EditorProjectResourceListPage = {
resources: EditorPublicShowcaseResourceSnapshot[];
nextCursor: string | null;
campaign?: EditorShowcaseCampaignSnapshot | null;
};
type EditorAssetLibraryResponse = {
library: EditorAssetLibrarySnapshot;
};
type EditorAssetFolderResponse = {
folder: EditorAssetFolderSnapshot;
};
type EditorAssetFolderDeleteResponse = {
library: EditorAssetLibrarySnapshot;
};
type EditorAssetResponse = {
asset: EditorAssetSnapshot;
};
type EditorShowcaseAssetResponse = {
showcaseAsset: EditorProjectResourceSnapshot;
};
type EditorShowcaseAssetViewerResponse = {
showcaseAsset: EditorPublicShowcaseResourceSnapshot;
};
type EditorImageGenerationResponse = EditorImageGenerationResult;
type EditorBackgroundRemovalResponse = EditorBackgroundRemovalResult;
type EditorPixelArtSnapResponse = EditorPixelArtSnapResult;
type EditorIconSpritesheetGenerationResponse =
EditorIconSpritesheetGenerationResult;
type EditorVideoGenerationResponse = EditorVideoGenerationResult;
type EditorAudioGenerationResponse = EditorAudioGenerationResult;
function jsonRequest(method: 'POST' | 'PATCH', body: Record<string, unknown>) {
return {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
};
}
export async function listEditorProjects() {
const response = await requestJson<{ projects: EditorProjectSnapshot[] }>(
EDITOR_PROJECT_API_BASE,
{ method: 'GET' },
'读取图片画布工程列表失败',
);
return response.projects;
}
export async function listPublicEditorProjectResources({
cursor,
viewer = 'anonymous',
}: {
cursor?: string | null;
viewer?: 'anonymous' | 'authenticated';
} = {}): Promise<EditorProjectResourceListPage> {
const params = new URLSearchParams();
if (cursor?.trim()) {
params.set('cursor', cursor.trim());
}
const url = params.toString()
? `${EDITOR_SHOWCASE_RESOURCE_API}?${params.toString()}`
: EDITOR_SHOWCASE_RESOURCE_API;
const response =
viewer === 'authenticated'
? await requestJson<EditorProjectResourceListResponse>(
url,
{ method: 'GET' },
'读取陶泥儿精选素材失败',
)
: await requestJson<EditorProjectResourceListResponse>(
url,
{ method: 'GET' },
'读取陶泥儿精选素材失败',
{
skipAuth: true,
skipRefresh: true,
notifyAuthStateChange: false,
clearAuthOnUnauthorized: false,
},
);
return {
resources: response.resources,
nextCursor: response.nextCursor?.trim() || null,
campaign: response.campaign ?? null,
};
}
export async function loadRecentEditorProject() {
return requestJson<EditorProjectRecentResponse>(
`${EDITOR_PROJECT_API_BASE}/recent`,
{ method: 'GET' },
'读取图片画布工程失败',
);
}
export async function createEditorProject(
input: EditorProjectCreateInput = {},
) {
const response = await requestJson<EditorProjectResponse>(
EDITOR_PROJECT_API_BASE,
jsonRequest('POST', {
title: input.title?.trim() || DEFAULT_PROJECT_TITLE,
}),
'创建图片画布工程失败',
);
return response.project;
}
export async function loadOrCreateRecentEditorProject() {
const response = await loadRecentEditorProject();
if (response.project) {
return response.project;
}
return createEditorProject({ title: DEFAULT_PROJECT_TITLE });
}
export async function loadEditorGenerationPricing() {
return requestJson<EditorGenerationPricingConfig>(
EDITOR_GENERATION_PRICING_API,
{ method: 'GET' },
'读取模型定价失败',
{
skipAuth: true,
skipRefresh: true,
notifyAuthStateChange: false,
clearAuthOnUnauthorized: false,
},
);
}
export async function loadEditorProject(
projectId: string,
options: EditorProjectLoadOptions = {},
) {
const hasAbsoluteDeadline =
typeof options.deadlineAt === 'number' &&
Number.isFinite(options.deadlineAt);
const response = await requestJson<EditorProjectResponse>(
`${EDITOR_PROJECT_API_BASE}/${encodeURIComponent(projectId)}`,
{
method: 'GET',
...(options.signal ? { signal: options.signal } : {}),
},
'读取图片画布工程失败',
hasAbsoluteDeadline
? { deadlineAt: options.deadlineAt }
: // 中文注释:普通项目读取继续保留既有单次 fetch timeout;完美像素对账显式传
// deadlineAt,改由 requestJson 从鉴权恢复到响应体读取约束完整生命周期。
{ timeoutMs: options.timeoutMs ?? 60_000 },
);
return response.project;
}
export async function renameEditorProject(projectId: string, title: string) {
const response = await requestJson<EditorProjectResponse>(
`${EDITOR_PROJECT_API_BASE}/${encodeURIComponent(projectId)}/metadata`,
jsonRequest('PATCH', { title }),
'重命名图片画布工程失败',
);
return response.project;
}
export async function deleteEditorProject(projectId: string) {
const response = await requestJson<{ deletedProjectId: string }>(
`${EDITOR_PROJECT_API_BASE}/${encodeURIComponent(projectId)}`,
{ method: 'DELETE' },
'删除图片画布工程失败',
);
return response.deletedProjectId;
}
// 中文注释:这里曾接受第三参数(调用方注入的 `signal` / `deadlineAt`),只服务于已删除的
// 严格布局保存通道;该通道删除后全部调用点都只传两个参数,注入分支恒不生效。一并摘掉,
// 免得后来者以为调用方还能控制这次保存的取消与截止。
export async function saveEditorProjectLayout(
projectId: string,
input: EditorProjectLayoutSaveInput,
) {
return requestJson<EditorProjectLayoutSaveResponse>(
`${EDITOR_PROJECT_API_BASE}/${encodeURIComponent(projectId)}`,
jsonRequest('PATCH', {
viewport: input.viewport,
layers: input.layers,
expectedRevision: input.expectedRevision,
}),
'保存图片画布工程失败',
// 布局保存会被生成提交链同步等待,必须连鉴权恢复和响应体读取一起有界。
{ deadlineAt: Date.now() + 60_000 },
);
}
export async function createEditorProjectResource(
projectId: string,
input: EditorProjectResourceCreateInput,
) {
const response = await requestJson<EditorProjectResourceResponse>(
`${EDITOR_PROJECT_API_BASE}/${encodeURIComponent(projectId)}/resources`,
jsonRequest('POST', { ...input }),
'创建图片画布资源失败',
);
return response.resource;
}
export async function updateEditorProjectResourceShowcase(
resourceId: string,
publicShowcaseEnabled: boolean,
) {
const response = await requestJson<EditorProjectResourceResponse>(
`${EDITOR_PROJECT_RESOURCE_API_BASE}/${encodeURIComponent(resourceId)}/showcase`,
jsonRequest('PATCH', { publicShowcaseEnabled }),
'更新素材公开展示状态失败',
);
return response.resource;
}
export async function loadEditorAssetLibrary() {
const response = await requestJson<EditorAssetLibraryResponse>(
`${EDITOR_ASSET_API_BASE}/library`,
{ method: 'GET' },
'读取图片画布素材库失败',
);
return response.library;
}
export async function createEditorAssetFolder(
label: string,
sortOrder?: number,
) {
const response = await requestJson<EditorAssetFolderResponse>(
`${EDITOR_ASSET_API_BASE}/folders`,
jsonRequest('POST', { label, sortOrder }),
'创建图片画布素材文件夹失败',
);
return response.folder;
}
export async function updateEditorAssetFolder(
folderId: string,
input: { label?: string; collapsed?: boolean },
) {
const response = await requestJson<EditorAssetFolderResponse>(
`${EDITOR_ASSET_API_BASE}/folders/${encodeURIComponent(folderId)}`,
jsonRequest('PATCH', input),
'更新图片画布素材文件夹失败',
);
return response.folder;
}
export async function deleteEditorAssetFolder(folderId: string) {
const response = await requestJson<EditorAssetFolderDeleteResponse>(
`${EDITOR_ASSET_API_BASE}/folders/${encodeURIComponent(folderId)}`,
{ method: 'DELETE' },
'删除图片画布素材文件夹失败',
);
return response.library;
}
export async function createEditorAsset(input: EditorAssetCreateInput) {
const response = await requestJson<EditorAssetResponse>(
EDITOR_ASSET_API_BASE,
jsonRequest('POST', input),
'创建图片画布素材失败',
);
return response.asset;
}
export async function updateEditorAsset(
assetId: string,
input: EditorAssetUpdateInput,
) {
const response = await requestJson<EditorAssetResponse>(
`${EDITOR_ASSET_API_BASE}/${encodeURIComponent(assetId)}`,
jsonRequest('PATCH', input),
'更新图片画布素材失败',
);
return response.asset;
}
export async function deleteEditorAsset(assetId: string) {
const response = await requestJson<EditorAssetResponse>(
`${EDITOR_ASSET_API_BASE}/${encodeURIComponent(assetId)}`,
{ method: 'DELETE' },
'删除图片画布素材失败',
);
return response.asset;
}
export async function submitEditorAssetShowcase(assetId: string) {
const response = await requestJson<EditorShowcaseAssetResponse>(
`${EDITOR_ASSET_API_BASE}/${encodeURIComponent(assetId)}/showcase-submissions`,
{ method: 'POST' },
'提交精选审核失败',
);
return response.showcaseAsset;
}
export async function toggleEditorShowcaseAssetLike(
showcaseId: string,
liked: boolean,
) {
const response = await requestJson<EditorShowcaseAssetViewerResponse>(
`${EDITOR_SHOWCASE_ASSET_API_BASE}/${encodeURIComponent(showcaseId)}/likes`,
jsonRequest('POST', { liked }),
liked ? '点赞精选素材失败' : '取消点赞精选素材失败',
);
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 =
input.kind === 'quick-edit'
? Math.min(
EDITOR_QUICK_EDIT_REFERENCE_LIMIT,
resolveEditorProviderReferenceLimit(input.model),
)
: EDITOR_IMAGE_REFERENCE_LIMIT;
assertEditorReferenceLimit(
input.referenceImageSrcs,
referenceLimit,
'生成参考图',
);
return requestJson<EditorImageGenerationResponse>(
EDITOR_IMAGE_GENERATION_API,
jsonRequest('POST', {
prompt: input.prompt,
...(input.size ? { size: input.size } : {}),
...(input.kind ? { kind: input.kind } : {}),
...(input.model ? { model: input.model } : {}),
...(input.screenColor ? { screenColor: input.screenColor } : {}),
...(input.segModel ? { segModel: input.segModel } : {}),
...(input.style ? { style: input.style } : {}),
...(input.aspectRatio ? { aspectRatio: input.aspectRatio } : {}),
...(input.imageSize ? { imageSize: input.imageSize } : {}),
...(input.referenceImageSrcs?.length
? { referenceImageSrcs: input.referenceImageSrcs }
: {}),
...(input.projectId ? { projectId: input.projectId } : {}),
...(input.assetKind ? { assetKind: input.assetKind } : {}),
...(input.generationInputs
? { generationInputs: input.generationInputs }
: {}),
...(input.assetFolderId ? { assetFolderId: input.assetFolderId } : {}),
...(input.assetLabel ? { assetLabel: input.assetLabel } : {}),
...(input.sourceResourceId
? { sourceResourceId: input.sourceResourceId }
: {}),
...(input.canvasCompletion
? { canvasCompletion: input.canvasCompletion }
: {}),
}),
'生成图片失败',
{
timeoutMs: 1_200_000,
retry: EDITOR_GENERATION_REQUEST_RETRY_OPTIONS,
},
);
}
export async function refineEditorIconSpecPlaySetting(playSetting: string) {
const validPlaySetting = requireEditorIconSpecPromptLength(
playSetting,
'玩法设定',
);
const response = await requestJson<{ playSetting: string }>(
EDITOR_ICON_SPEC_REFINE_GAME_PLAY_API,
jsonRequest('POST', { playSetting: validPlaySetting }),
'优化玩法设定失败',
);
return requireEditorIconSpecPromptLength(response.playSetting, '玩法设定');
}
export async function refineEditorIconSpecArtStyle(artStyle: string) {
const validArtStyle = requireEditorIconSpecPromptLength(artStyle, '美术风格');
const response = await requestJson<{ artStyle: string }>(
EDITOR_ICON_SPEC_REFINE_ART_STYLE_API,
jsonRequest('POST', { artStyle: validArtStyle }),
'优化美术风格失败',
);
return requireEditorIconSpecPromptLength(response.artStyle, '美术风格');
}
export async function generateEditorIconSpec(
input: EditorIconSpecGenerationInput,
) {
const playSetting = requireEditorIconSpecPromptLength(
input.playSetting,
'玩法设定',
);
const artStyle = requireEditorIconSpecPromptLength(
input.artStyle,
'美术风格',
);
return requestJson<EditorImageGenerationResponse>(
EDITOR_ICON_SPEC_GENERATION_API,
jsonRequest('POST', {
playSetting,
artStyle,
...(input.referenceId ? { referenceId: input.referenceId } : {}),
...(input.projectId ? { projectId: input.projectId } : {}),
...(input.generationInputs
? { generationInputs: input.generationInputs }
: {}),
...(input.assetFolderId ? { assetFolderId: input.assetFolderId } : {}),
...(input.assetLabel ? { assetLabel: input.assetLabel } : {}),
...(input.sourceResourceId
? { sourceResourceId: input.sourceResourceId }
: {}),
...(input.canvasCompletion
? { canvasCompletion: input.canvasCompletion }
: {}),
}),
'生成图标规范失败',
{
timeoutMs: 1_200_000,
},
);
}
export async function generateEditorIconSpritesheet(
input: EditorIconSpritesheetGenerationInput,
) {
const iconDescriptions = normalizeEditorIconDescriptions(
input.iconDescriptions,
);
const model = input.model?.trim() || EDITOR_IMAGE_MODEL_NANOBANANA2;
// 切分模式没有默认值:声明必须自洽,网格尺寸只能与 grid 同时提交。
if (input.sliceMode === 'grid') {
if (input.gridX === undefined || input.gridY === undefined) {
throw new Error('sliceMode=grid 必须同时提供 gridX 与 gridY');
}
} else if (input.gridX !== undefined || input.gridY !== undefined) {
throw new Error(
'sliceMode=connected-components 不接受 gridX/gridY:网格尺寸只能与 grid 同时提交',
);
}
assertStableEditorMediaReferences(input.referenceImageSrcs, '图标素材参考图');
assertEditorReferenceLimit(
input.referenceImageSrcs,
Math.min(
EDITOR_ICON_EXTRA_REFERENCE_LIMIT,
resolveEditorProviderReferenceLimit(model) - 1,
),
'图标素材参考图',
);
return requestJson<EditorIconSpritesheetGenerationResponse>(
EDITOR_ICON_SPRITESHEET_GENERATION_API,
jsonRequest('POST', {
referenceId: input.referenceId,
...(input.referenceImageSrcs?.length
? { referenceImageSrcs: input.referenceImageSrcs }
: {}),
iconDescriptions,
sliceMode: input.sliceMode,
...(input.sliceMode === 'grid' && input.gridX !== undefined
? { gridX: input.gridX }
: {}),
...(input.sliceMode === 'grid' && input.gridY !== undefined
? { gridY: input.gridY }
: {}),
model,
...(input.screenColor ? { screenColor: input.screenColor } : {}),
...(input.segModel ? { segModel: input.segModel } : {}),
...(input.style ? { style: input.style } : {}),
...(input.aspectRatio ? { aspectRatio: input.aspectRatio } : {}),
...(input.imageSize ? { imageSize: input.imageSize } : {}),
...(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 splitEditorIconSpritesheet(
input: EditorIconSpritesheetSliceInput,
) {
return requestJson<EditorIconSpritesheetSliceResult>(
EDITOR_ICON_SPRITESHEET_SLICE_API,
jsonRequest('POST', {
projectId: input.projectId,
sourceLayerId: input.sourceLayerId,
sourceResourceId: input.sourceResourceId,
...(input.assetFolderId ? { assetFolderId: input.assetFolderId } : {}),
canvasCompletion: input.canvasCompletion,
}),
'拆分图集失败',
);
}
export async function extractEditorUiDesignAssets(
input: EditorUiDesignAssetExtractionInput,
) {
assertStableEditorMediaReference(input.sourceImageSrc, 'UI设计图');
assertStableEditorMediaReferences(input.referenceImageSrcs, 'UI素材参考图');
assertEditorReferenceLimit(
input.referenceImageSrcs,
Math.min(
EDITOR_UI_EXTRA_REFERENCE_LIMIT,
resolveEditorProviderReferenceLimit(input.model) - 1,
),
'UI素材参考图',
);
return requestJson<EditorIconSpritesheetGenerationResponse>(
EDITOR_UI_DESIGN_ASSET_EXTRACTION_API,
jsonRequest('POST', {
sourceImageSrc: input.sourceImageSrc,
...(input.model ? { model: input.model } : {}),
...(input.screenColor ? { screenColor: input.screenColor } : {}),
...(input.segModel ? { segModel: input.segModel } : {}),
...(input.referenceImageSrcs?.length
? { referenceImageSrcs: input.referenceImageSrcs }
: {}),
...(input.projectId ? { projectId: input.projectId } : {}),
...(input.generationInputs
? { generationInputs: input.generationInputs }
: {}),
...(input.assetFolderId ? { assetFolderId: input.assetFolderId } : {}),
...(input.spritesheetLabel
? { spritesheetLabel: input.spritesheetLabel }
: {}),
...(input.aspectRatio ? { aspectRatio: input.aspectRatio } : {}),
...(input.imageSize ? { imageSize: input.imageSize } : {}),
...(input.canvasCompletion
? { canvasCompletion: input.canvasCompletion }
: {}),
}),
'提取UI设计图素材失败',
{
timeoutMs: 1_200_000,
retry: EDITOR_GENERATION_REQUEST_RETRY_OPTIONS,
},
);
}
export async function editEditorImage(input: EditorImageEditInput) {
assertStableEditorMediaReferences(input.referenceImageSrcs, '修改参考图');
assertEditorReferenceLimit(
input.referenceImageSrcs,
Math.min(
EDITOR_IMAGE_EDIT_EXTRA_REFERENCE_LIMIT,
resolveEditorProviderReferenceLimit(input.model) - 1,
),
'修改参考图',
);
return requestJson<EditorImageGenerationResponse>(
EDITOR_IMAGE_EDIT_API,
jsonRequest('POST', {
prompt: input.prompt,
sourceReferenceId: input.sourceReferenceId,
...(input.size ? { size: input.size } : {}),
...(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.targetLayerId ? { targetLayerId: input.targetLayerId } : {}),
...(input.canvasCompletion
? { canvasCompletion: input.canvasCompletion }
: {}),
}),
'修改图片失败',
{
timeoutMs: 1_200_000,
retry: EDITOR_GENERATION_REQUEST_RETRY_OPTIONS,
},
);
}
export async function removeEditorImageBackground(
input: EditorBackgroundRemovalInput,
) {
assertStableEditorMediaReference(input.sourceImageSrc, '待去除背景图片');
return requestJson<EditorBackgroundRemovalResponse>(
EDITOR_BACKGROUND_REMOVAL_API,
jsonRequest('POST', {
sourceImageSrc: input.sourceImageSrc,
...(input.projectId ? { projectId: input.projectId } : {}),
...(input.targetLayerId ? { targetLayerId: input.targetLayerId } : {}),
...(input.assetKind ? { assetKind: input.assetKind } : {}),
...(input.generationInputs
? { generationInputs: input.generationInputs }
: {}),
...(input.assetFolderId ? { assetFolderId: input.assetFolderId } : {}),
...(input.assetLabel ? { assetLabel: input.assetLabel } : {}),
...(input.sourceResourceId
? { sourceResourceId: input.sourceResourceId }
: {}),
...(input.taskId ? { taskId: input.taskId } : {}),
...(input.canvasCompletion
? { canvasCompletion: input.canvasCompletion }
: {}),
}),
'去除背景失败',
{
timeoutMs: 1_200_000,
retry: EDITOR_GENERATION_REQUEST_RETRY_OPTIONS,
},
);
}
export async function snapEditorImageToPixelArt(
input: EditorPixelArtSnapInput,
) {
assertStableEditorMediaReference(input.sourceImageSrc, '待完美像素处理图片');
return requestJson<EditorPixelArtSnapResponse>(
EDITOR_PIXEL_ART_SNAP_API,
jsonRequest('POST', {
sourceImageSrc: input.sourceImageSrc,
projectId: input.projectId,
...(input.sourceResourceId
? { sourceResourceId: input.sourceResourceId }
: {}),
...(input.assetKind ? { assetKind: input.assetKind } : {}),
...(input.generationInputs
? { generationInputs: input.generationInputs }
: {}),
...(input.assetFolderId ? { assetFolderId: input.assetFolderId } : {}),
...(input.assetLabel ? { assetLabel: input.assetLabel } : {}),
canvasCompletion: input.canvasCompletion,
}),
'完美像素处理失败',
{
timeoutMs: 120_000,
},
);
}
export async function generateEditorCharacterAnimation(
input: EditorCharacterAnimationGenerationInput,
) {
assertStableEditorMediaReference(input.sourceImageSrc, '角色动画源图');
return requestJson<EditorCharacterAnimationGenerationResult>(
EDITOR_CHARACTER_ANIMATION_GENERATION_API,
jsonRequest('POST', {
...input,
...(input.projectId ? { projectId: input.projectId } : {}),
...(input.canvasCompletion
? { canvasCompletion: input.canvasCompletion }
: {}),
...(input.generationInputs
? { generationInputs: input.generationInputs }
: {}),
...(input.sourceResourceId
? { sourceResourceId: input.sourceResourceId }
: {}),
}),
'生成角色动画失败',
{
timeoutMs: 1_200_000,
retry: EDITOR_GENERATION_REQUEST_RETRY_OPTIONS,
},
);
}
export async function generateEditorVideo(input: EditorVideoGenerationInput) {
assertStableEditorVideoReferences(input);
return requestJson<EditorVideoGenerationResponse>(
EDITOR_VIDEO_GENERATION_API,
jsonRequest('POST', {
prompt: input.prompt,
model: input.model,
aspectRatio: input.aspectRatio,
durationSeconds: input.durationSeconds,
resolution: input.resolution,
mode: input.mode,
sound: input.sound,
webSearchEnabled: input.webSearchEnabled ?? true,
...(input.referenceImageSrcs?.length
? { referenceImageSrcs: input.referenceImageSrcs }
: {}),
...(input.referenceVideoSrcs?.length
? { referenceVideoSrcs: input.referenceVideoSrcs }
: {}),
...(input.referenceAudioSrcs?.length
? { referenceAudioSrcs: input.referenceAudioSrcs }
: {}),
...(input.projectId ? { projectId: input.projectId } : {}),
...(input.canvasCompletion
? { canvasCompletion: input.canvasCompletion }
: {}),
...(input.generationInputs
? { generationInputs: input.generationInputs }
: {}),
...(input.sourceResourceId
? { sourceResourceId: input.sourceResourceId }
: {}),
...(input.assetKind ? { assetKind: input.assetKind } : {}),
...(input.assetFolderId ? { assetFolderId: input.assetFolderId } : {}),
...(input.assetLabel ? { assetLabel: input.assetLabel } : {}),
}),
'生成视频失败',
{
timeoutMs: 1_200_000,
retry: EDITOR_GENERATION_REQUEST_RETRY_OPTIONS,
},
);
}
export async function generateEditorSoundEffect(
input: EditorSoundEffectGenerationInput,
) {
const duration = input.duration ?? null;
if (
duration !== null &&
(!Number.isFinite(duration) ||
duration < SOUND_EFFECT_DURATION_MIN_SECONDS ||
duration > SOUND_EFFECT_DURATION_MAX_SECONDS)
) {
throw new Error('游戏音效时长必须在 0.5-30 秒之间');
}
return requestJson<EditorAudioGenerationResponse>(
EDITOR_SOUND_EFFECT_GENERATION_API,
jsonRequest('POST', {
prompt: input.prompt,
model: input.model,
duration,
loop: input.loop ?? false,
...(input.projectId ? { projectId: input.projectId } : {}),
...(input.canvasCompletion
? { canvasCompletion: input.canvasCompletion }
: {}),
...(input.generationInputs
? { generationInputs: input.generationInputs }
: {}),
...(input.assetFolderId ? { assetFolderId: input.assetFolderId } : {}),
...(input.assetLabel ? { assetLabel: input.assetLabel } : {}),
}),
'生成游戏音效失败',
{
timeoutMs: 1_200_000,
},
);
}
function requestEditorBackgroundMusicPromptAssist(
path: string,
input: BackgroundMusicPromptAssistRequest,
fallbackMessage: string,
options: EditorBackgroundMusicPromptAssistOptions,
) {
return requestJson<BackgroundMusicPromptAssistResponse>(
path,
{
...jsonRequest('POST', {
currentPrompt: input.currentPrompt,
}),
signal: options.signal,
},
fallbackMessage,
{
timeoutMs: EDITOR_BACKGROUND_MUSIC_PROMPT_ASSIST_TIMEOUT_MS,
},
);
}
export function completeEditorBackgroundMusicPrompt(
input: BackgroundMusicPromptAssistRequest,
options: EditorBackgroundMusicPromptAssistOptions = {},
) {
return requestEditorBackgroundMusicPromptAssist(
EDITOR_BACKGROUND_MUSIC_PROMPT_COMPLETION_API,
input,
'AI 补全背景音乐提示词失败',
options,
);
}
export function simplifyEditorBackgroundMusicPrompt(
input: BackgroundMusicPromptAssistRequest,
options: EditorBackgroundMusicPromptAssistOptions = {},
) {
return requestEditorBackgroundMusicPromptAssist(
EDITOR_BACKGROUND_MUSIC_PROMPT_SIMPLIFICATION_API,
input,
'简化背景音乐提示词失败',
options,
);
}
export function optimizeEditorSoundEffectPrompt(
input: SoundEffectPromptOptimizeRequest,
options: EditorSoundEffectPromptOptimizeOptions = {},
) {
return requestJson<SoundEffectPromptOptimizeResponse>(
EDITOR_SOUND_EFFECT_PROMPT_OPTIMIZATION_API,
{
...jsonRequest('POST', {
currentPrompt: input.currentPrompt,
}),
signal: options.signal,
},
'优化游戏音效提示词失败',
{
timeoutMs: EDITOR_SOUND_EFFECT_PROMPT_OPTIMIZE_TIMEOUT_MS,
},
);
}
export async function generateEditorBackgroundMusic(
input: EditorBackgroundMusicGenerationInput,
) {
return requestJson<EditorAudioGenerationResponse>(
EDITOR_BACKGROUND_MUSIC_GENERATION_API,
jsonRequest('POST', {
gptDescriptionPrompt: input.gptDescriptionPrompt,
makeInstrumental: input.makeInstrumental,
...(input.projectId ? { projectId: input.projectId } : {}),
...(input.canvasCompletion
? { canvasCompletion: input.canvasCompletion }
: {}),
...(input.generationInputs
? { generationInputs: input.generationInputs }
: {}),
...(input.assetFolderId ? { assetFolderId: input.assetFolderId } : {}),
...(input.assetLabel ? { assetLabel: input.assetLabel } : {}),
}),
'生成游戏背景音乐失败',
{
timeoutMs: 1_200_000,
},
);
}