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 & { 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; }; export type EditorGenerationPricingConfig = { models: Record; }; 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) { 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 { 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( url, { method: 'GET' }, '读取陶泥儿精选素材失败', ) : await requestJson( 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( `${EDITOR_PROJECT_API_BASE}/recent`, { method: 'GET' }, '读取图片画布工程失败', ); } export async function createEditorProject( input: EditorProjectCreateInput = {}, ) { const response = await requestJson( 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( 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( `${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( `${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( `${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( `${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( `${EDITOR_PROJECT_RESOURCE_API_BASE}/${encodeURIComponent(resourceId)}/showcase`, jsonRequest('PATCH', { publicShowcaseEnabled }), '更新素材公开展示状态失败', ); return response.resource; } export async function loadEditorAssetLibrary() { const response = await requestJson( `${EDITOR_ASSET_API_BASE}/library`, { method: 'GET' }, '读取图片画布素材库失败', ); return response.library; } export async function createEditorAssetFolder( label: string, sortOrder?: number, ) { const response = await requestJson( `${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( `${EDITOR_ASSET_API_BASE}/folders/${encodeURIComponent(folderId)}`, jsonRequest('PATCH', input), '更新图片画布素材文件夹失败', ); return response.folder; } export async function deleteEditorAssetFolder(folderId: string) { const response = await requestJson( `${EDITOR_ASSET_API_BASE}/folders/${encodeURIComponent(folderId)}`, { method: 'DELETE' }, '删除图片画布素材文件夹失败', ); return response.library; } export async function createEditorAsset(input: EditorAssetCreateInput) { const response = await requestJson( EDITOR_ASSET_API_BASE, jsonRequest('POST', input), '创建图片画布素材失败', ); return response.asset; } export async function updateEditorAsset( assetId: string, input: EditorAssetUpdateInput, ) { const response = await requestJson( `${EDITOR_ASSET_API_BASE}/${encodeURIComponent(assetId)}`, jsonRequest('PATCH', input), '更新图片画布素材失败', ); return response.asset; } export async function deleteEditorAsset(assetId: string) { const response = await requestJson( `${EDITOR_ASSET_API_BASE}/${encodeURIComponent(assetId)}`, { method: 'DELETE' }, '删除图片画布素材失败', ); return response.asset; } export async function submitEditorAssetShowcase(assetId: string) { const response = await requestJson( `${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( `${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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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, }, ); }