b6c5fdeb30
生成完成提示展示后后台确认,避免刷新重复弹出 项目封面改为保存并读取静态快照资源 补充封面快照上传、选择和展示测试 同步外部生成与图片画布契约文档
148 lines
4.0 KiB
TypeScript
148 lines
4.0 KiB
TypeScript
import { requestJson } from '../apiClient';
|
|
import { getSignedAssetReadUrl } from '../assetReadUrlService';
|
|
import {
|
|
EDITOR_REQUEST_RETRY_OPTIONS,
|
|
postEditorDirectUploadFile,
|
|
} from './editorRetryOptions';
|
|
|
|
export type EditorMediaAssetUploadType = 'image' | '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 UploadedEditorMediaAsset = {
|
|
src: string;
|
|
objectKey: string;
|
|
assetObjectId: string;
|
|
legacyPublicPath: string;
|
|
};
|
|
|
|
export type EditorMediaAssetUploadOptions = {
|
|
assetKind?: string;
|
|
pathSegments?: string[];
|
|
entityId?: string;
|
|
metadata?: Record<string, string>;
|
|
};
|
|
|
|
const EDITOR_MEDIA_READ_EXPIRE_SECONDS = 60 * 60;
|
|
|
|
function resolveEditorMediaAssetKind(mediaType: EditorMediaAssetUploadType) {
|
|
if (mediaType === 'video') {
|
|
return 'editor_uploaded_video';
|
|
}
|
|
if (mediaType === 'audio') {
|
|
return 'editor_uploaded_audio';
|
|
}
|
|
return 'editor_uploaded_image';
|
|
}
|
|
|
|
function resolveFallbackFileName(mediaType: EditorMediaAssetUploadType) {
|
|
if (mediaType === 'video') {
|
|
return 'editor-video.mp4';
|
|
}
|
|
if (mediaType === 'audio') {
|
|
return 'editor-audio.mp3';
|
|
}
|
|
return 'editor-image.png';
|
|
}
|
|
|
|
function sanitizeEditorMediaFileName(
|
|
fileName: string,
|
|
mediaType: EditorMediaAssetUploadType,
|
|
) {
|
|
const trimmedName = fileName.trim();
|
|
return trimmedName || resolveFallbackFileName(mediaType);
|
|
}
|
|
|
|
export async function uploadEditorMediaAssetFile(
|
|
file: File,
|
|
mediaType: EditorMediaAssetUploadType,
|
|
options: EditorMediaAssetUploadOptions = {},
|
|
): Promise<UploadedEditorMediaAsset> {
|
|
const assetKind = options.assetKind ?? resolveEditorMediaAssetKind(mediaType);
|
|
const fileName = sanitizeEditorMediaFileName(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: options.pathSegments ?? [
|
|
'editor',
|
|
'asset-library',
|
|
mediaType,
|
|
`${Date.now()}`,
|
|
],
|
|
fileName,
|
|
contentType,
|
|
access: 'private',
|
|
maxSizeBytes: file.size,
|
|
successActionStatus: 200,
|
|
metadata: {
|
|
asset_kind: assetKind,
|
|
editor_media_type: mediaType,
|
|
...options.metadata,
|
|
},
|
|
}),
|
|
},
|
|
'创建素材上传凭证失败',
|
|
{ retry: EDITOR_REQUEST_RETRY_OPTIONS },
|
|
);
|
|
|
|
await postEditorDirectUploadFile(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: options.entityId ?? `editor-asset-library-${mediaType}`,
|
|
}),
|
|
},
|
|
'确认素材资产失败',
|
|
{ retry: EDITOR_REQUEST_RETRY_OPTIONS },
|
|
);
|
|
|
|
// 中文注释:素材库立即预览使用短期 signed URL;持久化仍记录 legacy path 和 objectKey。
|
|
const signedUrl = await getSignedAssetReadUrl(
|
|
{
|
|
objectKey: confirmed.assetObject.objectKey,
|
|
expireSeconds: EDITOR_MEDIA_READ_EXPIRE_SECONDS,
|
|
},
|
|
undefined,
|
|
{ bypassCache: true },
|
|
);
|
|
|
|
return {
|
|
src: signedUrl,
|
|
objectKey: confirmed.assetObject.objectKey,
|
|
assetObjectId: confirmed.assetObject.assetObjectId,
|
|
legacyPublicPath: ticket.upload.legacyPublicPath,
|
|
};
|
|
}
|