ac180ecad9
新增画板底部生成音乐入口,支持游戏音效和游戏背景音乐生成面板。 接入编辑器音频生成前后端契约、VectorEngine 请求与 BFF 路由。 补齐音频结果图层、元数据、画布恢复和客户端提交流程。 统一生成类面板 Lovart 式参数浮层、参考素材入口与相关测试。 更新编辑器音乐生成设计文档和项目长期记忆。
140 lines
3.9 KiB
TypeScript
140 lines
3.9 KiB
TypeScript
import { requestJson } from '../apiClient';
|
|
import { getSignedAssetReadUrl } from '../assetReadUrlService';
|
|
|
|
type EditorSeedanceReferenceMediaType = 'video' | 'audio';
|
|
|
|
type DirectUploadTicketResponse = {
|
|
upload: {
|
|
bucket: string;
|
|
host: string;
|
|
objectKey: string;
|
|
legacyPublicPath: string;
|
|
formFields: Record<string, string | null | undefined>;
|
|
};
|
|
};
|
|
|
|
type ConfirmAssetObjectResponse = {
|
|
assetObject: {
|
|
assetObjectId: string;
|
|
objectKey: string;
|
|
assetKind: string;
|
|
};
|
|
};
|
|
|
|
export type UploadedEditorSeedanceReference = {
|
|
src: string;
|
|
objectKey: string;
|
|
assetObjectId: string;
|
|
legacyPublicPath: string;
|
|
};
|
|
|
|
const EDITOR_SEEDANCE_REFERENCE_READ_EXPIRE_SECONDS = 60 * 60;
|
|
|
|
function resolveReferenceAssetKind(mediaType: EditorSeedanceReferenceMediaType) {
|
|
return mediaType === 'video'
|
|
? 'editor_seedance_reference_video'
|
|
: 'editor_seedance_reference_audio';
|
|
}
|
|
|
|
function resolveReferenceEntityId(mediaType: EditorSeedanceReferenceMediaType) {
|
|
return `editor-seedance-reference-${mediaType}`;
|
|
}
|
|
|
|
function sanitizeReferenceFileName(fileName: string, mediaType: EditorSeedanceReferenceMediaType) {
|
|
const trimmedName = fileName.trim();
|
|
if (trimmedName) {
|
|
return trimmedName;
|
|
}
|
|
return mediaType === 'video' ? 'seedance-reference.mp4' : 'seedance-reference.mp3';
|
|
}
|
|
|
|
async function postDirectUploadFile(
|
|
upload: DirectUploadTicketResponse['upload'],
|
|
file: File,
|
|
) {
|
|
const formData = new FormData();
|
|
Object.entries(upload.formFields).forEach(([key, value]) => {
|
|
if (value !== null && value !== undefined) {
|
|
formData.append(key, value);
|
|
}
|
|
});
|
|
formData.append('file', file, file.name);
|
|
|
|
const response = await fetch(upload.host, {
|
|
method: 'POST',
|
|
body: formData,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('上传参考素材失败');
|
|
}
|
|
}
|
|
|
|
export async function uploadEditorSeedanceReferenceFile(
|
|
file: File,
|
|
mediaType: EditorSeedanceReferenceMediaType,
|
|
): Promise<UploadedEditorSeedanceReference> {
|
|
const assetKind = resolveReferenceAssetKind(mediaType);
|
|
const fileName = sanitizeReferenceFileName(file.name, mediaType);
|
|
const contentType = file.type.trim() || 'application/octet-stream';
|
|
|
|
const ticket = await requestJson<DirectUploadTicketResponse>(
|
|
'/api/assets/direct-upload-tickets',
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
legacyPrefix: 'generated-character-drafts',
|
|
pathSegments: ['editor', 'seedance-references', mediaType, `${Date.now()}`],
|
|
fileName,
|
|
contentType,
|
|
access: 'private',
|
|
maxSizeBytes: file.size,
|
|
successActionStatus: 200,
|
|
metadata: {
|
|
asset_kind: assetKind,
|
|
editor_seedance_reference_type: mediaType,
|
|
},
|
|
}),
|
|
},
|
|
'创建参考素材上传凭证失败',
|
|
);
|
|
|
|
await postDirectUploadFile(ticket.upload, file);
|
|
|
|
const confirmed = await requestJson<ConfirmAssetObjectResponse>(
|
|
'/api/assets/objects/confirm',
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
bucket: ticket.upload.bucket,
|
|
objectKey: ticket.upload.objectKey,
|
|
contentType,
|
|
contentLength: file.size,
|
|
assetKind,
|
|
accessPolicy: 'private',
|
|
entityId: resolveReferenceEntityId(mediaType),
|
|
}),
|
|
},
|
|
'确认参考素材资产失败',
|
|
);
|
|
|
|
// 中文注释:返回短期 signed URL 供前端立即预览;生成请求最终优先提交 objectKey,由后端统一重新签名。
|
|
const signedUrl = await getSignedAssetReadUrl(
|
|
{
|
|
objectKey: confirmed.assetObject.objectKey,
|
|
expireSeconds: EDITOR_SEEDANCE_REFERENCE_READ_EXPIRE_SECONDS,
|
|
},
|
|
undefined,
|
|
{ bypassCache: true },
|
|
);
|
|
|
|
return {
|
|
src: signedUrl,
|
|
objectKey: confirmed.assetObject.objectKey,
|
|
assetObjectId: confirmed.assetObject.assetObjectId,
|
|
legacyPublicPath: ticket.upload.legacyPublicPath,
|
|
};
|
|
}
|