diff --git a/src/components/UploadDatasetEntriesModal.tsx b/src/components/UploadDatasetEntriesModal.tsx index 9551690..47b9570 100644 --- a/src/components/UploadDatasetEntriesModal.tsx +++ b/src/components/UploadDatasetEntriesModal.tsx @@ -12,20 +12,20 @@ export default function UploadDatasetEntriesModal({ datasetId, }: UploadDatasetEntriesModalProps) { const [isOpen, setIsOpen] = useState(false); - const [file, setFile] = useState(null); + const [files, setFiles] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); - const [entriesCreated, setEntriesCreated] = useState(0); + const [uploadQueue, setUploadQueue] = useState>([]); const [status, setStatus] = useState(''); - const [isDragging, setIsDragging] = useState(false); - const [tempDir, setTempDir] = useState(null); function handleFileChange(e: React.ChangeEvent) { - const selectedFile = e.target.files?.[0]; - if (selectedFile) { - setFile(selectedFile); + const selectedFiles = e.target.files ? Array.from(e.target.files) : []; + if (selectedFiles.length > 0) { + setFiles(selectedFiles); setError(null); + // Initialize queue with pending status + setUploadQueue(selectedFiles.map(file => ({ file, status: 'pending' }))); } } @@ -34,58 +34,82 @@ export default function UploadDatasetEntriesModal({ setError(null); setSuccess(false); - if (!file) { - setError('Please select a ZIP file'); + if (files.length === 0) { + setError('Please select at least one ZIP file'); return; } setLoading(true); try { - // Step 1: Upload and extract ZIP via API route - setStatus('Uploading and extracting ZIP file...'); - const formData = new FormData(); - formData.append('file', file); - formData.append('datasetId', datasetId.toString()); - - const uploadResponse = await fetch('/api/upload', { - method: 'POST', - body: formData, - }); - - if (!uploadResponse.ok) { - const errorData = await uploadResponse.json(); - throw new Error(errorData.error || 'Failed to upload ZIP file'); - } - - const uploadResult = await uploadResponse.json(); + // Process each file in the queue sequentially + const newQueue = [...uploadQueue]; - if (!uploadResult.success) { - throw new Error('Failed to upload ZIP file'); + for (let i = 0; i < newQueue.length; i++) { + const item = newQueue[i]; + item.status = 'uploading'; + setUploadQueue([...newQueue]); + setStatus(`Processing file ${i + 1} of ${newQueue.length}: ${item.file.name}`); + + try { + const formData = new FormData(); + formData.append('file', item.file); + formData.append('datasetId', datasetId.toString()); + + const uploadResponse = await fetch('/api/upload', { + method: 'POST', + body: formData, + }); + + if (!uploadResponse.ok) { + const errorData = await uploadResponse.json(); + throw new Error(errorData.error || 'Failed to upload ZIP file'); + } + + const uploadResult = await uploadResponse.json(); + + if (!uploadResult.success) { + throw new Error('Failed to upload ZIP file'); + } + + // Process the extracted data + const processResult = await processDatasetEntries(datasetId, uploadResult.tempDir); + + item.status = 'completed'; + item.message = `✓ Processed (${processResult.entriesCreated} entries)`; + } catch (err) { + item.status = 'failed'; + item.message = err instanceof Error ? err.message : 'Unknown error'; + } + + setUploadQueue([...newQueue]); } - setTempDir(uploadResult.tempDir); - setStatus(`${uploadResult.message} Now processing...`); + const failedCount = newQueue.filter(item => item.status === 'failed').length; + const completedCount = newQueue.filter(item => item.status === 'completed').length; - // Step 2: Process the extracted data (server action) - setStatus('Processing entries and copying audio files...'); - const processResult = await processDatasetEntries(datasetId, uploadResult.tempDir); + if (failedCount === 0) { + setSuccess(true); + setStatus(`All files processed! (${completedCount}/${newQueue.length} completed)`); + } else if (completedCount > 0) { + setStatus(`Completed with errors: ${completedCount} succeeded, ${failedCount} failed`); + } else { + setError(`All files failed to process`); + } - setSuccess(true); - setEntriesCreated(processResult.entriesCreated); - setStatus(processResult.message); - setFile(null); - setTempDir(null); + setFiles([]); - setTimeout(() => { - setSuccess(false); - setIsOpen(false); - setStatus(''); - window.location.reload(); - }, 2500); + if (failedCount === 0) { + setTimeout(() => { + setSuccess(false); + setIsOpen(false); + setStatus(''); + setUploadQueue([]); + window.location.reload(); + }, 2500); + } } catch (err) { - const errorMsg = - err instanceof Error ? err.message : 'Failed to upload and process dataset entries'; + const errorMsg = err instanceof Error ? err.message : 'Failed to process uploads'; setError(errorMsg); setStatus(''); } finally { @@ -96,11 +120,11 @@ export default function UploadDatasetEntriesModal({ function handleClose() { if (!loading) { setIsOpen(false); - setFile(null); + setFiles([]); setError(null); setSuccess(false); setStatus(''); - setTempDir(null); + setUploadQueue([]); } } @@ -125,7 +149,7 @@ export default function UploadDatasetEntriesModal({ disabled: loading, }, { - label: loading ? 'Uploading & Processing...' : 'Upload', + label: loading ? `Processing ${uploadQueue.filter(f => f.status !== 'pending').length}/${uploadQueue.length}...` : 'Upload Queue', onClick: () => { const form = document.getElementById( 'uploadForm' @@ -133,40 +157,45 @@ export default function UploadDatasetEntriesModal({ form?.dispatchEvent(new Event('submit', { bubbles: true })); }, variant: 'primary', - disabled: loading || !file, + disabled: loading || files.length === 0, }, ]} >
@@ -185,6 +214,31 @@ export default function UploadDatasetEntriesModal({
+ {uploadQueue.length > 0 && ( +
+

Upload Queue:

+
+ {uploadQueue.map((item, idx) => ( +
+ + {item.status === 'completed' ? '✓' : + item.status === 'failed' ? '✗' : + item.status === 'uploading' ? '⟳' : + '○'} + + {item.file.name} + {item.message && {item.message}} +
+ ))} +
+
+ )} + {error && (
{error}