61fe4c48af
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 16s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust smoke (pull_request) Failing after 16s
Project CI / Backend tests (pull_request) Failing after 16s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 16s
Project CI / Native shell tests (pull_request) Failing after 16s
Project CI / Frontend tests (pull_request) Failing after 7s
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / AI game creator shell web tests (pull_request) Failing after 12s
- 后端资源投影按 asset_kind 分叉,新增 project_editor_client_media_src 与 EDITOR_MODEL3D_ASSET_KIND:model3d 只投影预览图,模型本体只留 objectKey,预览缺失才回落历史口径 - tripo3d 存储新增按字节魔数识别模型 content_type 与扩展名映射,不采信 provider 声明与下载响应头 - editor_project_storage 把 model3d 加入 EDITOR_CANVAS_ASSET_KINDS 白名单 - 前端 CanvasAssetKind 与 image-canvas-core 类型新增 model3d:画布卡片渲染预览图或 3D 占位,类型角标新增「3D模型」,覆盖资格只在它与空类别之间成立 - 新增 model3d-preview 子目录(Model3dViewerModal / Model3dViewerModel),Tailwind 内联、不新增 CSS,失败原因分超限 / 格式不可识别 / 无 WebGL2 三类展示 - 选中工具条新增只读 preview-model3d 能力位,仅 objectKey 为空时置灰;打开预览同时选中图层并暂停画布快捷键与舞台交互 - 查看器包源对象 format 改为可选,新增「声明 Content-Type → 字节魔数 → 地址扩展名」三级格式判定并导出支持格式清单 - 导出工作流新增 3d_models/ 目录,模型下载按真实响应头 MIME 定扩展名、对象键扩展名兜底 - 新增 3D 生成入口:底部工具栏浮动子选项与文生 / 图生 3D 面板,落 projectResource + canvasCompletion,复用占位框与任务侧栏 - 3D 定价只读实时定价查询,段缺失即入口不可提交;幂等键由前端铸造,用户重试必须换键 - 新增 ADR 0003 / 0004 与实施计划、里程碑文档;更新 Tripo 技术方案、后端架构、CONTEXT 术语、decision-log 与 pitfalls - .gitignore 忽略仓库根目录手工下载的 3D 样例模型
1786 lines
56 KiB
TypeScript
1786 lines
56 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,不写回候选。
|
||
*/
|
||
/** 3D 提交只做校验与入队,毫秒级返回,不沿用生成链路的分钟级超时。 */
|
||
export const EDITOR_MODEL3D_SUBMISSION_TIMEOUT_MS = 60_000;
|
||
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_MODEL3D_TEXT_TO_MODEL_API = '/api/assets/tripo/text-to-model';
|
||
const EDITOR_MODEL3D_IMAGE_TO_MODEL_API = '/api/assets/tripo/image-to-model';
|
||
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 Model3dPricingEndpoint = 'text-to-model' | 'image-to-model';
|
||
|
||
export type Model3dAddOnPriceKey =
|
||
| 'hdTexture'
|
||
| 'ultraTexture'
|
||
| 'hdGeometry'
|
||
| 'quadMesh'
|
||
| 'smartLowPoly'
|
||
| 'generateParts';
|
||
|
||
/**
|
||
* 3D 生成的定价段:底价按 `endpoint × modelVersion × 是否有贴图` 拆分,
|
||
* add-on 按请求参数判定后叠加。整段缺失即 3D 生成不可用(后端 fail closed),
|
||
* 前端不内置任何兜底数值。
|
||
*/
|
||
export type Model3dPricingConfig = {
|
||
basePrices?: Record<
|
||
Model3dPricingEndpoint,
|
||
Record<string, { noTexture: number; texture: number }>
|
||
> | null;
|
||
addOnPrices?: Partial<Record<Model3dAddOnPriceKey, number>> | null;
|
||
};
|
||
|
||
export type EditorGenerationPricingConfig = {
|
||
models: Record<string, EditorGenerationModelPricing>;
|
||
model3d?: Model3dPricingConfig | null;
|
||
};
|
||
|
||
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 type Model3dGenerationSubmissionResponse = {
|
||
operationId: string;
|
||
jobKind: string;
|
||
status: ExternalGenerationJobStatusRecord;
|
||
statusUrl: string;
|
||
};
|
||
|
||
/**
|
||
* 3D 生成提交:两个端点都要求 `Idempotency-Key`,键由调用方铸造并与当次请求内容绑定。
|
||
* 这里是画布链路里唯一发送该请求头的生成入口。
|
||
*/
|
||
export async function submitModel3dGenerationRequest({
|
||
endpoint,
|
||
body,
|
||
idempotencyKey,
|
||
}: {
|
||
endpoint: Model3dPricingEndpoint;
|
||
body: Record<string, unknown>;
|
||
idempotencyKey: string;
|
||
}) {
|
||
return requestJson<Model3dGenerationSubmissionResponse>(
|
||
endpoint === 'image-to-model'
|
||
? EDITOR_MODEL3D_IMAGE_TO_MODEL_API
|
||
: EDITOR_MODEL3D_TEXT_TO_MODEL_API,
|
||
{
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Idempotency-Key': idempotencyKey,
|
||
},
|
||
body: JSON.stringify(body),
|
||
},
|
||
'提交 3D 模型生成失败',
|
||
{
|
||
timeoutMs: EDITOR_MODEL3D_SUBMISSION_TIMEOUT_MS,
|
||
...EDITOR_GENERATION_REQUEST_RETRY_OPTIONS,
|
||
},
|
||
);
|
||
}
|
||
|
||
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,
|
||
},
|
||
);
|
||
}
|