File size: 4,161 Bytes
c0659f6 8f35e50 b49e394 c0659f6 b49e394 8f35e50 b49e394 8f35e50 c0659f6 b49e394 8f35e50 b49e394 8f35e50 b49e394 8f35e50 c0659f6 8f35e50 b49e394 8f35e50 b49e394 8f35e50 c0659f6 b49e394 c0659f6 8f35e50 b49e394 8f35e50 b49e394 c0659f6 8f35e50 c0659f6 b49e394 8f35e50 b49e394 8f35e50 b49e394 c0659f6 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
import { useState, useCallback, useRef } from 'react';
import { FileItem, UploadStatus } from '../types';
import { uploadBatchToHub } from '../services/hfService';
export const useFileUpload = () => {
const [files, setFiles] = useState<FileItem[]>([]);
const [isUploading, setIsUploading] = useState(false);
// Use Ref to access latest state inside async loops without dependencies issues
const filesRef = useRef<FileItem[]>([]);
filesRef.current = files;
// --- CONFIGURATION FOR SPEED ---
// Tăng số lượng file mỗi lần gửi để giảm thời gian tạo commit trên HF
const BATCH_SIZE = 10;
// Số lượng request gửi song song (Browser thường giới hạn 6 domain connections)
const CONCURRENCY_LIMIT = 5;
// --- UPLOAD LOGIC ---
const addFiles = useCallback((newFilesList: FileItem[]) => {
setFiles((prev) => [...prev, ...newFilesList]);
}, []);
const removeFile = useCallback((id: string) => {
setFiles((prev) => prev.filter((f) => f.id !== id));
}, []);
const updateFilePath = useCallback((id: string, newPath: string) => {
setFiles((prev) => prev.map((f) => (f.id === id ? { ...f, path: newPath } : f)));
}, []);
const startUpload = useCallback(async () => {
// Filter pending files
const pendingFiles = filesRef.current.filter(
(f) => f.status === UploadStatus.IDLE || f.status === UploadStatus.ERROR
);
if (pendingFiles.length === 0) return;
setIsUploading(true);
// 1. Chunk files into Batches
const batches: FileItem[][] = [];
for (let i = 0; i < pendingFiles.length; i += BATCH_SIZE) {
batches.push(pendingFiles.slice(i, i + BATCH_SIZE));
}
// A queue of batches to process
const queue = [...batches];
let activeWorkers = 0;
// Helper to update status safely
const updateBatchStatus = (batchItems: FileItem[], status: UploadStatus, result?: { urls?: string[], error?: string }) => {
setFiles((prev) =>
prev.map((f) => {
const batchIndex = batchItems.findIndex(b => b.id === f.id);
if (batchIndex !== -1) {
return {
...f,
status: status,
url: status === UploadStatus.SUCCESS ? result?.urls?.[batchIndex] : f.url,
error: status === UploadStatus.ERROR ? result?.error : undefined
};
}
return f;
})
);
};
// 2. The Worker Function
// Process one batch, then immediately grab the next one from the queue
const processNextBatch = async (): Promise<void> => {
if (queue.length === 0) return;
const batch = queue.shift();
if (!batch) return;
activeWorkers++;
// Update UI -> UPLOADING
updateBatchStatus(batch, UploadStatus.UPLOADING);
try {
const payload = batch.map(item => ({
id: item.id,
file: item.file,
path: item.path
}));
const urls = await uploadBatchToHub(payload);
// Update UI -> SUCCESS
updateBatchStatus(batch, UploadStatus.SUCCESS, { urls });
} catch (err: any) {
console.error("Batch failed:", err);
// Update UI -> ERROR
updateBatchStatus(batch, UploadStatus.ERROR, { error: err.message || "Upload failed" });
} finally {
activeWorkers--;
// Recursively process next batch if available
if (queue.length > 0) {
await processNextBatch();
}
}
};
// 3. Start Initial Workers (Pool)
// Create exactly CONCURRENCY_LIMIT workers that will keep eating from the queue
const initialWorkers = [];
const limit = Math.min(CONCURRENCY_LIMIT, batches.length);
for (let i = 0; i < limit; i++) {
initialWorkers.push(processNextBatch());
}
// Wait for all workers to finish depleting the queue
await Promise.all(initialWorkers);
setIsUploading(false);
}, []); // Remove 'files' dependency to avoid closure staleness, use ref
return {
files,
isUploading,
addFiles,
removeFile,
updateFilePath,
startUpload
};
};
|