'use client'; import { useState } from 'react'; import { processDatasetEntries } from '@/app/actions/upload'; import Modal from '@/components/Modal'; interface UploadDatasetEntriesModalProps { datasetId: number; } export default function UploadDatasetEntriesModal({ datasetId, }: UploadDatasetEntriesModalProps) { const [isOpen, setIsOpen] = useState(false); const [files, setFiles] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); const [uploadQueue, setUploadQueue] = useState>([]); const [status, setStatus] = useState(''); function handleFileChange(e: React.ChangeEvent) { const selectedFiles = e.target.files ? Array.from(e.target.files) : []; if (selectedFiles.length > 0) { setFiles(selectedFiles); setError(null); // Initialize queue with pending status and 0 progress setUploadQueue(selectedFiles.map(file => ({ file, status: 'pending', progress: 0 }))); } } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setError(null); setSuccess(false); if (files.length === 0) { setError('Please select at least one ZIP file'); return; } setLoading(true); try { // Process each file in the queue sequentially const newQueue = [...uploadQueue]; for (let i = 0; i < newQueue.length; i++) { const item = newQueue[i]; item.status = 'uploading'; item.progress = 0; setUploadQueue([...newQueue]); setStatus(`Processing file ${i + 1} of ${newQueue.length}: ${item.file.name}`); try { // Upload with progress tracking const uploadResult = await uploadFileWithProgress(item.file, i, newQueue); // Process the extracted data const processResult = await processDatasetEntries(datasetId, uploadResult.tempDir); if (!processResult.success) { item.status = 'failed'; item.progress = 0; item.message = `✗ ${processResult.error || 'Processing failed'}`; } else { item.status = 'completed'; item.progress = 100; item.message = `✓ Processed (${processResult.entriesCreated} entries)`; } } catch (err) { item.status = 'failed'; item.progress = 0; item.message = `✗ ${err instanceof Error ? err.message : 'Network error'}`; } setUploadQueue([...newQueue]); } const failedCount = newQueue.filter(item => item.status === 'failed').length; const completedCount = newQueue.filter(item => item.status === 'completed').length; 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`); } setFiles([]); 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 process uploads'; setError(errorMsg); setStatus(''); } finally { setLoading(false); } } function uploadFileWithProgress( file: File, index: number, queue: Array<{ file: File; status: 'pending' | 'uploading' | 'completed' | 'failed'; progress: number; message?: string }> ): Promise<{ tempDir: string }> { return new Promise((resolve, reject) => { const formData = new FormData(); formData.append('file', file); formData.append('datasetId', datasetId.toString()); const xhr = new XMLHttpRequest(); // Track upload progress xhr.upload.addEventListener('progress', (event) => { if (event.lengthComputable) { const progress = Math.round((event.loaded / event.total) * 100); queue[index].progress = progress; setUploadQueue([...queue]); } }); xhr.addEventListener('load', () => { if (xhr.status === 200) { try { const result = JSON.parse(xhr.responseText); if (result.success) { resolve(result); } else { reject(new Error(result.error || 'Upload failed')); } } catch (error) { reject(new Error('Failed to parse response')); } } else { try { const error = JSON.parse(xhr.responseText); reject(new Error(error.error || `Upload failed with status ${xhr.status}`)); } catch { reject(new Error(`Upload failed with status ${xhr.status}`)); } } }); xhr.addEventListener('error', () => { reject(new Error('Upload failed')); }); xhr.addEventListener('abort', () => { reject(new Error('Upload aborted')); }); xhr.open('POST', '/api/upload'); xhr.send(formData); }); } function handleClose() { if (!loading) { setIsOpen(false); setFiles([]); setError(null); setSuccess(false); setStatus(''); setUploadQueue([]); } } return ( <> f.status !== 'pending').length}/${uploadQueue.length}...` : 'Upload Queue', onClick: () => { const form = document.getElementById( 'uploadForm' ) as HTMLFormElement; form?.dispatchEvent(new Event('submit', { bubbles: true })); }, variant: 'primary', disabled: loading || files.length === 0, }, ]} >

ZIP file should contain:

  • metadata.csv{' '} with columns: id, audio_file, duration_ms, utt_id, speaker, model, dialect, iteration
  • Audio files (.wav) referenced in metadata.csv
{uploadQueue.length > 0 && (

Upload Queue:

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