mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
perf(web): optimize document upload validation with static sets, MIME checks, and 50MB size guard
- Hoist ALLOWED_EXTENSIONS and ALLOWED_MIME_TYPES Sets to module scope so batch drag-and-drop no longer re-allocates them per file check - Fail fast on files larger than 50MB (matching the chat attachment and backend worker limit) with a dedicated toast instead of uploading them only to be rejected at the gateway - Verify MIME types directly (application/pdf, Office types, text/csv, text/plain, ...) so extensionless uploads such as mobile blobs are accepted, stripping parameters like ";charset=utf-8" before lookup, with the extension check kept as fallback - Add application/pdf to FILE_ACCEPT, matching CHAT_ATTACHMENT_ACCEPT Fixes #1559 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
f11d8c4620
commit
90d52a2883
1 changed files with 67 additions and 27 deletions
|
|
@ -8,7 +8,9 @@ import { useHotkeys } from "react-hotkeys-hook"
|
|||
import { toast } from "sonner"
|
||||
|
||||
export const FILE_ACCEPT =
|
||||
"image/*,.pdf,.doc,.docx,.xls,.xlsx,.csv,.txt,.md,.mdx,.json,.html,.htm,text/markdown,application/json,text/html"
|
||||
"image/*,.pdf,.doc,.docx,.xls,.xlsx,.csv,.txt,.md,.mdx,.json,.html,.htm,application/pdf,text/markdown,application/json,text/html"
|
||||
|
||||
export const MAX_FILE_SIZE_BYTES = 50 * 1024 * 1024
|
||||
|
||||
export type FileQueueItemStatus = "pending" | "uploading" | "success" | "error"
|
||||
|
||||
|
|
@ -33,29 +35,50 @@ interface FileContentProps {
|
|||
isOpen?: boolean
|
||||
}
|
||||
|
||||
function isAcceptedFile(file: File): boolean {
|
||||
const ALLOWED_EXTENSIONS = new Set([
|
||||
".pdf",
|
||||
".doc",
|
||||
".docx",
|
||||
".xls",
|
||||
".xlsx",
|
||||
".csv",
|
||||
".txt",
|
||||
".md",
|
||||
".mdx",
|
||||
".json",
|
||||
".html",
|
||||
".htm",
|
||||
])
|
||||
|
||||
const ALLOWED_MIME_TYPES = new Set([
|
||||
"application/pdf",
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"text/csv",
|
||||
"text/plain",
|
||||
"text/markdown",
|
||||
"application/json",
|
||||
"text/html",
|
||||
])
|
||||
|
||||
export function isAcceptedFile(file: File): boolean {
|
||||
if (file.size > MAX_FILE_SIZE_BYTES) return false
|
||||
if (file.type) {
|
||||
// Drop parameters like ";charset=utf-8" before the exact lookup
|
||||
const semicolonIndex = file.type.indexOf(";")
|
||||
const mimeType = (
|
||||
semicolonIndex === -1 ? file.type : file.type.slice(0, semicolonIndex)
|
||||
)
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
if (ALLOWED_MIME_TYPES.has(mimeType)) return true
|
||||
if (mimeType.startsWith("image/")) return true
|
||||
}
|
||||
const name = file.name.toLowerCase()
|
||||
const ext = name.includes(".") ? name.slice(name.lastIndexOf(".")) : ""
|
||||
const allowedExt = new Set([
|
||||
".pdf",
|
||||
".doc",
|
||||
".docx",
|
||||
".xls",
|
||||
".xlsx",
|
||||
".csv",
|
||||
".txt",
|
||||
".md",
|
||||
".mdx",
|
||||
".json",
|
||||
".html",
|
||||
".htm",
|
||||
])
|
||||
if (allowedExt.has(ext)) return true
|
||||
if (file.type.startsWith("image/")) return true
|
||||
if (file.type === "text/markdown") return true
|
||||
if (file.type === "application/json") return true
|
||||
if (file.type === "text/html") return true
|
||||
return false
|
||||
return ALLOWED_EXTENSIONS.has(ext)
|
||||
}
|
||||
|
||||
function fileQueueKey(file: File): string {
|
||||
|
|
@ -100,13 +123,30 @@ export function FileContent({
|
|||
const addFiles = useCallback(
|
||||
(fileList: FileList | File[]) => {
|
||||
const incoming = Array.from(fileList)
|
||||
const accepted = incoming.filter(isAcceptedFile)
|
||||
const rejected = incoming.length - accepted.length
|
||||
if (rejected > 0) {
|
||||
const accepted: File[] = []
|
||||
let oversizedCount = 0
|
||||
let unsupportedCount = 0
|
||||
for (const file of incoming) {
|
||||
if (file.size > MAX_FILE_SIZE_BYTES) {
|
||||
oversizedCount++
|
||||
} else if (isAcceptedFile(file)) {
|
||||
accepted.push(file)
|
||||
} else {
|
||||
unsupportedCount++
|
||||
}
|
||||
}
|
||||
if (oversizedCount > 0) {
|
||||
toast.error(
|
||||
rejected === 1
|
||||
oversizedCount === 1
|
||||
? `One file exceeds the ${formatFileSize(MAX_FILE_SIZE_BYTES)} limit`
|
||||
: `${oversizedCount} files exceed the ${formatFileSize(MAX_FILE_SIZE_BYTES)} limit`,
|
||||
)
|
||||
}
|
||||
if (unsupportedCount > 0) {
|
||||
toast.error(
|
||||
unsupportedCount === 1
|
||||
? "One file type is not supported"
|
||||
: `${rejected} files are not supported`,
|
||||
: `${unsupportedCount} files are not supported`,
|
||||
)
|
||||
}
|
||||
if (accepted.length === 0) return
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue