fix: allow js/css/txt extensions and limit upload concurrency to 6

This commit is contained in:
Tianyang 2026-08-01 19:38:09 +08:00
parent c6cc9b7146
commit 7386b215e2
2 changed files with 54 additions and 45 deletions

View File

@ -11,7 +11,12 @@ logger = logging.getLogger(__name__)
def _natural_sort_key(name: str) -> list: def _natural_sort_key(name: str) -> list:
return [int(part) if part.isdigit() else part for part in re.split(r"(\d+)", name)] 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 MAX_UPLOAD_SIZE_BYTES = 10 * 1024 * 1024

View File

@ -7,6 +7,37 @@ interface UploadButtonProps {
onUploaded: (result: UploadDocumentResponse) => void; 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) { export default function UploadButton({ path, onUploaded }: UploadButtonProps) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isDragOver, setIsDragOver] = useState(false); const [isDragOver, setIsDragOver] = useState(false);
@ -16,52 +47,29 @@ export default function UploadButton({ path, onUploaded }: UploadButtonProps) {
function setFolderInputRef(node: HTMLInputElement | null) { function setFolderInputRef(node: HTMLInputElement | null) {
folderInputRef.current = node; folderInputRef.current = node;
if (node) { if (node) node.setAttribute('webkitdirectory', '');
node.setAttribute('webkitdirectory', '');
}
} }
function openModal() { function openModal() { setIsOpen(true); }
setIsOpen(true); function closeModal() { setIsOpen(false); setIsDragOver(false); setUploadError(null); }
}
function closeModal() { function runQueue(entries: FileWithRelativePath[]) {
setIsOpen(false);
setIsDragOver(false);
setUploadError(null); setUploadError(null);
} uploadQueue(entries, path, onUploaded, setUploadError);
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 }));
} }
function handleFileInputChange(event: React.ChangeEvent<HTMLInputElement>) { function handleFileInputChange(event: React.ChangeEvent<HTMLInputElement>) {
if (event.target.files) { if (event.target.files)
uploadFiles(event.target.files); runQueue(Array.from(event.target.files).map((file) => ({ file })));
}
event.target.value = ''; event.target.value = '';
} }
function handleFolderInputChange(event: React.ChangeEvent<HTMLInputElement>) { function handleFolderInputChange(event: React.ChangeEvent<HTMLInputElement>) {
if (event.target.files) { if (event.target.files)
Array.from(event.target.files).forEach((file) => runQueue(Array.from(event.target.files).map((file) => ({
uploadOne({ file, relativePath: file.webkitRelativePath || file.name }), file,
); relativePath: file.webkitRelativePath || file.name,
} })));
event.target.value = ''; event.target.value = '';
} }
@ -69,14 +77,12 @@ export default function UploadButton({ path, onUploaded }: UploadButtonProps) {
event.preventDefault(); event.preventDefault();
setIsDragOver(false); setIsDragOver(false);
const items = event.dataTransfer.items; const items = event.dataTransfer.items;
if (items && items.length > 0 && typeof items[0]?.webkitGetAsEntry === 'function') { if (items?.length > 0 && typeof items[0]?.webkitGetAsEntry === 'function') {
const entries = await readFileEntries(items); runQueue(await readFileEntries(items));
entries.forEach(uploadOne);
return; return;
} }
if (event.dataTransfer.files) { if (event.dataTransfer.files)
uploadFiles(event.dataTransfer.files); runQueue(Array.from(event.dataTransfer.files).map((file) => ({ file })));
}
} }
function handleDragOver(event: React.DragEvent<HTMLDivElement>) { function handleDragOver(event: React.DragEvent<HTMLDivElement>) {
@ -84,9 +90,7 @@ export default function UploadButton({ path, onUploaded }: UploadButtonProps) {
setIsDragOver(true); setIsDragOver(true);
} }
function handleDragLeave() { function handleDragLeave() { setIsDragOver(false); }
setIsDragOver(false);
}
return ( return (
<> <>