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
  };
};