feat(web-client): add per-type file size validation on upload

Enforce Claude API file size limits at upload time with user-friendly
error messages:
- Images: max 5MB (Claude API hard limit)
- PDF: max 25MB (32MB request limit minus headroom)
- Other documents: max 50MB (general upload limit)

Replaced duplicate ALLOWED_TYPES/MAX_FILE_SIZE in InputArea with shared
validateFile() from fileService, showing alert() on rejection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-07 04:57:54 -08:00
parent 5338bdfc0f
commit 470ec9a64e
2 changed files with 36 additions and 35 deletions

View File

@ -2,6 +2,7 @@ import { useState, useRef, useEffect, KeyboardEvent, ChangeEvent, ClipboardEvent
import { Send, Paperclip, X, Image, FileText, Loader2, Upload } from 'lucide-react'; import { Send, Paperclip, X, Image, FileText, Loader2, Upload } from 'lucide-react';
import { clsx } from 'clsx'; import { clsx } from 'clsx';
import { FileAttachment } from '../stores/chatStore'; import { FileAttachment } from '../stores/chatStore';
import { validateFile, ALLOWED_FILE_TYPES } from '@/shared/services/fileService';
interface PendingFile { interface PendingFile {
id: string; id: string;
@ -20,25 +21,6 @@ interface InputAreaProps {
uploadProgress?: Record<string, { progress: number; status: string }>; uploadProgress?: Record<string, { progress: number; status: string }>;
} }
// 允许上传的文件类型
const ALLOWED_TYPES = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'image/svg+xml',
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'text/plain',
'text/csv',
'text/markdown',
];
const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
export function InputArea({ export function InputArea({
onSend, onSend,
disabled, disabled,
@ -89,12 +71,9 @@ export function InputArea({
const validFiles: File[] = []; const validFiles: File[] = [];
Array.from(files).forEach((file) => { Array.from(files).forEach((file) => {
if (!ALLOWED_TYPES.includes(file.type)) { const result = validateFile(file);
console.warn(`不支持的文件类型: ${file.type}`); if (!result.valid) {
return; alert(result.error);
}
if (file.size > MAX_FILE_SIZE) {
console.warn(`文件过大: ${file.name}`);
return; return;
} }
validFiles.push(file); validFiles.push(file);
@ -116,12 +95,9 @@ export function InputArea({
const processFiles = (files: File[]) => { const processFiles = (files: File[]) => {
const validFiles: File[] = []; const validFiles: File[] = [];
files.forEach((file) => { files.forEach((file) => {
if (!ALLOWED_TYPES.includes(file.type)) { const result = validateFile(file);
console.warn(`不支持的文件类型: ${file.type}`); if (!result.valid) {
return; alert(result.error);
}
if (file.size > MAX_FILE_SIZE) {
console.warn(`文件过大: ${file.name}`);
return; return;
} }
validFiles.push(file); validFiles.push(file);
@ -301,7 +277,7 @@ export function InputArea({
ref={fileInputRef} ref={fileInputRef}
type="file" type="file"
multiple multiple
accept={ALLOWED_TYPES.join(',')} accept={[...ALLOWED_FILE_TYPES.image, ...ALLOWED_FILE_TYPES.document].join(',')}
onChange={handleFileSelect} onChange={handleFileSelect}
className="hidden" className="hidden"
/> />

View File

@ -45,9 +45,18 @@ export const ALLOWED_FILE_TYPES = {
], ],
}; };
export const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
export const MAX_DIRECT_UPLOAD_SIZE = 10 * 1024 * 1024; // 10MB export const MAX_DIRECT_UPLOAD_SIZE = 10 * 1024 * 1024; // 10MB
/** Claude API 文件大小限制(按类型区分) */
export const FILE_SIZE_LIMITS = {
/** 图片: Claude API 硬限制 5MB */
image: 5 * 1024 * 1024,
/** PDF: Claude API 整个请求 32MB 限制,单文件留 25MB */
pdf: 25 * 1024 * 1024,
/** 其他文档Word/Excel/Text 等): 通用 50MB 上传限制 */
document: 50 * 1024 * 1024,
} as const;
/** /**
* *
*/ */
@ -61,10 +70,26 @@ export function validateFile(file: File): { valid: boolean; error?: string } {
}; };
} }
if (file.size > MAX_FILE_SIZE) { // 按文件类型检查大小限制
const fileType = getFileType(file.type);
let maxSize: number;
let typeLabel: string;
if (fileType === 'image') {
maxSize = FILE_SIZE_LIMITS.image;
typeLabel = '图片';
} else if (file.type === 'application/pdf') {
maxSize = FILE_SIZE_LIMITS.pdf;
typeLabel = 'PDF';
} else {
maxSize = FILE_SIZE_LIMITS.document;
typeLabel = '文档';
}
if (file.size > maxSize) {
return { return {
valid: false, valid: false,
error: `文件大小超过限制。最大允许: ${MAX_FILE_SIZE / 1024 / 1024}MB`, error: `${typeLabel}大小超过限制 (${(file.size / 1024 / 1024).toFixed(1)}MB)。${typeLabel}最大允许: ${maxSize / 1024 / 1024}MB`,
}; };
} }