import React, { useState, useCallback, useRef, useMemo, useEffect } from 'react'; import { Database, Play, Square, Download, FolderOpen, Save, Loader2, Music2, Edit3, Upload, X, Volume2, FileAudio, ChevronRight, Zap, Search, Cpu, Wand2, Settings, RefreshCw, } from 'lucide-react'; import { useAuth } from '../context/AuthContext'; import { useI18n } from '../context/I18nContext'; import { trainingApi, getTrainingAudioUrl, TrainingSample, DatasetSettings } from '../services/api'; type TrainingTab = 'dataset' | 'train' | 'export'; interface DataframeRow { [key: string]: unknown; } const LANGUAGES = [ { value: 'instrumental', label: 'Instrumental' }, { value: 'en', label: 'English' }, { value: 'zh', label: 'Chinese' }, { value: 'ja', label: 'Japanese' }, { value: 'ko', label: 'Korean' }, { value: 'es', label: 'Spanish' }, { value: 'fr', label: 'French' }, { value: 'de', label: 'German' }, { value: 'pt', label: 'Portuguese' }, { value: 'ru', label: 'Russian' }, { value: 'unknown', label: 'Unknown' }, ]; const TIME_SIGS = ['', '2', '3', '4', '6', 'N/A']; const DEVICES = ['auto', 'cuda', 'mps', 'xpu', 'cpu']; const BACKENDS = ['pt', 'vllm', 'mlx']; // Pipeline step definitions const PIPELINE_STEPS = [ { key: 'upload', label: 'Upload', icon: Upload }, { key: 'edit', label: 'Edit', icon: Edit3 }, { key: 'save', label: 'Save', icon: Save }, { key: 'preprocess', label: 'Preprocess', icon: Zap }, { key: 'train', label: 'Train', icon: Play }, { key: 'export', label: 'Export', icon: Download }, ] as const; type PipelineStepKey = typeof PIPELINE_STEPS[number]['key']; export const TrainingPanel: React.FC = () => { const { token } = useAuth(); const { t } = useI18n(); const [activeTab, setActiveTab] = useState('dataset'); // Pipeline completion tracking const [completedSteps, setCompletedSteps] = useState>(new Set()); // Model / Service config state const [showModelConfig, setShowModelConfig] = useState(false); const [modelCheckpoints, setModelCheckpoints] = useState([]); const [modelConfigs, setModelConfigs] = useState([]); const [selectedCheckpoint, setSelectedCheckpoint] = useState(''); const [selectedConfig, setSelectedConfig] = useState(''); const [selectedDevice, setSelectedDevice] = useState('auto'); const [selectedBackend, setSelectedBackend] = useState('pt'); const [initLlm, setInitLlm] = useState(false); const [lmModelPath, setLmModelPath] = useState(''); const [useFlashAttention, setUseFlashAttention] = useState(false); const [offloadToCpu, setOffloadToCpu] = useState(false); const [offloadDitToCpu, setOffloadDitToCpu] = useState(false); const [compileModel, setCompileModel] = useState(false); const [quantization, setQuantization] = useState(false); const [modelInitStatus, setModelInitStatus] = useState(''); const [modelInitializing, setModelInitializing] = useState(false); // Upload state const [queuedFiles, setQueuedFiles] = useState([]); const [uploadDatasetName, setUploadDatasetName] = useState('my_lora_dataset'); const [uploading, setUploading] = useState(false); const [uploadStatus, setUploadStatus] = useState(''); const [isDragOver, setIsDragOver] = useState(false); const fileInputRef = useRef(null); // Scan directory state const [scanDir, setScanDir] = useState(''); const [scanning, setScanning] = useState(false); const [scanStatus, setScanStatus] = useState(''); // Dataset state const [datasetPath, setDatasetPath] = useState('./datasets/my_lora_dataset.json'); const [datasetLoaded, setDatasetLoaded] = useState(false); const [datasetLoading, setDatasetLoading] = useState(false); const [sampleCount, setSampleCount] = useState(0); const [currentSampleIdx, setCurrentSampleIdx] = useState(0); const [currentSample, setCurrentSample] = useState(null); const [datasetSettings, setDatasetSettings] = useState({ datasetName: 'my_lora_dataset', customTag: '', tagPosition: 'replace', allInstrumental: true, genreRatio: 0, }); const [datasetStatus, setDatasetStatus] = useState(''); // Dataset table state const [dataframeHeaders, setDataframeHeaders] = useState([]); const [dataframeRows, setDataframeRows] = useState([]); // Auto-label state const [autoLabeling, setAutoLabeling] = useState(false); const [autoLabelStatus, setAutoLabelStatus] = useState(''); const [skipMetas, setSkipMetas] = useState(false); const [formatLyrics, setFormatLyrics] = useState(false); const [transcribeLyrics, setTranscribeLyrics] = useState(false); const [onlyUnlabeled, setOnlyUnlabeled] = useState(false); // Editing sample state const [editCaption, setEditCaption] = useState(''); const [editGenre, setEditGenre] = useState(''); const [editPromptOverride, setEditPromptOverride] = useState('Use Global Ratio'); const [editLyrics, setEditLyrics] = useState(''); const [editBpm, setEditBpm] = useState(120); const [editKey, setEditKey] = useState(''); const [editTimeSig, setEditTimeSig] = useState(''); const [editDuration, setEditDuration] = useState(0); const [editLanguage, setEditLanguage] = useState('instrumental'); const [editInstrumental, setEditInstrumental] = useState(true); const [editRawLyrics, setEditRawLyrics] = useState(''); // Dataset save state const [savePath, setSavePath] = useState('./datasets/my_lora_dataset.json'); const [saveStatus, setSaveStatus] = useState(''); const [editSaveStatus, setEditSaveStatus] = useState(''); // Preprocess state — has its own load-existing-dataset sub-section (matches Gradio) const [preprocessDatasetPath, setPreprocessDatasetPath] = useState('./datasets/my_lora_dataset.json'); const [preprocessDatasetLoading, setPreprocessDatasetLoading] = useState(false); const [preprocessDatasetStatus, setPreprocessDatasetStatus] = useState(''); const [preprocessOutputDir, setPreprocessOutputDir] = useState('./datasets/preprocessed_tensors'); const [preprocessing, setPreprocessing] = useState(false); const [preprocessStatus, setPreprocessStatus] = useState(''); // Training state const [trainingParams, setTrainingParams] = useState({ tensorDir: './datasets/preprocessed_tensors', rank: 64, alpha: 128, dropout: 0.1, learningRate: 0.0003, epochs: 1000, batchSize: 1, gradientAccumulation: 1, saveEvery: 200, shift: 3.0, seed: 42, outputDir: './lora_output', resumeCheckpoint: '' as string, }); const [isTraining, setIsTraining] = useState(false); const [trainingProgress, setTrainingProgress] = useState(''); const [trainingLog, setTrainingLog] = useState(''); const [trainingMetrics, setTrainingMetrics] = useState(null); const [trainingDatasetInfo, setTrainingDatasetInfo] = useState(''); // Export state const [exportPath, setExportPath] = useState('./lora_output/final_lora'); const [exportOutputDir, setExportOutputDir] = useState('./lora_output'); const [exportStatus, setExportStatus] = useState(''); // Loading states const [saving, setSaving] = useState(false); const [exporting, setExporting] = useState(false); // Audio preview URL const audioPreviewUrl = useMemo(() => { if (!currentSample?.audio) return undefined; return getTrainingAudioUrl(currentSample.audio); }, [currentSample?.audio]); const markStep = useCallback((step: PipelineStepKey) => { setCompletedSteps(prev => new Set([...prev, step])); }, []); const populateSampleFields = (sample: TrainingSample) => { setEditCaption(sample.caption || ''); setEditGenre(sample.genre || ''); setEditPromptOverride(sample.promptOverride || 'Use Global Ratio'); setEditLyrics(sample.lyrics || ''); setEditBpm(sample.bpm || 120); setEditKey(sample.key || ''); setEditTimeSig(sample.timeSignature || ''); setEditDuration(sample.duration || 0); setEditLanguage(sample.language || 'instrumental'); setEditInstrumental(sample.instrumental ?? true); setEditRawLyrics(sample.rawLyrics || ''); }; // Parse dataframe from Gradio response const parseDataframe = (df: unknown) => { if (!df || typeof df !== 'object') return; const dfObj = df as { headers?: string[]; data?: unknown[][] }; if (dfObj.headers && Array.isArray(dfObj.data)) { setDataframeHeaders(dfObj.headers); setDataframeRows(dfObj.data.map(row => { const obj: DataframeRow = {}; dfObj.headers!.forEach((h, i) => { obj[h] = row[i]; }); return obj; })); } }; // Load checkpoints on mount useEffect(() => { if (!token) return; trainingApi.getCheckpoints(token).then(result => { setModelCheckpoints(result.checkpoints); setModelConfigs(result.configs); if (result.checkpoints.length > 0 && !selectedCheckpoint) { setSelectedCheckpoint(result.checkpoints[0]); } if (result.configs.length > 0 && !selectedConfig) { setSelectedConfig(result.configs[0]); } }).catch(() => { /* ignore */ }); }, [token]); // eslint-disable-line react-hooks/exhaustive-deps // === Model init === const handleRefreshCheckpoints = useCallback(async () => { if (!token) return; try { const result = await trainingApi.getCheckpoints(token); setModelCheckpoints(result.checkpoints); setModelConfigs(result.configs); } catch { /* ignore */ } }, [token]); const handleInitModel = useCallback(async () => { if (!token) return; setModelInitializing(true); setModelInitStatus('Initializing model...'); try { const result = await trainingApi.initModel({ checkpoint: selectedCheckpoint, configPath: selectedConfig, device: selectedDevice, initLlm, lmModelPath, backend: selectedBackend, useFlashAttention, offloadToCpu, offloadDitToCpu, compileModel, quantization, }, token); setModelInitStatus(result.status || result.error || ''); } catch (error) { const msg = error instanceof Error ? error.message : 'Failed'; setModelInitStatus(msg.includes('501') ? 'Use Gradio UI to initialize model' : msg); } finally { setModelInitializing(false); } }, [token, selectedCheckpoint, selectedConfig, selectedDevice, initLlm, lmModelPath, selectedBackend, useFlashAttention, offloadToCpu, offloadDitToCpu, compileModel, quantization]); // === Drop zone handlers === const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragOver(true); }, []); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragOver(false); }, []); const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragOver(false); const files = Array.from(e.dataTransfer.files).filter((f: File) => { const ext = f.name.toLowerCase().split('.').pop(); return ['wav', 'mp3', 'flac', 'ogg', 'opus'].includes(ext || ''); }); if (files.length > 0) { setQueuedFiles(prev => [...prev, ...files]); } }, []); const handleFileSelect = useCallback((e: React.ChangeEvent) => { if (e.target.files) { setQueuedFiles(prev => [...prev, ...Array.from(e.target.files!)]); } if (fileInputRef.current) fileInputRef.current.value = ''; }, []); const removeQueuedFile = useCallback((idx: number) => { setQueuedFiles(prev => prev.filter((_, i) => i !== idx)); }, []); // === Upload + Build Dataset === const handleUploadAndBuild = useCallback(async () => { if (!token || queuedFiles.length === 0) return; setUploading(true); setUploadStatus('Uploading files...'); try { await trainingApi.uploadAudio(queuedFiles, uploadDatasetName, token); setUploadStatus(`Uploaded ${queuedFiles.length} files. Building dataset...`); const result = await trainingApi.buildDataset({ datasetName: uploadDatasetName, customTag: datasetSettings.customTag, tagPosition: datasetSettings.tagPosition, allInstrumental: datasetSettings.allInstrumental, }, token); setDatasetLoaded(true); setSampleCount(result.sampleCount); setCurrentSampleIdx(0); if (result.sample) { setCurrentSample(result.sample); populateSampleFields(result.sample); } if (result.settings) setDatasetSettings(result.settings); if (result.dataframe) parseDataframe(result.dataframe); const dp = result.datasetPath || `./datasets/${uploadDatasetName}.json`; setDatasetPath(dp); setSavePath(dp); setDatasetStatus(result.status as string); setQueuedFiles([]); markStep('upload'); setUploadStatus(''); } catch (error) { setUploadStatus(`Error: ${error instanceof Error ? error.message : 'Upload failed'}`); } finally { setUploading(false); } }, [token, queuedFiles, uploadDatasetName, datasetSettings, markStep]); // === Scan directory === const handleScanDirectory = useCallback(async () => { if (!token || !scanDir) return; setScanning(true); setScanStatus('Scanning...'); try { const result = await trainingApi.scanDirectory({ audioDir: scanDir, datasetName: datasetSettings.datasetName, customTag: datasetSettings.customTag, tagPosition: datasetSettings.tagPosition, allInstrumental: datasetSettings.allInstrumental, }, token); setScanStatus(result.status); setSampleCount(result.sampleCount); if (result.dataframe) parseDataframe(result.dataframe); } catch (error) { setScanStatus(`Error: ${error instanceof Error ? error.message : 'Scan failed'}`); } finally { setScanning(false); } }, [token, scanDir, datasetSettings]); // === Load existing dataset === const handleLoadDataset = useCallback(async () => { if (!token || !datasetPath) return; setDatasetLoading(true); setDatasetStatus(t('loadingDataset')); try { const result = await trainingApi.loadDataset(datasetPath, token); setDatasetLoaded(true); setSampleCount(result.sampleCount); setCurrentSampleIdx(0); setCurrentSample(result.sample); populateSampleFields(result.sample); setDatasetSettings(result.settings); parseDataframe(result.dataframe); setDatasetStatus(result.status as string); setSavePath(datasetPath); markStep('upload'); } catch (error) { setDatasetStatus(`${t('error')}: ${error instanceof Error ? error.message : 'Failed'}`); } finally { setDatasetLoading(false); } }, [token, datasetPath, t, markStep]); // === Auto-label === const handleAutoLabel = useCallback(async () => { if (!token) return; setAutoLabeling(true); setAutoLabelStatus(t('autoLabeling')); try { const result = await trainingApi.autoLabel({ skipMetas, formatLyrics, transcribeLyrics, onlyUnlabeled, }, token); if (result.dataframe) parseDataframe(result.dataframe); setAutoLabelStatus(result.status || result.hint || ''); // Refresh current sample if (token && sampleCount > 0) { const sample = await trainingApi.getSamplePreview(currentSampleIdx, token); setCurrentSample(sample); populateSampleFields(sample); } } catch (error) { const msg = error instanceof Error ? error.message : 'Failed'; setAutoLabelStatus(msg.includes('501') ? 'Auto-label requires model loaded in Gradio UI' : msg); } finally { setAutoLabeling(false); } }, [token, skipMetas, formatLyrics, transcribeLyrics, onlyUnlabeled, sampleCount, currentSampleIdx, t]); // === Sample navigation === const handleSampleNavigate = useCallback(async (idx: number) => { if (!token || idx < 0 || idx >= sampleCount) return; setCurrentSampleIdx(idx); try { const sample = await trainingApi.getSamplePreview(idx, token); setCurrentSample(sample); populateSampleFields(sample); } catch (error) { console.error('Failed to load sample:', error); } }, [token, sampleCount]); // === Save sample === const handleSaveSample = useCallback(async () => { if (!token) return; setSaving(true); try { const result = await trainingApi.saveSample({ sampleIdx: currentSampleIdx, caption: editCaption, genre: editGenre, promptOverride: editPromptOverride, lyrics: editLyrics, bpm: editBpm, key: editKey, timeSignature: editTimeSig, language: editLanguage, instrumental: editInstrumental, }, token); if (result.dataframe) parseDataframe(result.dataframe); setEditSaveStatus(result.status as string); markStep('edit'); } catch (error) { setEditSaveStatus(`${t('error')}: ${error instanceof Error ? error.message : 'Failed'}`); } finally { setSaving(false); } }, [token, currentSampleIdx, editCaption, editGenre, editPromptOverride, editLyrics, editBpm, editKey, editTimeSig, editLanguage, editInstrumental, t, markStep]); // === Update settings === const handleUpdateSettings = useCallback(async () => { if (!token) return; try { await trainingApi.updateSettings({ customTag: datasetSettings.customTag, tagPosition: datasetSettings.tagPosition, allInstrumental: datasetSettings.allInstrumental, genreRatio: datasetSettings.genreRatio, }, token); setDatasetStatus('Settings updated'); } catch (error) { setDatasetStatus(`${t('error')}: ${error instanceof Error ? error.message : 'Failed'}`); } }, [token, datasetSettings, t]); // === Save dataset === const handleSaveDataset = useCallback(async () => { if (!token) return; setSaving(true); setSaveStatus(t('savingDataset')); try { const result = await trainingApi.saveDataset({ savePath: savePath || `./datasets/${datasetSettings.datasetName}.json`, datasetName: datasetSettings.datasetName, customTag: datasetSettings.customTag, tagPosition: datasetSettings.tagPosition, allInstrumental: datasetSettings.allInstrumental, genreRatio: datasetSettings.genreRatio, }, token); setSaveStatus(result.status as string); if (result.path) setSavePath(result.path); markStep('save'); } catch (error) { setSaveStatus(`${t('error')}: ${error instanceof Error ? error.message : 'Failed'}`); } finally { setSaving(false); } }, [token, savePath, datasetSettings.datasetName, t, markStep]); // === Load existing dataset for preprocessing (matches Gradio's load_existing_dataset_for_preprocess) === const handleLoadDatasetForPreprocess = useCallback(async () => { if (!token) return; setPreprocessDatasetLoading(true); setPreprocessDatasetStatus('Loading dataset for preprocessing...'); try { const result = await trainingApi.loadDataset(preprocessDatasetPath, token); setPreprocessDatasetStatus(result.status || `Loaded ${result.sampleCount} samples`); if (result.sampleCount) setSampleCount(result.sampleCount); if (result.dataframe) parseDataframe(result.dataframe); } catch (error) { setPreprocessDatasetStatus(`Error: ${error instanceof Error ? error.message : 'Failed to load'}`); } finally { setPreprocessDatasetLoading(false); } }, [token, preprocessDatasetPath]); // === Preprocess === const handlePreprocess = useCallback(async () => { if (!token) return; setPreprocessing(true); setPreprocessStatus('Preprocessing...'); try { const result = await trainingApi.preprocess({ datasetPath: preprocessDatasetPath || savePath || datasetPath, outputDir: preprocessOutputDir, }, token); setPreprocessStatus(result.message || result.status); markStep('preprocess'); } catch (error) { setPreprocessStatus(`Error: ${error instanceof Error ? error.message : 'Preprocessing failed'}`); } finally { setPreprocessing(false); } }, [token, preprocessDatasetPath, savePath, datasetPath, preprocessOutputDir, markStep]); // === Load tensors === const handleLoadTensors = useCallback(async () => { if (!token) return; try { const result = await trainingApi.loadTensors(trainingParams.tensorDir, token); setTrainingDatasetInfo(result.status); } catch (error) { setTrainingDatasetInfo(`Error: ${error instanceof Error ? error.message : 'Failed'}`); } }, [token, trainingParams.tensorDir]); // === Training === const handleStartTraining = useCallback(async () => { if (!token) return; setIsTraining(true); setTrainingProgress(t('startingTraining')); setTrainingLog(''); setTrainingMetrics(null); try { const result = await trainingApi.startTraining({ ...trainingParams, resumeCheckpoint: trainingParams.resumeCheckpoint || null, }, token); setTrainingProgress(result.progress as string); setTrainingLog(result.log as string); setTrainingMetrics(result.metrics); markStep('train'); } catch (error) { setTrainingProgress(`${t('error')}: ${error instanceof Error ? error.message : 'Failed'}`); } finally { setIsTraining(false); } }, [token, trainingParams, t, markStep]); const handleStopTraining = useCallback(async () => { if (!token) return; try { const result = await trainingApi.stopTraining(token); setTrainingProgress(result.status as string); setIsTraining(false); } catch (error) { console.error('Failed to stop training:', error); } }, [token]); // === Export === const handleExportLora = useCallback(async () => { if (!token) return; setExporting(true); setExportStatus('Exporting...'); try { const result = await trainingApi.exportLora({ exportPath, loraOutputDir: exportOutputDir, }, token); setExportStatus(result.status as string); markStep('export'); } catch (error) { setExportStatus(`${t('error')}: ${error instanceof Error ? error.message : 'Failed'}`); } finally { setExporting(false); } }, [token, exportPath, exportOutputDir, t, markStep]); // === Loss chart === const lossChartSvg = useMemo(() => { if (!trainingMetrics) return null; let points: { step: number; loss: number }[] = []; const m = trainingMetrics as any; if (m?.data && Array.isArray(m.data)) { points = m.data.map((row: unknown[]) => ({ step: Number(row[0]) || 0, loss: Number(row[1]) || 0 })).filter((p: { loss: number }) => p.loss > 0); } else if (Array.isArray(m)) { points = m.map((item: any, i: number) => ({ step: item.step ?? item.x ?? i, loss: item.loss ?? item.y ?? 0 })).filter((p: { loss: number }) => p.loss > 0); } if (points.length < 2) return null; const width = 280, height = 100, pad = 4; const minStep = Math.min(...points.map(p => p.step)); const maxStep = Math.max(...points.map(p => p.step)); const minLoss = Math.min(...points.map(p => p.loss)); const maxLoss = Math.max(...points.map(p => p.loss)); const rangeStep = maxStep - minStep || 1; const rangeLoss = maxLoss - minLoss || 1; const polyPoints = points.map(p => { const x = pad + ((p.step - minStep) / rangeStep) * (width - 2 * pad); const y = pad + (1 - (p.loss - minLoss) / rangeLoss) * (height - 2 * pad); return `${x},${y}`; }).join(' '); return ( {minStep} {maxStep} {minLoss.toFixed(4)} ); }, [trainingMetrics]); // Mutual exclusion: formatLyrics / transcribeLyrics useEffect(() => { if (formatLyrics && transcribeLyrics) setTranscribeLyrics(false); }, [formatLyrics]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { if (transcribeLyrics && formatLyrics) setFormatLyrics(false); }, [transcribeLyrics]); // eslint-disable-line react-hooks/exhaustive-deps const tabs: { id: TrainingTab; label: string; icon: React.ReactNode }[] = [ { id: 'dataset', label: t('datasetBuilder'), icon: }, { id: 'train', label: t('trainLora'), icon: }, { id: 'export', label: 'Export', icon: }, ]; return (
{/* Header */}

