diff --git a/surfsense_web/components/assistant-ui/document-upload-popup.tsx b/surfsense_web/components/assistant-ui/document-upload-popup.tsx index 29f633ebf..73d34de71 100644 --- a/surfsense_web/components/assistant-ui/document-upload-popup.tsx +++ b/surfsense_web/components/assistant-ui/document-upload-popup.tsx @@ -96,35 +96,35 @@ const DocumentUploadPopupContent: FC<{ return ( - + Upload Document - {/* Fixed Header */} -
- {/* Upload header */} -
-
- -
-
-

Upload Documents

-

- Upload and sync your documents to your search space -

+ {/* Scrollable container for mobile */} +
+ {/* Header - scrolls with content on mobile */} +
+ {/* Upload header */} +
+
+ +
+
+

Upload Documents

+

+ Upload and sync your documents to your search space +

+
+ + {/* Content */} +
+ +
- {/* Scrollable Content */} -
-
-
- -
-
- {/* Bottom fade shadow */} -
-
+ {/* Bottom fade shadow - hidden on very small screens */} +
); diff --git a/surfsense_web/components/sources/DocumentUploadTab.tsx b/surfsense_web/components/sources/DocumentUploadTab.tsx index 0b7f7b51f..82f4b161c 100644 --- a/surfsense_web/components/sources/DocumentUploadTab.tsx +++ b/surfsense_web/components/sources/DocumentUploadTab.tsx @@ -110,6 +110,11 @@ const FILE_TYPE_CONFIG: Record> = { const cardClass = "border border-border bg-slate-400/5 dark:bg-white/5"; +// Upload limits +const MAX_FILES = 10; +const MAX_TOTAL_SIZE_MB = 200; +const MAX_TOTAL_SIZE_BYTES = MAX_TOTAL_SIZE_MB * 1024 * 1024; + export function DocumentUploadTab({ searchSpaceId, onSuccess, @@ -135,14 +140,36 @@ export function DocumentUploadTab({ ); const onDrop = useCallback((acceptedFiles: File[]) => { - setFiles((prev) => [...prev, ...acceptedFiles]); - }, []); + setFiles((prev) => { + const newFiles = [...prev, ...acceptedFiles]; + + // Check file count limit + if (newFiles.length > MAX_FILES) { + toast.error(t("max_files_exceeded"), { + description: t("max_files_exceeded_desc", { max: MAX_FILES }), + }); + return prev; + } + + // Check total size limit + const newTotalSize = newFiles.reduce((sum, file) => sum + file.size, 0); + if (newTotalSize > MAX_TOTAL_SIZE_BYTES) { + toast.error(t("max_size_exceeded"), { + description: t("max_size_exceeded_desc", { max: MAX_TOTAL_SIZE_MB }), + }); + return prev; + } + + return newFiles; + }); + }, [t]); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: acceptedFileTypes, - maxSize: 50 * 1024 * 1024, + maxSize: 50 * 1024 * 1024, // 50MB per file noClick: false, + disabled: files.length >= MAX_FILES, }); // Handle file input click to prevent event bubbling that might reopen dialog @@ -160,6 +187,12 @@ export function DocumentUploadTab({ const totalFileSize = files.reduce((total, file) => total + file.size, 0); + // Check if limits are reached + const isFileCountLimitReached = files.length >= MAX_FILES; + const isSizeLimitReached = totalFileSize >= MAX_TOTAL_SIZE_BYTES; + const remainingFiles = MAX_FILES - files.length; + const remainingSizeMB = Math.max(0, (MAX_TOTAL_SIZE_BYTES - totalFileSize) / (1024 * 1024)).toFixed(1); + // Track accordion state changes const handleAccordionChange = useCallback( (value: string) => { @@ -210,7 +243,7 @@ export function DocumentUploadTab({ - {t("file_size_limit")} + {t("file_size_limit")} {t("upload_limits", { maxFiles: MAX_FILES, maxSizeMB: MAX_TOTAL_SIZE_MB })} @@ -221,7 +254,11 @@ export function DocumentUploadTab({
- {isDragActive ? ( + {isFileCountLimitReached ? ( +
+ +
+

{t("file_limit_reached")}

+

+ {t("file_limit_reached_desc", { max: MAX_FILES })} +

+
+
+ ) : isDragActive ? ( {t("drag_drop")}

{t("or_browse")}

+ {files.length > 0 && ( +

+ {t("remaining_capacity", { files: remainingFiles, sizeMB: remainingSizeMB })} +

+ )} + + )} + {!isFileCountLimitReached && ( +
+
)} -
- -
diff --git a/surfsense_web/messages/en.json b/surfsense_web/messages/en.json index 5159d4df0..b6eaf8824 100644 --- a/surfsense_web/messages/en.json +++ b/surfsense_web/messages/en.json @@ -378,6 +378,7 @@ "title": "Upload Documents", "subtitle": "Upload your files to make them searchable and accessible through AI-powered conversations.", "file_size_limit": "Maximum file size: 50MB per file. Supported formats vary based on your ETL service configuration.", + "upload_limits": "Upload limit: {maxFiles} files, {maxSizeMB}MB total.", "drop_files": "Drop files here", "drag_drop": "Drag & drop files here", "or_browse": "or click to browse", @@ -393,7 +394,14 @@ "upload_error": "Upload Error", "upload_error_desc": "Error uploading files", "supported_file_types": "Supported File Types", - "file_types_desc": "These file types are supported based on your current ETL service configuration." + "file_types_desc": "These file types are supported based on your current ETL service configuration.", + "max_files_exceeded": "File Limit Exceeded", + "max_files_exceeded_desc": "You can upload a maximum of {max} files at a time.", + "max_size_exceeded": "Size Limit Exceeded", + "max_size_exceeded_desc": "Total file size cannot exceed {max}MB.", + "file_limit_reached": "Maximum Files Reached", + "file_limit_reached_desc": "Remove some files to add more (max {max} files).", + "remaining_capacity": "{files} files remaining • {sizeMB}MB available" }, "add_webpage": { "title": "Add Webpages for Crawling", diff --git a/surfsense_web/messages/zh.json b/surfsense_web/messages/zh.json index 1404c176f..b48e3e9c7 100644 --- a/surfsense_web/messages/zh.json +++ b/surfsense_web/messages/zh.json @@ -363,6 +363,7 @@ "title": "上传文档", "subtitle": "上传您的文件,使其可通过 AI 对话进行搜索和访问。", "file_size_limit": "最大文件大小:每个文件 50MB。支持的格式因您的 ETL 服务配置而异。", + "upload_limits": "上传限制:最多 {maxFiles} 个文件,总大小不超过 {maxSizeMB}MB。", "drop_files": "放下文件到这里", "drag_drop": "拖放文件到这里", "or_browse": "或点击浏览", @@ -378,7 +379,14 @@ "upload_error": "上传错误", "upload_error_desc": "上传文件时出错", "supported_file_types": "支持的文件类型", - "file_types_desc": "根据您当前的 ETL 服务配置支持这些文件类型。" + "file_types_desc": "根据您当前的 ETL 服务配置支持这些文件类型。", + "max_files_exceeded": "超过文件数量限制", + "max_files_exceeded_desc": "一次最多只能上传 {max} 个文件。", + "max_size_exceeded": "超过文件大小限制", + "max_size_exceeded_desc": "文件总大小不能超过 {max}MB。", + "file_limit_reached": "已达到最大文件数量", + "file_limit_reached_desc": "移除一些文件以添加更多(最多 {max} 个文件)。", + "remaining_capacity": "剩余 {files} 个文件名额 • 可用 {sizeMB}MB" }, "add_webpage": { "title": "添加网页爬取",