完善 AI 设计资产预览与视频首帧选择
需求:生成图片需要支持大图查看和本地下载;制作视频时需要从当前项目作品选择首帧,或上传本地图片。 实现:新增首帧选择弹窗、JPEG/PNG/WebP 有界上传、附件 Asset ID 透传、Main 到服务端 multipart 转发,并保留上传失败重试与私有图片下载链路。 验证:相关 57 项单测、TypeScript 类型检查、目标 ESLint 和 Vite 生产构建全部通过。
This commit is contained in:
@@ -9,6 +9,9 @@ import {
|
||||
IMAGE_WORKSPACE_API_PATH,
|
||||
IMAGE_WORKSPACE_UNAVAILABLE_CODE,
|
||||
IMAGE_WORKSPACE_UNAVAILABLE_MESSAGE,
|
||||
designAssetDownloadPath,
|
||||
type DesignAsset,
|
||||
type DesignAssetSaveResult,
|
||||
type DesignGenerationTask,
|
||||
type DesignWorkspace,
|
||||
type DesignWorkspaceBootstrap,
|
||||
@@ -125,6 +128,7 @@ export function sendImageWorkspaceMessage(
|
||||
expectedTurnRevision: number,
|
||||
message: string,
|
||||
clientTurnId = createImageWorkspaceTurnId(),
|
||||
attachmentAssetIds: string[] = [],
|
||||
): Promise<DesignWorkspace> {
|
||||
return requestData(
|
||||
`${IMAGE_WORKSPACE_API_PATH}/workspaces/${encodeURIComponent(workspaceId)}/messages`,
|
||||
@@ -134,7 +138,7 @@ export function sendImageWorkspaceMessage(
|
||||
clientTurnId,
|
||||
expectedTurnRevision,
|
||||
message: message.trim(),
|
||||
attachmentAssetIds: [],
|
||||
attachmentAssetIds,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -178,3 +182,82 @@ export async function resolveImageWorkspaceAssetUrl(contentPath: string): Promis
|
||||
const separator = contentPath.includes('?') ? '&' : '?';
|
||||
return `${getHostApiBase()}${contentPath}${separator}token=${encodeURIComponent(token)}`;
|
||||
}
|
||||
|
||||
const IMAGE_FILE_EXTENSIONS: Record<string, string> = {
|
||||
'image/gif': 'gif',
|
||||
'image/jpeg': 'jpg',
|
||||
'image/png': 'png',
|
||||
'image/svg+xml': 'svg',
|
||||
'image/webp': 'webp',
|
||||
};
|
||||
|
||||
function assetDownloadFileName(asset: DesignAsset): string {
|
||||
const safeAssetId = asset.assetId
|
||||
.replace(/[^a-zA-Z0-9_-]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.slice(0, 32) || 'image';
|
||||
const extension = IMAGE_FILE_EXTENSIONS[asset.mimeType.toLowerCase()] ?? 'png';
|
||||
return `Makelore-AI-Design-${safeAssetId}.${extension}`;
|
||||
}
|
||||
|
||||
export function saveImageWorkspaceAsset(asset: DesignAsset): Promise<DesignAssetSaveResult> {
|
||||
return requestData(
|
||||
designAssetDownloadPath(asset.workspaceId, asset.assetId),
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ defaultFileName: assetDownloadFileName(asset) }),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const DESIGN_IMAGE_UPLOAD_MIME_TYPES = new Set([
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/webp',
|
||||
]);
|
||||
const MAX_DESIGN_IMAGE_UPLOAD_BYTES = 10 * 1024 * 1024;
|
||||
|
||||
async function fileToBase64(file: File): Promise<string> {
|
||||
const bytes = new Uint8Array(await file.arrayBuffer());
|
||||
let encoded = '';
|
||||
for (let offset = 0; offset < bytes.length; offset += 48 * 1024) {
|
||||
const chunk = bytes.subarray(offset, offset + 48 * 1024);
|
||||
let binary = '';
|
||||
for (let index = 0; index < chunk.length; index += 0x8000) {
|
||||
binary += String.fromCharCode(...chunk.subarray(index, index + 0x8000));
|
||||
}
|
||||
encoded += btoa(binary);
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
export async function uploadImageWorkspaceAsset(
|
||||
workspaceId: string,
|
||||
file: File,
|
||||
): Promise<DesignAsset> {
|
||||
if (!DESIGN_IMAGE_UPLOAD_MIME_TYPES.has(file.type)) {
|
||||
throw new ImageWorkspaceApiError(
|
||||
400,
|
||||
'design_asset_upload_type_invalid',
|
||||
'请选择 JPEG、PNG 或 WebP 图片',
|
||||
);
|
||||
}
|
||||
if (file.size <= 0 || file.size > MAX_DESIGN_IMAGE_UPLOAD_BYTES) {
|
||||
throw new ImageWorkspaceApiError(
|
||||
413,
|
||||
'design_asset_upload_too_large',
|
||||
'图片大小需在 10 MB 以内',
|
||||
);
|
||||
}
|
||||
return requestData(
|
||||
`${IMAGE_WORKSPACE_API_PATH}/workspaces/${encodeURIComponent(workspaceId)}/assets`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
fileName: file.name,
|
||||
mimeType: file.type,
|
||||
dataBase64: await fileToBase64(file),
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user