{t('loraTraining')}

{t('trainingDescription')}

{/* Pipeline Steps */}
{PIPELINE_STEPS.map((step, i) => { const Icon = step.icon; const done = completedSteps.has(step.key); return ( {i > 0 && }
{step.label}
); })}
{/* Tab Bar */}
{tabs.map(tab => ( ))}
{/* Tab Content */}
{/* ===== MODEL CONFIGURATION (shown at top of all tabs) ===== */}
setShowModelConfig(!showModelConfig)} className="flex items-center gap-1.5 w-full text-left"> Model Configuration }> {showModelConfig && (
{initLlm && ( setLmModelPath(e.target.value)} placeholder="LM model path" className="flex-1 bg-white/5 border border-white/10 rounded-lg px-2 py-1 text-xs text-zinc-200" /> )} {modelInitStatus &&

{modelInitStatus}

}
)}
{activeTab === 'dataset' && ( <> {/* Drop Zone */}
fileInputRef.current?.click()} className={`border-2 border-dashed rounded-xl p-4 text-center cursor-pointer transition-all ${isDragOver ? 'border-pink-500 bg-pink-500/10' : 'border-white/10 hover:border-white/20 hover:bg-white/[0.02]'}`} >

Drop audio files here or click to browse

.wav, .mp3, .flac, .ogg, .opus

{queuedFiles.length > 0 && (
{queuedFiles.map((f, i) => (
{f.name} {(f.size / 1024 / 1024).toFixed(1)}MB
))}
)} {queuedFiles.length > 0 && (
setUploadDatasetName(e.target.value)} className="flex-1 bg-white/5 border border-white/10 rounded-lg px-3 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-pink-500/50" placeholder="my_lora_dataset" />
)} {uploadStatus &&

{uploadStatus}

}
{/* Scan Directory */}
setScanDir(e.target.value)} className="flex-1 bg-white/5 border border-white/10 rounded-lg px-3 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-pink-500/50" placeholder="./path/to/audio/folder" />
{scanStatus &&

{scanStatus}

}
{/* Load Existing Dataset */}
setDatasetPath(e.target.value)} className="flex-1 bg-white/5 border border-white/10 rounded-lg px-3 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-pink-500/50" placeholder="./datasets/my_dataset.json" />
{datasetStatus &&

{datasetStatus}

}
{/* Dataset Table */} {dataframeRows.length > 0 && (
{dataframeHeaders.slice(0, 6).map(h => ( ))} {dataframeRows.map((row, i) => ( handleSampleNavigate(i)} className={`cursor-pointer transition-colors ${i === currentSampleIdx ? 'bg-pink-500/10 text-pink-300' : 'hover:bg-white/5 text-zinc-300'}`}> {dataframeHeaders.slice(0, 6).map(h => ( ))} ))}
#{h}
{i + 1}{String(row[h] ?? '')}
)} {/* Dataset Settings */} {datasetLoaded && ( <>
setDatasetSettings(s => ({ ...s, datasetName: e.target.value }))} className="flex-1 bg-white/5 border border-white/10 rounded-lg px-3 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-pink-500/50" /> setDatasetSettings(s => ({ ...s, customTag: e.target.value }))} className="flex-1 bg-white/5 border border-white/10 rounded-lg px-3 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-pink-500/50" placeholder="e.g. my_style" /> setDatasetSettings(s => ({ ...s, allInstrumental: e.target.checked }))} className="w-4 h-4 accent-pink-500" /> setDatasetSettings(s => ({ ...s, genreRatio: parseInt(e.target.value) }))} className="flex-1 accent-pink-500" />

{t('genreRatioHint')}

{/* Auto-Label */}

{t('autoLabelDescription')}

{autoLabelStatus &&

{autoLabelStatus}

}
{/* Sample Editor */}
{/* Sample Navigation */}
{ const v = parseInt(e.target.value) - 1; if (v >= 0 && v < sampleCount) handleSampleNavigate(v); }} className="w-16 bg-white/5 border border-white/10 rounded px-2 py-1 text-xs text-center text-zinc-200" /> {currentSample?.filename || ''}
{/* Audio Preview */} {audioPreviewUrl && (
)}
setEditCaption(e.target.value)} className="flex-1 bg-white/5 border border-white/10 rounded-lg px-3 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-pink-500/50" placeholder={t('musicDescription')} /> setEditGenre(e.target.value)} className="flex-1 bg-white/5 border border-white/10 rounded-lg px-3 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-pink-500/50" />