fix: clarify upload errors and raise upload limits
This commit is contained in:
+81
-12
@@ -139,6 +139,70 @@ export class ApiError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
function formatBytes(bytes: number) {
|
||||
if (!Number.isFinite(bytes) || bytes < 0) return '0B';
|
||||
if (bytes >= 1024 * 1024) {
|
||||
const mb = bytes / 1024 / 1024;
|
||||
return `${Number.isInteger(mb) ? mb : mb.toFixed(1)}MB`;
|
||||
}
|
||||
if (bytes >= 1024) {
|
||||
const kb = bytes / 1024;
|
||||
return `${Number.isInteger(kb) ? kb : kb.toFixed(1)}KB`;
|
||||
}
|
||||
return `${bytes}B`;
|
||||
}
|
||||
|
||||
function readNumberDetail(details: ApiError['details'], key: string) {
|
||||
if (!details || typeof details !== 'object') return null;
|
||||
const value = (details as Record<string, unknown>)[key];
|
||||
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
function isImageUploadFile(file: File) {
|
||||
if (file.type.startsWith('image/')) return true;
|
||||
return /\.(png|jpe?g|webp|gif)$/i.test(file.name);
|
||||
}
|
||||
|
||||
function normalizeMindSpaceUploadError(error: unknown, file: File): Error {
|
||||
if (!(error instanceof ApiError)) {
|
||||
return error instanceof Error ? error : new Error('上传失败,请重试');
|
||||
}
|
||||
|
||||
const imageMax = formatBytes(CHAT_IMAGE_UPLOAD_MAX_INPUT_BYTES);
|
||||
if (error.code === 'quota_exceeded' || error.status === 429) {
|
||||
const requiredBytes = readNumberDetail(error.details, 'requiredBytes');
|
||||
const availableBytes = readNumberDetail(error.details, 'availableBytes');
|
||||
const detail =
|
||||
requiredBytes !== null && availableBytes !== null
|
||||
? `当前剩余 ${formatBytes(availableBytes)},本次需要 ${formatBytes(requiredBytes)}。`
|
||||
: '';
|
||||
return new ApiError(
|
||||
error.status,
|
||||
`剩余空间不足,${detail}请减少图片数量或压缩后重试。`,
|
||||
error.code,
|
||||
error.details,
|
||||
);
|
||||
}
|
||||
|
||||
if (error.code === 'file_too_large' || error.status === 413) {
|
||||
const message = isImageUploadFile(file)
|
||||
? `图片文件过大,单张图片不能超过 ${imageMax},请压缩后重试。`
|
||||
: `文件过大,请压缩到单文件上限以内后重试。`;
|
||||
return new ApiError(error.status, message, error.code, error.details);
|
||||
}
|
||||
|
||||
if (/MindSpace\s*服务异常/.test(error.message) || error.code === 'internal_error') {
|
||||
return new ApiError(
|
||||
error.status,
|
||||
`上传失败,请减少图片数量或压缩图片后重试;单张图片上限 ${imageMax}。`,
|
||||
error.code,
|
||||
error.details,
|
||||
);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
function sanitizeUserFacingErrorMessage(message: string) {
|
||||
const normalized = String(message ?? '').trim();
|
||||
if (!normalized) return normalized;
|
||||
@@ -722,17 +786,22 @@ export async function uploadMindSpaceAsset(
|
||||
);
|
||||
}
|
||||
|
||||
const created = await apiFetch<{ data: MindSpaceUpload }>('/mindspace/v1/uploads', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
category_id: categoryId,
|
||||
filename: file.name,
|
||||
size_bytes: file.size,
|
||||
declared_mime_type: file.type || null,
|
||||
...(options.sessionId ? { session_id: options.sessionId } : {}),
|
||||
...(options.messageId ? { message_id: options.messageId } : {}),
|
||||
}),
|
||||
});
|
||||
let created: { data: MindSpaceUpload };
|
||||
try {
|
||||
created = await apiFetch<{ data: MindSpaceUpload }>('/mindspace/v1/uploads', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
category_id: categoryId,
|
||||
filename: file.name,
|
||||
size_bytes: file.size,
|
||||
declared_mime_type: file.type || null,
|
||||
...(options.sessionId ? { session_id: options.sessionId } : {}),
|
||||
...(options.messageId ? { message_id: options.messageId } : {}),
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
throw normalizeMindSpaceUploadError(error, file);
|
||||
}
|
||||
|
||||
try {
|
||||
await uploadFileContent(created.data.uploadUrl, file, options.onProgress);
|
||||
@@ -745,7 +814,7 @@ export async function uploadMindSpaceAsset(
|
||||
await apiFetch(`/mindspace/v1/uploads/${created.data.id}`, {
|
||||
method: 'DELETE',
|
||||
}).catch(() => {});
|
||||
throw error;
|
||||
throw normalizeMindSpaceUploadError(error, file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ function sortPagesByCreatedAt(pages: MindSpacePage[]) {
|
||||
});
|
||||
}
|
||||
const IMAGE_PAGE_SIZE = 10;
|
||||
const DEFAULT_MAX_UPLOAD_FILE_BYTES = 5 * 1024 * 1024;
|
||||
const DEFAULT_MAX_UPLOAD_FILE_BYTES = 30 * 1024 * 1024;
|
||||
const UPLOAD_FILE_EXTENSIONS = [
|
||||
'.doc',
|
||||
'.docx',
|
||||
@@ -361,7 +361,7 @@ function validateSelectedUploadFile(file: File, maxBytes: number) {
|
||||
return `暂不支持 ${extension || '无扩展名'} 文件,请选择支持的资料格式。`;
|
||||
}
|
||||
const effectiveMaxBytes = isUploadImageFile(file)
|
||||
? Math.min(maxBytes, MINDSPACE_IMAGE_UPLOAD_MAX_BYTES)
|
||||
? MINDSPACE_IMAGE_UPLOAD_MAX_BYTES
|
||||
: maxBytes;
|
||||
if (file.size > effectiveMaxBytes) {
|
||||
return `文件不能超过 ${formatBytes(effectiveMaxBytes)},当前为 ${formatBytes(file.size)}。`;
|
||||
@@ -2118,7 +2118,8 @@ export function MindSpaceView({
|
||||
</>
|
||||
)}
|
||||
剩余 {formatBytes(space.quota.availableBytes)} ·{' '}
|
||||
单文件上限 {formatBytes(space.quota.maxFileBytes)} · 可发布页面{' '}
|
||||
文档上限 {formatBytes(space.quota.maxFileBytes)} · 图片上限{' '}
|
||||
{formatBytes(MINDSPACE_IMAGE_UPLOAD_MAX_BYTES)} · 可发布页面{' '}
|
||||
{space.quota.publicPageUsed}/{space.quota.publicPageLimit} · 今日 AI{' '}
|
||||
{space.quota.aiDailyUsed}/{space.quota.aiDailyLimit}
|
||||
</p>
|
||||
@@ -3089,7 +3090,7 @@ export function MindSpaceView({
|
||||
</div>
|
||||
<p className="mindspace-upload-dialog-desc">
|
||||
支持 {UPLOAD_FILE_TYPE_LABEL},文档最大 {formatBytes(maxUploadFileBytes)},图片最大{' '}
|
||||
{formatBytes(Math.min(maxUploadFileBytes, MINDSPACE_IMAGE_UPLOAD_MAX_BYTES))}。
|
||||
{formatBytes(MINDSPACE_IMAGE_UPLOAD_MAX_BYTES)}。
|
||||
</p>
|
||||
<input
|
||||
type="file"
|
||||
|
||||
@@ -25,7 +25,7 @@ export const PREVIEW_SPACE: MindSpace = {
|
||||
usedBytes: 1.2 * 1024 * 1024,
|
||||
reservedBytes: 0,
|
||||
availableBytes: 3.8 * 1024 * 1024,
|
||||
maxFileBytes: 5 * 1024 * 1024,
|
||||
maxFileBytes: 30 * 1024 * 1024,
|
||||
publicPageLimit: 3,
|
||||
publicPageUsed: 1,
|
||||
aiDailyLimit: 20,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export const CHAT_IMAGE_UPLOAD_MAX_INPUT_BYTES = 4 * 1024 * 1024;
|
||||
export const CHAT_IMAGE_UPLOAD_MAX_INPUT_BYTES = 10 * 1024 * 1024;
|
||||
export const CHAT_IMAGE_UPLOAD_MAX_OUTPUT_BYTES = 1.5 * 1024 * 1024;
|
||||
export const MINDSPACE_IMAGE_UPLOAD_MAX_BYTES = 4 * 1024 * 1024;
|
||||
export const MINDSPACE_IMAGE_UPLOAD_MAX_BYTES = 10 * 1024 * 1024;
|
||||
export const CHAT_IMAGE_UPLOAD_MAX_COUNT = 10;
|
||||
export const CHAT_IMAGE_UPLOAD_MAX_TOTAL_BYTES = 20 * 1024 * 1024;
|
||||
export const CHAT_IMAGE_UPLOAD_MAX_TOTAL_BYTES = 30 * 1024 * 1024;
|
||||
export const CHAT_IMAGE_MAX_SIDE = 1920;
|
||||
const MAX_PIXELS = 2_500_000;
|
||||
const ACCEPTED_IMAGE_MIME = new Set(['image/jpeg', 'image/png', 'image/webp']);
|
||||
|
||||
Reference in New Issue
Block a user