From 7386b215e23d2e0bf46cf384890a82a68e806e7a Mon Sep 17 00:00:00 2001 From: Tianyang Date: Sat, 1 Aug 2026 19:38:09 +0800 Subject: [PATCH] fix: allow js/css/txt extensions and limit upload concurrency to 6 --- backend/app/document_service.py | 7 +- frontend/src/components/UploadButton.tsx | 92 ++++++++++++------------ 2 files changed, 54 insertions(+), 45 deletions(-) diff --git a/backend/app/document_service.py b/backend/app/document_service.py index 1785f3b..7108cd2 100644 --- a/backend/app/document_service.py +++ b/backend/app/document_service.py @@ -11,7 +11,12 @@ logger = logging.getLogger(__name__) def _natural_sort_key(name: str) -> list: return [int(part) if part.isdigit() else part for part in re.split(r"(\d+)", name)] -ALLOWED_UPLOAD_EXTENSIONS = {".md", ".html", ".htm", ".png", ".jpg", ".jpeg", ".gif", ".webp"} +ALLOWED_UPLOAD_EXTENSIONS = { + ".md", ".html", ".htm", + ".js", ".ts", ".css", + ".json", ".yaml", ".yml", ".toml", ".xml", ".csv", ".txt", + ".png", ".jpg", ".jpeg", ".gif", ".webp", +} MAX_UPLOAD_SIZE_BYTES = 10 * 1024 * 1024 diff --git a/frontend/src/components/UploadButton.tsx b/frontend/src/components/UploadButton.tsx index aca9d9d..c219b87 100644 --- a/frontend/src/components/UploadButton.tsx +++ b/frontend/src/components/UploadButton.tsx @@ -7,6 +7,37 @@ interface UploadButtonProps { onUploaded: (result: UploadDocumentResponse) => void; } +const CONCURRENCY = 6; + +async function uploadQueue( + entries: FileWithRelativePath[], + path: string, + onDone: (result: UploadDocumentResponse) => void, + onError: (msg: string) => void, +) { + let i = 0; + async function next() { + if (i >= entries.length) return; + const entry = entries[i++]; + const name = entry.relativePath ?? entry.file.name; + try { + const result = await (entry.relativePath + ? uploadDocument(entry.file, path, entry.relativePath) + : uploadDocument(entry.file, path)); + onDone(result); + } catch (err) { + onError(`${name}:${err instanceof Error ? err.message : '上传失败'}`); + } + await next(); + } + await Promise.all(Array.from({ length: Math.min(CONCURRENCY, entries.length) }, next)); +} + +interface UploadButtonProps { + path: string; + onUploaded: (result: UploadDocumentResponse) => void; +} + export default function UploadButton({ path, onUploaded }: UploadButtonProps) { const [isOpen, setIsOpen] = useState(false); const [isDragOver, setIsDragOver] = useState(false); @@ -16,52 +47,29 @@ export default function UploadButton({ path, onUploaded }: UploadButtonProps) { function setFolderInputRef(node: HTMLInputElement | null) { folderInputRef.current = node; - if (node) { - node.setAttribute('webkitdirectory', ''); - } + if (node) node.setAttribute('webkitdirectory', ''); } - function openModal() { - setIsOpen(true); - } + function openModal() { setIsOpen(true); } + function closeModal() { setIsOpen(false); setIsDragOver(false); setUploadError(null); } - function closeModal() { - setIsOpen(false); - setIsDragOver(false); + function runQueue(entries: FileWithRelativePath[]) { setUploadError(null); - } - - function uploadOne(entry: FileWithRelativePath) { - setUploadError(null); - const promise = entry.relativePath - ? uploadDocument(entry.file, path, entry.relativePath) - : uploadDocument(entry.file, path); - promise - .then((result) => { - onUploaded(result); - }) - .catch((err) => { - setUploadError(err instanceof Error ? err.message : '上传失败'); - }); - } - - function uploadFiles(files: FileList | File[]) { - Array.from(files).forEach((file) => uploadOne({ file })); + uploadQueue(entries, path, onUploaded, setUploadError); } function handleFileInputChange(event: React.ChangeEvent) { - if (event.target.files) { - uploadFiles(event.target.files); - } + if (event.target.files) + runQueue(Array.from(event.target.files).map((file) => ({ file }))); event.target.value = ''; } function handleFolderInputChange(event: React.ChangeEvent) { - if (event.target.files) { - Array.from(event.target.files).forEach((file) => - uploadOne({ file, relativePath: file.webkitRelativePath || file.name }), - ); - } + if (event.target.files) + runQueue(Array.from(event.target.files).map((file) => ({ + file, + relativePath: file.webkitRelativePath || file.name, + }))); event.target.value = ''; } @@ -69,14 +77,12 @@ export default function UploadButton({ path, onUploaded }: UploadButtonProps) { event.preventDefault(); setIsDragOver(false); const items = event.dataTransfer.items; - if (items && items.length > 0 && typeof items[0]?.webkitGetAsEntry === 'function') { - const entries = await readFileEntries(items); - entries.forEach(uploadOne); + if (items?.length > 0 && typeof items[0]?.webkitGetAsEntry === 'function') { + runQueue(await readFileEntries(items)); return; } - if (event.dataTransfer.files) { - uploadFiles(event.dataTransfer.files); - } + if (event.dataTransfer.files) + runQueue(Array.from(event.dataTransfer.files).map((file) => ({ file }))); } function handleDragOver(event: React.DragEvent) { @@ -84,9 +90,7 @@ export default function UploadButton({ path, onUploaded }: UploadButtonProps) { setIsDragOver(true); } - function handleDragLeave() { - setIsDragOver(false); - } + function handleDragLeave() { setIsDragOver(false); } return ( <>