diff --git "a/app/frontend/src/App.js" "b/app/frontend/src/App.js"
--- "a/app/frontend/src/App.js"
+++ "b/app/frontend/src/App.js"
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useRef, useCallback, Suspense, Component } from 'react';
+import React, { useState, useEffect, useMemo } from 'react';
import {
Container,
Box,
@@ -12,10 +12,6 @@ import {
Alert,
CircularProgress,
Grid,
- Card,
- CardContent,
- Chip,
- Divider,
Dialog,
DialogTitle,
DialogContent,
@@ -23,1389 +19,47 @@ import {
LinearProgress,
Slider,
FormControl,
- InputLabel,
Select,
MenuItem,
Accordion,
AccordionSummary,
AccordionDetails,
- List,
- ListItem,
- ListItemText,
+ CssBaseline,
ThemeProvider,
- createTheme,
- Backdrop,
- Fade,
- Checkbox,
- FormControlLabel,
- Snackbar
+ useMediaQuery
} from '@mui/material';
import {
- Add as AddIcon,
- Delete as DeleteIcon,
+ Plus as AddIcon,
Upload as UploadIcon,
- PlayArrow as PlayIcon,
- Stop as StopIcon,
- Download as DownloadIcon,
- Refresh as RefreshIcon,
- ExpandMore as ExpandMoreIcon,
+ Play as PlayIcon,
+ Square as StopIcon,
+ Activity as ActivityIcon,
+ SlidersHorizontal as SlidersIcon,
+ Sparkles as SparklesIcon,
+ RefreshCw as RefreshIcon,
+ ChevronDown as ExpandMoreIcon,
CloudDownload as CloudDownloadIcon,
- Close as CloseIcon,
+ FolderOpen as FolderOpenIcon,
Info as InfoIcon,
- MenuBook as MenuBookIcon,
- Settings as SettingsIcon,
- FolderOpen as FolderOpenIcon
-} from '@mui/icons-material';
-import { useDropzone } from 'react-dropzone';
-import ReactPlayer from './react-player-config';
-import axios from 'axios';
-import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
-
-class AudioPlayerErrorBoundary extends Component {
- constructor(props) {
- super(props);
- this.state = { hasError: false };
- }
- static getDerivedStateFromError() {
- return { hasError: true };
- }
- componentDidCatch(error, info) {
- console.warn('ReactPlayer failed to load, using native audio:', error);
- }
- render() {
- if (this.state.hasError) {
- return this.props.audioUrl
- ?
- : null;
- }
- return this.props.children;
- }
-}
-
-const theme = createTheme({
- palette: {
- mode: 'dark',
- primary: {
- main: '#3a6fec',
- light: '#3a6fec',
- dark: '#3a6fec',
- contrastText: '#ffffff',
- },
- secondary: {
- main: '#9198A1',
- light: '#C9D1D9',
- dark: '#6E7681',
- contrastText: '#ffffff',
- },
- background: {
- default: '#0D1117',
- paper: '#161B22',
- },
- text: {
- primary: '#E6EDF3',
- secondary: '#9198A1',
- },
- divider: '#30363D',
- error: {
- main: '#DC5145',
- },
- warning: {
- main: '#EB8B3A',
- },
- success: {
- main: '#3A6FEC',
- },
- },
- typography: {
- fontFamily: [
- 'Helvetica Neue',
- 'Helvetica',
- 'Arial',
- 'sans-serif'
- ].join(','),
- h1: {
- fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
- fontWeight: 300,
- },
- h2: {
- fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
- fontWeight: 300,
- },
- h3: {
- fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
- fontWeight: 400,
- },
- h4: {
- fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
- fontWeight: 400,
- },
- h5: {
- fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
- fontWeight: 500,
- },
- h6: {
- fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
- fontWeight: 500,
- },
- body1: {
- fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
- fontWeight: 400,
- },
- body2: {
- fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
- fontWeight: 400,
- },
- button: {
- fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
- fontWeight: 500,
- },
- },
- components: {
- MuiCssBaseline: {
- styleOverrides: {
- body: {
- background: 'radial-gradient(ellipse at center, #0D1117 0%, #1C2128 50%, #0A0D10 100%)',
- minHeight: '100vh',
- },
- '*::-webkit-scrollbar': {
- width: '8px',
- height: '8px',
- },
- '*::-webkit-scrollbar-track': {
- background: '#30363D',
- borderRadius: '4px',
- },
- '*::-webkit-scrollbar-thumb': {
- background: '#484F58',
- borderRadius: '4px',
- '&:hover': {
- background: '#6E7681',
- },
- },
- '*::-webkit-scrollbar-corner': {
- background: '#30363D',
- },
- '*': {
- scrollbarWidth: 'thin',
- scrollbarColor: '#484F58 #30363D',
- },
- },
- },
- MuiPaper: {
- styleOverrides: {
- root: {
- backgroundColor: '#161B22',
- backgroundImage: 'none',
- border: '1px solid #30363D',
- },
- },
- },
- MuiCard: {
- styleOverrides: {
- root: {
- backgroundColor: '#161B22',
- backgroundImage: 'none',
- border: '1px solid #30363D',
- '&:hover': {
- borderColor: '#484F58',
- boxShadow: '0 8px 24px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.2)',
- },
- },
- },
- },
- MuiButton: {
- styleOverrides: {
- root: {
- textTransform: 'none',
- borderRadius: '8px',
- fontWeight: 500,
- },
- contained: {
- boxShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
- '&:hover': {
- boxShadow: '0 2px 6px rgba(0, 0, 0, 0.4)',
- },
- },
- outlined: {
- borderColor: '#30363D',
- '&:hover': {
- borderColor: '#3a6fec',
- backgroundColor: 'rgba(255, 107, 53, 0.08)',
- },
- },
- },
- },
- MuiTextField: {
- styleOverrides: {
- root: {
- '& .MuiOutlinedInput-root': {
- backgroundColor: '#0D1117',
- '& fieldset': {
- borderColor: '#30363D',
- },
- '&:hover fieldset': {
- borderColor: '#6E7681',
- },
- '&.Mui-focused fieldset': {
- borderColor: '#3a6fec',
- },
- },
- },
- },
- },
- MuiSelect: {
- styleOverrides: {
- root: {
- backgroundColor: '#0D1117',
- '& .MuiOutlinedInput-notchedOutline': {
- borderColor: '#30363D',
- },
- '&:hover .MuiOutlinedInput-notchedOutline': {
- borderColor: '#6E7681',
- },
- '&.Mui-focused .MuiOutlinedInput-notchedOutline': {
- borderColor: '#3a6fec',
- },
- },
- },
- },
- MuiMenuItem: {
- styleOverrides: {
- root: {
- backgroundColor: '#161B22',
- '&:hover': {
- backgroundColor: '#21262D',
- },
- '&.Mui-selected': {
- backgroundColor: 'rgba(53, 100, 255, 0.12)',
- '&:hover': {
- backgroundColor: 'rgba(53, 124, 255, 0.2)',
- },
- },
- },
- },
- },
- MuiChip: {
- styleOverrides: {
- root: {
- backgroundColor: '#21262D',
- color: '#E6EDF3',
- '&.MuiChip-colorPrimary': {
- backgroundColor: 'rgba(53, 134, 255, 0.2)',
- color: '#3a6fec',
- },
- },
- outlined: {
- borderColor: '#30363D',
- '&.MuiChip-colorPrimary': {
- borderColor: '#3a6fec',
- color: '#3a6fec'
- },
- },
- },
- },
- MuiAccordion: {
- styleOverrides: {
- root: {
- backgroundColor: '#161B22',
- border: '1px solid #30363D',
- '&:before': {
- display: 'none',
- },
- '&.Mui-expanded': {
- margin: 0,
- },
- },
- },
- },
- MuiAccordionSummary: {
- styleOverrides: {
- root: {
- backgroundColor: '#21262D',
- '&:hover': {
- backgroundColor: '#262C36',
- },
- },
- },
- },
- MuiDialog: {
- styleOverrides: {
- paper: {
- backgroundColor: '#161B22',
- border: '1px solid #30363D',
- borderRadius: 12,
- boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.5)',
- },
- },
- },
- MuiDialogTitle: {
- styleOverrides: {
- root: {
- backgroundColor: '#21262D',
- borderBottom: '1px solid #30363D',
- color: '#F0F6FC',
- fontWeight: 600,
- fontSize: '1.25rem',
- },
- },
- },
- MuiDialogContent: {
- styleOverrides: {
- root: {
- backgroundColor: '#161B22',
- color: '#C9D1D9',
- },
- },
- },
- MuiDialogActions: {
- styleOverrides: {
- root: {
- backgroundColor: '#161B22',
- borderTop: '1px solid #30363D',
- padding: '16px 24px',
- },
- },
- },
- MuiListItem: {
- styleOverrides: {
- root: {
- '&:hover': {
- backgroundColor: '#21262D',
- },
- '&.Mui-selected': {
- backgroundColor: 'rgba(53, 147, 255, 0.12)',
- '&:hover': {
- backgroundColor: 'rgba(53, 124, 255, 0.2)',
- },
- },
- },
- },
- },
- MuiCheckbox: {
- styleOverrides: {
- root: {
- color: '#6E7681',
- '&.Mui-checked': {
- color: '#3a6fec',
- },
- '&:hover': {
- backgroundColor: 'rgba(58, 111, 236, 0.08)',
- },
- },
- },
- },
- MuiFormControlLabel: {
- styleOverrides: {
- label: {
- color: '#C9D1D9',
- fontSize: '0.875rem',
- },
- },
- },
- MuiSlider: {
- styleOverrides: {
- root: {
- color: '#3a6fec',
- },
- rail: {
- backgroundColor: '#30363D',
- },
- track: {
- backgroundColor: '#3a6fec',
- },
- thumb: {
- backgroundColor: '#3a6fec',
- '&:hover': {
- boxShadow: '0 0 0 8px rgba(53, 134, 255, 0.16)',
- },
- },
- },
- },
- MuiLinearProgress: {
- styleOverrides: {
- root: {
- backgroundColor: '#30363D',
- },
- bar: {
- backgroundColor: '#3a6fec',
- },
- },
- },
- MuiCircularProgress: {
- styleOverrides: {
- root: {
- color: '#3a6fec',
- },
- },
- },
- MuiTabs: {
- styleOverrides: {
- root: {
- '& .MuiTabs-indicator': {
- backgroundColor: '#3a6fec',
- },
- },
- },
- },
- MuiTab: {
- styleOverrides: {
- root: {
- color: '#9198A1',
- '&.Mui-selected': {
- color: '#3a6fec',
- },
- '&:hover': {
- color: '#E6EDF3',
- },
- },
- },
- },
- MuiBackdrop: {
- styleOverrides: {
- root: {
- backgroundColor: 'rgba(0, 0, 0, 0.8)',
- },
- },
- },
- MuiDivider: {
- styleOverrides: {
- root: {
- borderColor: '#30363D',
- },
- },
- },
- MuiIconButton: {
- styleOverrides: {
- root: {
- color: '#9198A1',
- '&:hover': {
- backgroundColor: 'rgba(255, 107, 53, 0.08)',
- color: '#3a6fec',
- },
- },
- },
- },
- MuiContainer: {
- styleOverrides: {
- root: {
- backgroundColor: 'transparent',
- background: 'transparent',
- },
- },
- },
- },
-});
-
-function TabPanel({ children, value, index, ...other }) {
- return (
-
- {value === index && (
-
- {children}
-
- )}
-
- );
-}
-
-function AudioUploadRow({ index, data, onChange, onRemove }) {
- const [audioFile, setAudioFile] = useState(null);
- const [audioUrl, setAudioUrl] = useState('');
-
- useEffect(() => {
- if (!data.file && !data.audioUrl) {
- if (audioUrl) {
- URL.revokeObjectURL(audioUrl);
- }
- setAudioFile(null);
- setAudioUrl('');
- }
- }, [data.file, data.audioUrl, audioUrl]);
-
- const { getRootProps, getInputProps, isDragActive } = useDropzone({
- accept: {
- 'audio/*': ['.mp3', '.wav', '.flac', '.m4a', '.aac']
- },
- multiple: false,
- onDrop: (acceptedFiles) => {
- const file = acceptedFiles[0];
- setAudioFile(file);
- setAudioUrl(URL.createObjectURL(file));
- onChange(index, { ...data, file, audioUrl: URL.createObjectURL(file) });
- }
- });
-
- return (
-
-
-
-
-
-
- {audioFile ? (
-
-
- {audioFile.name}
-
- {audioUrl && (
-
-
- }>
-
-
-
- )}
-
- ) : (
-
-
-
- {isDragActive ? 'Drop audio here' : 'Click or drag audio file'}
-
-
- )}
-
-
-
-
- onChange(index, { ...data, prompt: e.target.value })}
- variant="outlined"
- />
-
-
-
- onRemove(index)}
- sx={{ alignSelf: 'flex-start' }}
- >
-
-
-
-
-
-
- );
-}
-
-function formatDuration(seconds) {
- const sec = Math.floor(seconds % 60);
- const min = Math.floor((seconds / 60) % 60);
- const hr = Math.floor(seconds / 3600);
- return [hr, min, sec]
- .map((v, i) => (i === 0 ? v : v.toString().padStart(2, '0')))
- .join(':');
-}
-
-function TrainingMonitor({
- isTraining,
- trainingProgress,
- trainingStatus,
- trainingHistory,
- trainingStartTime,
- trainingError,
- trainingConfig,
- systemStatus
-}) {
- const getElapsedTime = () => {
- if (!trainingStartTime) return 0;
- return Math.floor((Date.now() - trainingStartTime) / 1000);
- };
-
- const getEstimatedTimeRemaining = () => {
- if (!trainingStartTime || trainingProgress === 0) return null;
- const elapsed = getElapsedTime();
- const estimatedTotal = (elapsed / trainingProgress) * 100;
- return Math.max(0, estimatedTotal - elapsed);
- };
-
- const getProgressColor = () => {
- if (trainingError) return 'error';
- if (trainingProgress === 100) return 'success';
- return 'primary';
- };
-
- return (
-
-
-
-
- Training Monitor
-
- {isTraining && (
-
- )}
-
-
-
-
- Progress
- {trainingProgress}%
-
-
-
-
- {trainingStatus?.device_info && (
-
-
- Device Used for Training
-
-
- Device: {trainingStatus.device_info.device} ({trainingStatus.device_info.memory_gb?.toFixed(2)}GB VRAM)
-
-
- Info: {trainingStatus.device_info.type === 'cuda' ? 'CUDA GPU available and selected for training' :
- trainingStatus.device_info.type === 'cpu' ? 'Using CPU (no CUDA GPU available or compatible)' :
- 'Using MPS (Apple Silicon GPU)'}
-
-
- )}
-
-
-
- Current Epoch
-
- {trainingStatus?.current_epoch !== undefined ?
- `${trainingStatus.current_epoch + 1} / ${trainingConfig.epochs}` :
- '0 / ' + trainingConfig.epochs}
-
-
-
- Global Step / Total Steps
-
- {trainingStatus?.global_step !== undefined && trainingStatus?.total_steps !== undefined ?
- `${trainingStatus.global_step} / ${trainingStatus.total_steps}` :
- 'N/A'}
-
-
-
- Checkpoints Saved
-
- {trainingStatus?.checkpoints_saved || 0}
-
-
-
- Current Loss
-
- {trainingStatus?.loss ? parseFloat(trainingStatus.loss).toFixed(4) : 'N/A'}
-
-
-
-
-
- {trainingStatus?.loss_history && trainingStatus.loss_history.length > 0 && (
-
-
- Loss History
-
-
-
-
-
- `${Math.floor(value / 60)}:${(value % 60).toString().padStart(2, '0')}`}
- label={{ value: 'Time (min:sec)', position: 'insideBottom', offset: -5 }}
- />
-
- `Time: ${Math.floor(value / 60)}:${(value % 60).toString().padStart(2, '0')}`}
- formatter={(value, name) => [value.toFixed(4), 'Loss']}
- />
-
-
-
-
-
- )}
-
- {trainingError && (
-
-
- Training Error: {trainingError}
-
-
- )}
-
-
- );
-}
-
-function ModelUnwrapButton({ model, onUnwrap, onRefresh }) {
- const [loading, setLoading] = useState(false);
- const [result, setResult] = useState(null);
- const [error, setError] = useState(null);
-
- const handleUnwrap = async () => {
- setLoading(true);
- setResult(null);
- setError(null);
-
- try {
- const response = await axios.post('/api/unwrap-model', {
- model_config: model.configPath,
- ckpt_path: model.ckptPath,
- name: model.name + '_unwrapped'
- });
- setResult(response.data);
- if (onUnwrap) onUnwrap(response.data);
- if (onRefresh) onRefresh(); // Refresh model list after unwrapping
- } catch (err) {
- console.error('Unwrap error:', err);
- setError(err.response?.data?.error || err.message);
- } finally {
- setLoading(false);
- }
- };
-
- return (
-
-
}
- onClick={handleUnwrap}
- disabled={loading}
- >
- {loading ? 'Unwrapping...' : 'Unwrap for Inference'}
-
- {result && result.unwrapped_path && (
-
- )}
- {error && (
-
{error}
- )}
-
- );
-}
-
-function CheckpointManager({ model, onRefresh }) {
- const [loadingStates, setLoadingStates] = useState({});
- const [error, setError] = useState(null);
- const [expandedCheckpoint, setExpandedCheckpoint] = useState(null);
-
- const handleUnwrapCheckpoint = async (checkpoint) => {
- const checkpointId = checkpoint.path;
- setLoadingStates(prev => ({ ...prev, [checkpointId]: { unwrapping: true } }));
- setError(null);
- try {
- const response = await axios.post('/api/unwrap-model', {
- model_config: model.config_path,
- ckpt_path: checkpoint.path,
- name: `${checkpoint.name}_unwrapped`
- });
- setError(null);
- alert(`Checkpoint "${checkpoint.name}" unwrapped successfully!`);
- onRefresh();
- } catch (err) {
- setError(`Failed to unwrap ${checkpoint.name}: ${err.response?.data?.error || err.message}`);
- } finally {
- setLoadingStates(prev => ({ ...prev, [checkpointId]: { unwrapping: false } }));
- }
- };
-
- const handleDeleteCheckpoint = async (checkpoint) => {
- if (!confirm(`Are you sure you want to delete the wrapped checkpoint "${checkpoint.name}"? This action cannot be undone.`)) {
- return;
- }
- const checkpointId = checkpoint.path;
- setLoadingStates(prev => ({ ...prev, [checkpointId]: { deleting: true } }));
- setError(null);
- try {
- await axios.post('/api/delete-checkpoint', {
- checkpoint_path: checkpoint.path
- });
- alert(`Checkpoint "${checkpoint.name}" deleted successfully.`);
- onRefresh();
- } catch (err) {
- setError(`Failed to delete ${checkpoint.name}: ${err.response?.data?.error || err.message}`);
- } finally {
- setLoadingStates(prev => ({ ...prev, [checkpointId]: { deleting: false } }));
- }
- };
-
- const checkpoints = model.checkpoints || [];
-
- return (
-
-
- Checkpoint Management for {model.name}
-
-
- {checkpoints.length === 0 ? (
-
- No checkpoints found for this model.
-
- ) : (
- <>
-
- Available Checkpoints: {checkpoints.length}
-
-
- Unwrapped Models: {model.unwrapped_models?.length || 0}
-
-
- {/* Individual Checkpoint Cards */}
-
- {checkpoints.map((checkpoint, index) => {
- const checkpointId = checkpoint.path;
- const isUnwrapping = loadingStates[checkpointId]?.unwrapping;
- const isDeleting = loadingStates[checkpointId]?.deleting;
-
- const hasUnwrappedVersion = model.unwrapped_models?.some(unwrapped =>
- unwrapped.name.includes(checkpoint.name) ||
- checkpoint.name.includes(unwrapped.name.replace('_unwrapped', ''))
- );
-
- return (
-
-
-
-
- {checkpoint.name}
- {hasUnwrappedVersion && (
-
- )}
-
-
- Size: {checkpoint.size_mb} MB
-
- {checkpoint.epoch !== undefined && (
-
- | Epoch: {checkpoint.epoch}
-
- )}
- {checkpoint.step !== undefined && (
-
- | Step: {checkpoint.step}
-
- )}
-
-
- {!hasUnwrappedVersion && (
- }
- onClick={() => handleUnwrapCheckpoint(checkpoint)}
- disabled={isUnwrapping || isDeleting}
- >
- {isUnwrapping ? 'Unwrapping...' : 'Unwrap'}
-
- )}
-
- {hasUnwrappedVersion && (
- }
- onClick={() => handleDeleteCheckpoint(checkpoint)}
- disabled={isDeleting}
- >
- {isDeleting ? 'Deleting Wrapped...' : 'Delete Wrapped Checkpoint'}
-
- )}
-
-
-
- );
- })}
-
- >
- )}
-
- {error && (
- {error}
- )}
-
- );
-}
-
-function GeneratedFragmentsWindow({ fragments, onDownload, isDocker, onDockerMessage }) {
- const [playingFragment, setPlayingFragment] = useState(null);
- const audioRefs = useRef({});
-
- const handlePlayPause = (fragment) => {
- const audio = audioRefs.current[fragment.id];
- if (!audio) return;
-
- if (playingFragment === fragment.id) {
- audio.pause();
- setPlayingFragment(null);
- } else {
- if (playingFragment && audioRefs.current[playingFragment]) {
- audioRefs.current[playingFragment].pause();
- }
- audio.play();
- setPlayingFragment(fragment.id);
- }
- };
-
- const setAudioRef = useCallback((fragmentId, audioElement) => {
- if (audioElement) {
- audioRefs.current[fragmentId] = audioElement;
- }
- }, []);
-
- return (
-
-
-
- Generated Fragments ({fragments.length})
-
-
-
-
- {fragments.length === 0 ? (
-
-
- No fragments generated yet
-
-
- ) : (
-
- {fragments.slice().reverse().map((fragment, index) => (
-
-
-
-
- {fragment.prompt}
-
-
- {fragment.duration}s • {fragment.timestamp}
-
-
-
- handlePlayPause(fragment)}
- color={playingFragment === fragment.id ? "primary" : "default"}
- sx={{
- border: '1px solid',
- borderColor: playingFragment === fragment.id ? 'primary.main' : 'divider'
- }}
- >
- {playingFragment === fragment.id ? : }
-
- }
- onClick={() => onDownload(fragment)}
- >
- Download
-
-
-
-
-
- ))}
-
- )}
-
- );
-}
-
-function WelcomePage({ open, onClose }) {
- const [titleVisible, setTitleVisible] = useState(false);
- const [textVisible, setTextVisible] = useState(false);
-
- useEffect(() => {
- if (open) {
- const titleTimer = setTimeout(() => {
- setTitleVisible(true);
- }, 500);
-
- const textTimer = setTimeout(() => {
- setTextVisible(true);
- }, 1500);
-
- return () => {
- clearTimeout(titleTimer);
- clearTimeout(textTimer);
- };
- } else {
- setTitleVisible(false);
- setTextVisible(false);
- }
- }, [open]);
-
- if (!open) return null;
-
- return (
-
-
- e.stopPropagation()}
-
- >
-
-
-
-
-
-
- Welcome to Fragmenta!
-
-
-
-
-
-
- An End-to-End Pipeline to Fine-Tune and Use Text-to-Audio Models.
-
-
-
-
-
- Made for composers and audio creators.
-
-
-
- @2025 Misagh Azimi
-
-
- Version 0.0.1
-
-
-
- or click anywhere to continue
-
-
-
-
-
- );
-}
+ BookOpen as BookOpenIcon,
+ Moon as MoonIcon,
+ Sun as SunIcon
+} from 'lucide-react';
+import api from './api';
+import HfAuthDialog from './components/HfAuthDialog';
+import TabPanel from './components/TabPanel';
+import AudioUploadRow from './components/AudioUploadRow';
+import BulkAnnotatePanel from './components/BulkAnnotatePanel';
+import TrainingMonitor from './components/TrainingMonitor';
+import ModelUnwrapButton from './components/ModelUnwrapButton';
+import CheckpointManager from './components/CheckpointManager';
+import GeneratedFragmentsWindow from './components/GeneratedFragmentsWindow';
+import WelcomePage from './components/WelcomePage';
+import { formatDuration } from './utils/format';
+import theme, { appStyles, lightTheme } from './theme';
+
+const COLOR_MODE_STORAGE_KEY = 'fragmenta-color-mode';
+const HIDE_WELCOME_PAGE_KEY = 'fragmenta-hide-welcome';
function App() {
const [tabValue, setTabValue] = useState(0);
@@ -1417,29 +71,32 @@ function App() {
const [processedCount, setProcessedCount] = useState(0);
const [chunksPreview, setChunksPreview] = useState([]);
- const [showWelcomePage, setShowWelcomePage] = useState(true);
-
- // Docker mode state
- const [isDocker, setIsDocker] = useState(false);
- const [showDownloadDialog, setShowDownloadDialog] = useState(false);
- const [showAboutDialog, setShowAboutDialog] = useState(false);
- const [showOutputFilesDialog, setShowOutputFilesDialog] = useState(false);
- const [outputFiles, setOutputFiles] = useState([]);
- const [downloadingModels, setDownloadingModels] = useState({});
- const [downloadProgress, setDownloadProgress] = useState({});
- const [dockerSnackbar, setDockerSnackbar] = useState({ open: false, message: '' });
- const [hfToken, setHfToken] = useState('');
- const [hfAuthStatus, setHfAuthStatus] = useState({ authenticated: false, username: null });
- const [hfTokenSaving, setHfTokenSaving] = useState(false);
- const [hfTokenError, setHfTokenError] = useState('');
+ const [showWelcomePage, setShowWelcomePage] = useState(
+ () => window.localStorage.getItem(HIDE_WELCOME_PAGE_KEY) !== 'true'
+ );
+ const [authDialogOpen, setAuthDialogOpen] = useState(false);
+ const [showInfoDialog, setShowInfoDialog] = useState(false);
+ const [isOpeningDocumentation, setIsOpeningDocumentation] = useState(false);
+ const [colorMode, setColorMode] = useState(() => {
+ if (typeof window === 'undefined') {
+ return 'dark';
+ }
+
+ const savedMode = window.localStorage.getItem(COLOR_MODE_STORAGE_KEY);
+ if (savedMode === 'light' || savedMode === 'dark') {
+ return savedMode;
+ }
+
+ return 'dark';
+ });
const [trainingConfig, setTrainingConfig] = useState({
- epochs: 50,
- checkpointSteps: 100,
+ epochs: 30,
+ checkpointSteps: 50,
batchSize: 4,
learningRate: 1e-4,
modelName: 'my_fine_tuned_model',
- baseModel: 'stable-audio-open-small',
+ baseModel: '',
saveWrappedCheckpoint: false
});
const [isTraining, setIsTraining] = useState(false);
@@ -1516,6 +173,17 @@ function App() {
const [uploadKey, setUploadKey] = useState(0);
const [isFreeingGPU, setIsFreeingGPU] = useState(false);
const [showFreeGPUDialog, setShowFreeGPUDialog] = useState(false);
+ const [modelWarning, setModelWarning] = useState({
+ open: false,
+ title: '',
+ message: '',
+ canOpenModels: false,
+ });
+ const appTheme = useMemo(
+ () => (colorMode === 'light' ? lightTheme : theme),
+ [colorMode]
+ );
+ const isCompactLayout = useMediaQuery(appTheme.breakpoints.down('md'));
useEffect(() => {
setSelectedUnwrappedModel('');
@@ -1577,7 +245,7 @@ function App() {
const fetchSystemStatus = async () => {
try {
- const response = await axios.get('/api/status');
+ const response = await api.get('/api/status');
setSystemStatus(response.data);
} catch (error) {
console.error('Error fetching system status:', error);
@@ -1586,7 +254,7 @@ function App() {
const fetchAvailableModels = async () => {
try {
- const response = await axios.get('/api/models');
+ const response = await api.get('/api/models');
console.log('Fetched models:', response.data.models);
setAvailableModels(response.data.models || []);
} catch (error) {
@@ -1596,10 +264,10 @@ function App() {
const fetchBaseModelsStatus = async () => {
try {
- const response = await axios.get('/api/base-models/status');
+ const response = await api.get('/api/base-models/status');
const baseModelsStatus = response.data.base_models;
-
- setBaseModels(prevModels =>
+
+ setBaseModels(prevModels =>
prevModels.map(model => ({
...model,
downloaded: baseModelsStatus[model.name]?.downloaded || false
@@ -1620,7 +288,7 @@ function App() {
const fetchGpuMemoryStatus = async () => {
try {
setIsUpdatingGpuMemory(true);
- const response = await axios.get('/api/gpu-memory-status');
+ const response = await api.get('/api/gpu-memory-status');
console.log('GPU Memory Response:', response.data);
setGpuMemoryStatus(response.data.memory_info);
} catch (error) {
@@ -1638,164 +306,23 @@ function App() {
fetchGpuMemoryStatus();
}, []);
- // Detect Docker mode on mount
useEffect(() => {
- const fetchEnvironment = async () => {
- try {
- const response = await axios.get('/api/environment');
- const docker = response.data.docker === true;
- setIsDocker(docker);
- // If Docker, also check HF auth status
- if (docker) {
- try {
- const authResp = await axios.get('/api/hf-token/status');
- setHfAuthStatus(authResp.data);
- } catch (e) {
- console.error('Error checking HF auth status:', e);
- }
- }
- } catch (error) {
- console.error('Error fetching environment:', error);
- }
- };
- fetchEnvironment();
- }, []);
-
- // Check HF auth status when download dialog opens
- const checkHfAuthStatus = async () => {
- try {
- const response = await axios.get('/api/hf-token/status');
- setHfAuthStatus(response.data);
- return response.data.authenticated;
- } catch (e) {
- return false;
- }
- };
+ const interval = setInterval(() => {
+ fetchGpuMemoryStatus();
+ }, isTraining ? 2000 : 10000);
- // Save HF token
- const handleSaveHfToken = async () => {
- if (!hfToken.trim()) return;
- setHfTokenSaving(true);
- setHfTokenError('');
- try {
- const response = await axios.post('/api/hf-token', { token: hfToken.trim() });
- if (response.data.success) {
- setHfAuthStatus({ authenticated: true, username: response.data.username });
- setHfToken('');
- setDockerSnackbar({ open: true, message: `Authenticated as ${response.data.username}` });
- }
- } catch (error) {
- const errMsg = error.response?.data?.error || 'Failed to set token';
- setHfTokenError(errMsg);
- } finally {
- setHfTokenSaving(false);
- }
- };
+ return () => clearInterval(interval);
+ }, [isTraining]);
- // Show download dialog in Docker mode when welcome page dismissed and no models downloaded
useEffect(() => {
- if (!showWelcomePage && isDocker) {
- // Fresh API check to avoid race with stale initial state
- const checkModels = async () => {
+ let statusInterval;
+
+ if (isTraining) {
+ statusInterval = setInterval(async () => {
try {
- const response = await axios.get('/api/base-models/status');
- const statuses = response.data.base_models;
- // Update baseModels with fresh data
- setBaseModels(prevModels =>
- prevModels.map(model => ({
- ...model,
- downloaded: statuses[model.name]?.downloaded || false
- }))
- );
- const allNotDownloaded = Object.values(statuses).every(m => !m.downloaded);
- if (allNotDownloaded) {
- setShowDownloadDialog(true);
- }
- } catch (error) {
- // API error — fall back to current state
- const allNotDownloaded = baseModels.every(m => !m.downloaded);
- if (allNotDownloaded) {
- setShowDownloadDialog(true);
- }
- }
- };
- checkModels();
- }
- }, [showWelcomePage, isDocker]); // eslint-disable-line
-
- // HF model page URLs for terms acceptance
- const modelPageUrls = {
- 'stable-audio-open-small': 'https://huggingface.co/stabilityai/stable-audio-open-small',
- 'stable-audio-open-1.0': 'https://huggingface.co/stabilityai/stable-audio-open-1.0',
- };
-
- // Helper: accept terms then download a model (async with progress polling)
- const handleDockerModelDownload = async (modelId) => {
- setDownloadingModels(prev => ({ ...prev, [modelId]: 'downloading' }));
- setDownloadProgress(prev => ({ ...prev, [modelId]: { percent: 0, message: 'Starting...' } }));
- try {
- // Accept terms first
- await axios.post(`/api/models/${modelId}/accept-terms`);
- // Kick off download (backend runs it in background thread)
- const response = await axios.post(`/api/models/${modelId}/download`);
- if (!response.data.success) {
- setDownloadingModels(prev => ({ ...prev, [modelId]: 'error' }));
- return;
- }
-
- // Poll for progress
- const pollInterval = setInterval(async () => {
- try {
- const prog = await axios.get(`/api/models/${modelId}/download/progress`);
- const { percent, message, status } = prog.data;
- setDownloadProgress(prev => ({ ...prev, [modelId]: { percent, message } }));
-
- if (status === 'done') {
- clearInterval(pollInterval);
- setDownloadingModels(prev => ({ ...prev, [modelId]: 'done' }));
- await fetchBaseModelsStatus();
- } else if (status === 'error') {
- clearInterval(pollInterval);
- setDownloadingModels(prev => ({ ...prev, [modelId]: 'error' }));
- setDownloadProgress(prev => ({ ...prev, [modelId]: { percent: 0, message } }));
- }
- } catch (err) {
- console.error('Error polling progress:', err);
- }
- }, 1500);
- } catch (error) {
- console.error(`Error downloading model ${modelId}:`, error);
- setDownloadingModels(prev => ({ ...prev, [modelId]: 'error' }));
- }
- };
-
- // Helper: fetch output files list (Docker)
- const fetchOutputFiles = async () => {
- try {
- const response = await axios.get('/api/output-files');
- setOutputFiles(response.data.files || []);
- } catch (error) {
- console.error('Error fetching output files:', error);
- }
- };
-
- useEffect(() => {
- const interval = setInterval(() => {
- fetchGpuMemoryStatus();
- }, isTraining ? 2000 : 10000);
-
- return () => clearInterval(interval);
- }, [isTraining]);
-
- useEffect(() => {
- let statusInterval;
-
- if (isTraining) {
- statusInterval = setInterval(async () => {
- try {
- const statusResponse = await axios.get('/api/training-status');
- const currentStatus = statusResponse.data;
- setTrainingStatus(currentStatus);
+ const statusResponse = await api.get('/api/training-status');
+ const currentStatus = statusResponse.data;
+ setTrainingStatus(currentStatus);
if (currentStatus.progress !== undefined) {
setTrainingProgress(prevProgress => {
@@ -1876,9 +403,7 @@ function App() {
}
});
- const response = await axios.post('/api/process-files', formData, {
- headers: { 'Content-Type': 'multipart/form-data' }
- });
+ const response = await api.post('/api/process-files', formData);
setProcessingStatus(response.data.message);
setProcessedCount(response.data.processed_count);
@@ -1895,19 +420,40 @@ function App() {
};
const startTraining = async () => {
+ const selectedBaseModel = baseModels.find(m => m.name === trainingConfig.baseModel);
+ if (!selectedBaseModel) {
+ showModelWarning({
+ title: 'Base Model Required',
+ message: 'Please select a base model before starting training.',
+ canOpenModels: false,
+ });
+ return;
+ }
+
+ if (!selectedBaseModel.downloaded) {
+ showModelWarning({
+ title: 'Base Model Not Downloaded',
+ message: `The selected base model "${selectedBaseModel.displayName}" is not downloaded.`,
+ canOpenModels: true,
+ });
+ return;
+ }
+
setIsTraining(true);
setTrainingProgress(0);
setTrainingError(null);
setTrainingStartTime(Date.now());
setTrainingHistory([]);
+ await api.post('/api/bulk-annotate/unload-clap').catch(() => {});
+
try {
- const response = await axios.post('/api/start-training', trainingConfig);
+ const response = await api.post('/api/start-training', trainingConfig);
setProcessingStatus('Training started successfully!');
} catch (error) {
const errorData = error.response?.data;
const errorMessage = errorData?.error || error.message;
-
+
if (errorData?.checkpoint_warning) {
setTrainingError(errorMessage);
setProcessingStatus(errorMessage);
@@ -1921,7 +467,7 @@ function App() {
const stopTraining = async () => {
try {
- const response = await axios.post('/api/stop-training');
+ const response = await api.post('/api/stop-training');
setProcessingStatus('Training stopped gracefully');
setIsTraining(false);
setTrainingProgress(0);
@@ -1951,6 +497,15 @@ function App() {
const baseModel = baseModels.find(m => m.name === selectedModel);
if (baseModel) {
+ if (!baseModel.downloaded) {
+ showModelWarning({
+ title: 'Base Model Not Downloaded',
+ message: `The selected base model "${baseModel.displayName}" is not downloaded.`,
+ canOpenModels: true,
+ });
+ return;
+ }
+
requestData.model_name = selectedModel;
console.log('FRONTEND: Using base model:', selectedModel);
console.log('FRONTEND: Base model details:', baseModel);
@@ -1968,6 +523,8 @@ function App() {
console.log('FRONTEND: Final request data:', requestData);
+ await api.post('/api/bulk-annotate/unload-clap').catch(() => {});
+
setIsGenerating(true);
setGenerationProgress(0);
setProcessingStatus('Starting audio generation...');
@@ -1983,7 +540,7 @@ function App() {
try {
console.log('FRONTEND: Sending request to /api/generate with data:', requestData);
- const response = await axios.post('/api/generate', requestData, {
+ const response = await api.post('/api/generate', requestData, {
responseType: 'blob'
});
@@ -2029,7 +586,7 @@ function App() {
setShowStartFreshDialog(false);
try {
- const response = await axios.post('/api/start-fresh');
+ const response = await api.post('/api/start-fresh');
setUploadRows([{ file: null, prompt: '', audioUrl: '' }]);
setProcessedCount(0);
@@ -2056,7 +613,7 @@ function App() {
setIsFreeingGPU(true);
setShowFreeGPUDialog(false);
try {
- const response = await axios.post('/api/free-gpu-memory');
+ const response = await api.post('/api/free-gpu-memory');
setProcessingStatus(`GPU Memory Freed: ${response.data.message}`);
if (response.data.memory_info && response.data.memory_info.cuda) {
@@ -2072,6 +629,45 @@ function App() {
}
};
+ const handleOpenOutputFolder = async () => {
+ try {
+ const response = await api.post('/api/open-output-folder');
+ if (!response.data.success) {
+ setProcessingStatus(`Open output folder error: ${response.data.error || 'Unknown error'}`);
+ }
+ } catch (error) {
+ setProcessingStatus(`Open output folder error: ${error.response?.data?.error || error.message}`);
+ }
+ };
+
+ const handleOpenDocumentation = async (docKey = 'about') => {
+ try {
+ setIsOpeningDocumentation(true);
+ const response = await api.post('/api/open-documentation', { doc_key: docKey });
+ if (!response.data.success) {
+ setProcessingStatus(`Open documentation error: ${response.data.error || 'Unknown error'}`);
+ return;
+ }
+ if (response.data.message) {
+ setProcessingStatus(response.data.message);
+ }
+ } catch (error) {
+ setProcessingStatus(`Open documentation error: ${error.response?.data?.error || error.message}`);
+ } finally {
+ setIsOpeningDocumentation(false);
+ }
+ };
+
+ const toggleColorMode = () => {
+ setColorMode((prevMode) => {
+ const nextMode = prevMode === 'light' ? 'dark' : 'light';
+ if (typeof window !== 'undefined') {
+ window.localStorage.setItem(COLOR_MODE_STORAGE_KEY, nextMode);
+ }
+ return nextMode;
+ });
+ };
+
const getSelectedModelDisplayName = () => {
console.log('=== GETTING DISPLAY NAME ===');
console.log('selectedModel:', selectedModel);
@@ -2112,25 +708,80 @@ function App() {
setSelectedModel(newSelectedModel);
setSelectedUnwrappedModel('');
+
+ const selectedBaseModel = baseModels.find(m => m.name === newSelectedModel);
+ if (selectedBaseModel && !selectedBaseModel.downloaded) {
+ showModelWarning({
+ title: 'Base Model Not Downloaded',
+ message: `The selected base model "${selectedBaseModel.displayName}" is not downloaded.`,
+ canOpenModels: true,
+ });
+ }
+ };
+
+ const handleTrainingBaseModelChange = (event) => {
+ const newBaseModel = event.target.value;
+ setTrainingConfig({
+ ...trainingConfig,
+ baseModel: newBaseModel,
+ });
+
+ const selectedBaseModel = baseModels.find(m => m.name === newBaseModel);
+ if (selectedBaseModel && !selectedBaseModel.downloaded) {
+ showModelWarning({
+ title: 'Base Model Not Downloaded',
+ message: `The selected base model "${selectedBaseModel.displayName}" is not downloaded.`,
+ canOpenModels: true,
+ });
+ }
};
+ const showModelWarning = ({ title, message, canOpenModels = false }) => {
+ setModelWarning({
+ open: true,
+ title,
+ message,
+ canOpenModels,
+ });
+ };
+
+ const closeModelWarning = () => {
+ setModelWarning(prev => ({ ...prev, open: false }));
+ };
+
+ const handleOpenModelsFromWarning = () => {
+ closeModelWarning();
+ setAuthDialogOpen(true);
+ };
+
+ const getTrainingIndicatorState = () => {
+ if (trainingError) {
+ return { status: 'error', label: 'Error', animate: false };
+ }
+ if (isTraining) {
+ return { status: 'live', label: 'Live', animate: true };
+ }
+ if (trainingProgress === 100) {
+ return { status: 'complete', label: 'Complete', animate: false };
+ }
+ return { status: 'idle', label: 'Idle', animate: false };
+ };
+
+ const trainingIndicatorState = getTrainingIndicatorState();
+
return (
-
-
+
+
+
{
+ onClose={(dontShowAgain) => {
setShowWelcomePage(false);
-
- axios.post('http://127.0.0.1:5001/api/welcome-page-closed')
+ if (dontShowAgain) {
+ window.localStorage.setItem(HIDE_WELCOME_PAGE_KEY, 'true');
+ }
+
+ api.post('/api/welcome-page-closed')
.then(() => {
console.log('Welcome page closure signal sent successfully');
})
@@ -2140,1526 +791,985 @@ function App() {
}}
/>
- {/* Docker Mode Menu Bar */}
- {isDocker && (
-
- }
- onClick={() => setShowDownloadDialog(true)}
- sx={{ textTransform: 'none', fontSize: '0.8rem', color: 'text.secondary', '&:hover': { color: 'text.primary' } }}
- >
- Model Setup
-
- }
- onClick={() => { fetchOutputFiles(); setShowOutputFilesDialog(true); }}
- sx={{ textTransform: 'none', fontSize: '0.8rem', color: 'text.secondary', '&:hover': { color: 'text.primary' } }}
- >
- Output Files
-
- }
- onClick={() => window.open('https://github.com/MAz-Codes/Fragmenta', '_blank')}
- sx={{ textTransform: 'none', fontSize: '0.8rem', color: 'text.secondary', '&:hover': { color: 'text.primary' } }}
- >
- Documentation
-
- }
- onClick={() => setShowAboutDialog(true)}
- sx={{ textTransform: 'none', fontSize: '0.8rem', color: 'text.secondary', '&:hover': { color: 'text.primary' } }}
- >
- About
-
-
- )}
-
-
-
-
- {/* Logo */}
-
-
- {/* Title */}
-
-
- Fragmenta
-
-
-
-
-
- {/* Action Buttons - Left Side */}
-
- }
- onClick={() => setShowFreeGPUDialog(true)}
- disabled={isFreeingGPU || !(gpuMemoryStatus && gpuMemoryStatus.cuda)}
- sx={{
- fontSize: '0.65rem',
- py: 0.25,
- px: 1,
- minWidth: 90,
- height: 28,
- opacity: !(gpuMemoryStatus && gpuMemoryStatus.cuda) ? 0.5 : 1
- }}
- >
- {isFreeingGPU ? 'Freeing...' : 'Free GPU'}
-
- }
- onClick={() => setShowStartFreshDialog(true)}
- disabled={isStartingFresh}
- sx={{
- fontSize: '0.65rem',
- py: 0.25,
- px: 1,
- minWidth: 90,
- height: 28
- }}
- >
- {isStartingFresh ? 'Starting...' : 'Fresh Start'}
-
-
+
+
+
+ {/* Logo */}
+
- {/* GPU Memory Status - Right Side */}
-
- {gpuMemoryStatus && gpuMemoryStatus.cuda ? (
- <>
- {/* Status Indicator */}
- 2 ? 'success.main' :
- gpuMemoryStatus.cuda.free > 0.5 ? 'warning.main' : 'error.main'
- }} />
-
- {/* Header */}
-
-
- GPU Memory
-
-
- 2 ? 'success.main' :
- gpuMemoryStatus.cuda.free > 0.5 ? 'warning.main' : 'error.main',
- animation: 'pulse 2s infinite',
- '@keyframes pulse': {
- '0%': { opacity: 1 },
- '50%': { opacity: 0.5 },
- '100%': { opacity: 1 }
- }
- }} />
-
- {gpuMemoryStatus.cuda.free > 2 ? 'Good' :
- gpuMemoryStatus.cuda.free > 0.5 ? 'Low' : 'Critical'}
-
-
-
-
- {/* Memory Bar */}
-
-
- {/* Used Memory */}
-
- {/* Cached Memory */}
-
-
-
+ {/* Title */}
+
+
+ Fragmenta
+
- {/* Memory Details */}
-
-
- {gpuMemoryStatus.cuda.free.toFixed(1)}GB free
-
-
- {gpuMemoryStatus.cuda.total.toFixed(1)}GB total
-
-
- >
- ) : (
- <>
- {/* Status Indicator - No GPU */}
-
-
- {/* Header */}
-
-
- GPU Status
-
-
-
-
- No GPU
-
-
-
-
- {/* No GPU Message */}
-
- No CUDA GPU detected
-
-
- Using CPU for processing
-
- >
- )}
+
-
-
-
- {/* Main Content with Sidebar Layout */}
-
- {/* Left Sidebar with Vertical Tabs */}
-
-
-
-
-
-
-
-
- {/* Main Content Area */}
-
-
- {/* Data Processing Tab */}
-
-
-
-
-
- Upload Audio Files with Annotations
-
-
- {uploadRows.map((row, index) => (
-
- ))}
-
- }
- onClick={addUploadRow}
- sx={{ mb: 3 }}
+
+
+
+ }
+ onClick={() => setAuthDialogOpen(true)}
+ sx={appStyles.headerActionButton}
>
- Add Another Row
+ Get Models
-
: }
- fullWidth
+ color="primary"
+ size="small"
+ startIcon={}
+ onClick={() => setShowFreeGPUDialog(true)}
+ disabled={isFreeingGPU || !(gpuMemoryStatus && gpuMemoryStatus.cuda)}
+ sx={appStyles.headerActionButtonWithOpacity(Boolean(gpuMemoryStatus && gpuMemoryStatus.cuda))}
>
- {isProcessing ? 'Processing...' : 'Process Files'}
+ {isFreeingGPU ? 'Freeing...' : 'Free GPU'}
-
-
-
-
-
- Processing Status
-
-
- {processingStatus && (
-
- {processingStatus}
-
- )}
+ }
+ onClick={handleOpenOutputFolder}
+ sx={appStyles.headerActionButton}
+ >
+ Outputs
+
+ }
+ onClick={() => setShowStartFreshDialog(true)}
+ disabled={isStartingFresh}
+ sx={appStyles.headerActionButton}
+ >
+ {isStartingFresh ? 'Starting...' : 'Fresh Start'}
+
+
- {systemStatus && (
-
- System Status
- Raw Files: {systemStatus.raw_files}
- Processed Segments: {systemStatus.processed_segments}
-
- Total Duration: {formatDuration(systemStatus.total_duration || 0)}
-
-
- Custom Metadata: {systemStatus.has_metadata_json ? 'Yes' : 'Not Found'}
-
- {systemStatus.raw_file_names && systemStatus.raw_file_names.length > 0 && (
-
-
- Recent files: {systemStatus.raw_file_names.join(', ')}
+
+ {gpuMemoryStatus && gpuMemoryStatus.cuda ? (
+ <>
+
+
+ GPU Memory
+
+
+ 2 ? 'good' : gpuMemoryStatus.cuda.free > 0.5 ? 'low' : 'critical'
+ )}
+ />
+
+ {gpuMemoryStatus.cuda.free > 2 ? 'Good' :
+ gpuMemoryStatus.cuda.free > 0.5 ? 'Low' : 'Critical'}
- )}
-
- )}
+
-
-
-
-
- {/* Training Tab */}
-
-
-
-
-
- Training Configuration
-
-
-
- Base Model
-
-
-
- setTrainingConfig({
- ...trainingConfig,
- modelName: e.target.value
- })}
- sx={{ mb: 2 }}
- />
-
-
- }>
- Advanced Settings
-
-
-
- {/* Row 1 */}
-
- Epochs
-
- setTrainingConfig({
- ...trainingConfig,
- epochs: value
- })}
- min={1}
- max={1000}
- valueLabelDisplay="auto"
- sx={{ flex: 1 }}
- />
- {
- const val = parseInt(e.target.value) || 1;
- setTrainingConfig({
- ...trainingConfig,
- epochs: Math.max(1, Math.min(1000, val))
- });
- }}
- inputProps={{ min: 1, max: 1000, step: 1 }}
- sx={{ width: '80px' }}
- size="small"
- />
-
-
+
-
- Checkpoint Interval (steps)
-
- setTrainingConfig({
- ...trainingConfig,
- checkpointSteps: value
- })}
- min={10}
- max={1000}
- step={10}
- valueLabelDisplay="auto"
- sx={{ flex: 1 }}
- />
- {
- const val = parseInt(e.target.value) || 10;
- setTrainingConfig({
- ...trainingConfig,
- checkpointSteps: Math.max(10, Math.min(1000, val))
- });
- }}
- inputProps={{ min: 10, max: 1000, step: 10 }}
- sx={{ width: '80px' }}
- size="small"
- />
+
+ No CUDA GPU detected
+
+
+ Using CPU for processing
+
+ >
+ )}
+
+
+
+
+ {/* Main Content with Sidebar Layout */}
+
+ {/* Left Sidebar with Vertical Tabs */}
+
+
+ } iconPosition="start" label="Data Processing" />
+ } iconPosition="start" label="Training" />
+ } iconPosition="start" label="Generation" />
+
+
+
+ {/* Main Content Area */}
+
+
+ {/* Data Processing Tab */}
+
+
+
+
+
+
+
+ Manual Annotate
-
+
+ Upload audio files one by one and write each prompt yourself.
+ Use this when you want full control over every annotation.
+
- {/* Row 2 */}
-
- Learning Rate
-
- setTrainingConfig({
- ...trainingConfig,
- learningRate: value
- })}
- min={1e-6}
- max={1e-3}
- step={1e-6}
- valueLabelDisplay="auto"
- sx={{ flex: 1 }}
- />
- {
- const val = parseFloat(e.target.value) || 1e-6;
- setTrainingConfig({
- ...trainingConfig,
- learningRate: Math.max(1e-6, Math.min(1e-3, val))
- });
- }}
- inputProps={{ min: 1e-6, max: 1e-3, step: 1e-6 }}
- sx={{ width: '100px' }}
- size="small"
+ {uploadRows.map((row, index) => (
+
+ ))}
+
+ }
+ onClick={addUploadRow}
+ sx={appStyles.addRowButton}
+ >
+ Add Another Row
+
+
+ : }
+ fullWidth
+ >
+ {isProcessing ? 'Saving…' : 'Save to dataset'}
+
+
+
+
+
+
+
+
+
+ {processingStatus && (
+
+ {processingStatus}
+
+ )}
+
+ {systemStatus && (
+
+
+
+
+
+ Dataset Status
-
-
-
-
-
-
-
-
-
-
- }
- sx={{ flex: 1 }}
- >
- Stop Training
-
-
-
- {/* Warning when base model is not downloaded */}
- {(() => {
- const baseModel = baseModels.find(m => m.name === trainingConfig.baseModel);
- if (baseModel && !baseModel.downloaded) {
- return (
-
-
- The selected base model "{baseModel.displayName}" is not downloaded.
- {isDocker
- ? ' Click "Model Setup" in the menu bar above to download it.'
- : ' Please use the File Authentication menu to download it before training.'}
+ Raw Files: {systemStatus.raw_files}
+
+ Total Duration: {formatDuration(systemStatus.total_duration || 0)}
- {isDocker && (
-
- )}
-
- );
- }
- return null;
- })()}
-
-
-
-
-
-
- Training Monitor
-
-
-
-
-
-
-
-
-
-
- {/* Generation Tab */}
-
-
-
-
-
- Audio Generation
-
-
-
-
-
+
+
+
+ {/* Training Tab */}
+
+
+
+
+
+
+
+
-
- ))}
-
-
-
-
-
-
-
- {/* Unwrapped Model Selection for Fine-tuned Models */}
- {selectedModel && availableModels.find(m => m.name === selectedModel)?.unwrapped_models?.length > 0 && (
- (() => {
- const unwrappedModels = availableModels.find(m => m.name === selectedModel)?.unwrapped_models || [];
- const validPaths = unwrappedModels.map(u => String(u.path));
- // Only allow the value if it's in the list, otherwise set to ''
- const safeSelected = validPaths.includes(selectedUnwrappedModel) ? selectedUnwrappedModel : '';
- return (
- <>
-
+ Training Configuration
+
+
+
- >
- );
- })()
- )}
- setGenerationPrompt(e.target.value)}
- sx={{ mb: 3 }}
- />
-
-
-
- Desired Duration (seconds):
-
- setGenerationDuration(value)}
- min={1}
- max={getMaxDuration()}
- step={1}
- marks
- valueLabelDisplay="auto"
- />
-
- {generationDuration}s
-
-
-
-
-
- {isGenerating ? (
-
-
-
-
- Generating audio... {Math.round(generationProgress)}%
-
+ setTrainingConfig({
+ ...trainingConfig,
+ modelName: e.target.value
+ })}
+ sx={appStyles.fieldMarginBottom}
+ />
+
+
+ }>
+ Advanced Settings
+
+
+
+
+ Epochs
+
+ setTrainingConfig({
+ ...trainingConfig,
+ epochs: value
+ })}
+ min={1}
+ max={1000}
+ valueLabelDisplay="auto"
+ sx={appStyles.sliderFlexGrow}
+ />
+ {
+ const val = parseInt(e.target.value) || 1;
+ setTrainingConfig({
+ ...trainingConfig,
+ epochs: Math.max(1, Math.min(1000, val))
+ });
+ }}
+ inputProps={{ min: 1, max: 1000, step: 1 }}
+ sx={appStyles.sliderInputSmall}
+ size="small"
+ />
+
+
+
+
+ Checkpoint Interval (steps)
+
+ setTrainingConfig({
+ ...trainingConfig,
+ checkpointSteps: value
+ })}
+ min={10}
+ max={1000}
+ step={10}
+ valueLabelDisplay="auto"
+ sx={appStyles.sliderFlexGrow}
+ />
+ {
+ const val = parseInt(e.target.value) || 10;
+ setTrainingConfig({
+ ...trainingConfig,
+ checkpointSteps: Math.max(10, Math.min(1000, val))
+ });
+ }}
+ inputProps={{ min: 10, max: 1000, step: 10 }}
+ sx={appStyles.sliderInputSmall}
+ size="small"
+ />
+
+
+
+
+ Learning Rate
+
+ setTrainingConfig({
+ ...trainingConfig,
+ learningRate: value
+ })}
+ min={1e-6}
+ max={1e-3}
+ step={1e-6}
+ valueLabelDisplay="auto"
+ sx={appStyles.sliderFlexGrow}
+ />
+ {
+ const val = parseFloat(e.target.value) || 1e-6;
+ setTrainingConfig({
+ ...trainingConfig,
+ learningRate: Math.max(1e-6, Math.min(1e-3, val))
+ });
+ }}
+ inputProps={{ min: 1e-6, max: 1e-3, step: 1e-6 }}
+ sx={appStyles.sliderInputMedium}
+ size="small"
+ />
+
+
+
+
+
+
+
+
+
+
+
+ }
+ sx={appStyles.actionButtonFlexGrow}
+ >
+ Stop
+
+
+
-
-
- Generation time may vary considerably depending on your hardware.
-
-
- ) : (
-
- )}
-
- {/* Warnings for model issues */}
- {selectedModel &&
- availableModels.find(m => m.name === selectedModel) &&
- availableModels.find(m => m.name === selectedModel)?.unwrapped_models?.length > 0 &&
- !selectedUnwrappedModel && (
-
- Please select a checkpoint for the selected fine-tuned model before generating audio.
-
- )}
-
- {/* Warning when base model is not downloaded */}
- {(() => {
- const baseModel = baseModels.find(m => m.name === selectedModel);
- if (baseModel && !baseModel.downloaded) {
- return (
-
-
- The selected base model "{baseModel.displayName}" is not downloaded.
- {isDocker
- ? ' Click "Model Setup" in the menu bar above to download it.'
- : ' Please use the Authentication menu to download it before generating audio.'}
-
- {isDocker && (
-
- )}
-
- );
- }
- return null;
- })()}
-
-
-
-
-
-
- Selected Model
-
-
-
- {selectedModel ? (
- (() => {
- // Check if it's a base model
- const baseModel = baseModels.find(m => m.name === selectedModel);
- if (baseModel) {
- const maxDuration = getMaxDuration();
- return (
-
-
- {baseModel.displayName}
-
-
- Type: Base Model
-
-
- {baseModel.description}
-
- {baseModel.downloaded ? (
-
- Ready for inference
-
- ) : (
-
- Model not downloaded
-
- )}
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Generation Tab */}
+
+
+
+
+
+
+
+
- );
- }
-
- // Check if it's a fine-tuned model
- const model = availableModels.find(m => m.name === selectedModel);
- if (model) {
- const maxDuration = getMaxDuration();
- return (
-
-
- {model.name}
-
-
- Type: Fine-tuned Model
-
-
- Path: {model.path}
-
-
- Checkpoint: {model.has_checkpoint ? 'Available' : 'Missing'}
-
+ Audio Generation
+
- {model.unwrapped_models && model.unwrapped_models.length > 0 && (
-
-
- Selected Unwrapped Model for Generation
+
+
+
+
+
+
+
+
+
+ {/* Unwrapped Model Selection for Fine-tuned Models */}
+ {selectedModel && availableModels.find(m => m.name === selectedModel)?.unwrapped_models?.length > 0 && (
+ (() => {
+ const unwrappedModels = availableModels.find(m => m.name === selectedModel)?.unwrapped_models || [];
+ const validPaths = unwrappedModels.map(u => String(u.path));
+ // Only allow the value if it's in the list, otherwise set to ''
+ const safeSelected = validPaths.includes(selectedUnwrappedModel) ? selectedUnwrappedModel : '';
+ return (
+ <>
+
+
- )}
+
+ ))}
+
+
+ >
+ );
+ })()
+ )}
+
+ setGenerationPrompt(e.target.value)}
+ sx={appStyles.fieldMarginBottomLarge}
+ />
+
+
+
+ Desired Duration (seconds):
+
+ setGenerationDuration(value)}
+ min={1}
+ max={getMaxDuration()}
+ step={1}
+ marks
+ valueLabelDisplay="auto"
+ />
+
+ {generationDuration}s
+
+
+
+
+
+ {isGenerating ? (
+
+
+
+
+ Generating audio... {Math.round(generationProgress)}%
+
+
+
+
+ Generation time may vary considerably depending on your hardware.
+
- );
- }
+ ) : (
+
+ )}
- return (
-
- Model not found
-
- );
- })()
- ) : (
-
- Please select a model to generate audio
-
- )}
-
-
- {/* Checkpoint Management Section */}
- {selectedModel && availableModels.find(m => m.name === selectedModel) && (
- m.name === selectedModel)}
- onRefresh={refreshAllModels}
- />
- )}
+ {/* Warnings for model issues */}
+ {selectedModel &&
+ availableModels.find(m => m.name === selectedModel) &&
+ availableModels.find(m => m.name === selectedModel)?.unwrapped_models?.length > 0 &&
+ !selectedUnwrappedModel && (
+
+ Please select a checkpoint for the selected fine-tuned model before generating audio.
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ Selected Model
+
+ {selectedModel ? (
+ (() => {
+ // Check if it's a base model
+ const baseModel = baseModels.find(m => m.name === selectedModel);
+ if (baseModel) {
+ return (
+
+
+ {baseModel.displayName}
+
+ {baseModel.downloaded ? (
+
+ Ready for inference
+
+ ) : (
+
+ Model not downloaded
+
+ )}
+
+ );
+ }
+
+ // Check if it's a fine-tuned model
+ const model = availableModels.find(m => m.name === selectedModel);
+ if (model) {
+ const maxDuration = getMaxDuration();
+ const selectedUnwrapped = selectedUnwrappedModel
+ ? model.unwrapped_models?.find(u => u.path === selectedUnwrappedModel)
+ : null;
+ return (
+
+
+ {model.name}
+
+
+ {model.checkpoints?.length || 0} wrapped checkpoints
+
+
+ {selectedUnwrapped ? (
+
+
+ Using: {selectedUnwrapped.name}
+
+
+ {selectedUnwrapped.size_mb} MB
+
+
+ Max Duration: {maxDuration} seconds
+
+
+ ) : (
+
+ Select a checkpoint to generate audio.
+
+ )}
+
+
+
+ );
+ }
+
+ return (
+
+ Model not found
+
+ );
+ })()
+ ) : (
+
+ Please select a model to generate audio
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {/* Start Fresh Confirmation Dialog */}
+
+
+ {/* Free GPU Memory Confirmation Dialog */}
+
-
- Generated Fragments
+
+
+
- setDockerSnackbar({ open: true, message: msg })}
- />
-
-
-
-
-
-
-
- {/* Start Fresh Confirmation Dialog */}
-
-
- {/* Free GPU Memory Confirmation Dialog */}
- setShowFreeGPUDialog(false)}
- aria-labelledby="free-gpu-dialog-title"
- >
-
- Free GPU Memory
-
-
-
- This will stop all running processes and free GPU memory. Any active training will be stopped immediately.
-
-
- Are you sure you want to continue?
-
-
-
-
}
+ onClick={() => handleOpenDocumentation('documentation')}
+ disabled={isOpeningDocumentation}
+ sx={appStyles.infoDocButton}
>
- {isFreeingGPU ? 'Freeing...' : 'Free GPU Memory'}
+ Documentation
-
-
-
- {/* Docker: Model Download Dialog */}
- setShowDownloadDialog(false)}
- maxWidth="sm"
- fullWidth
- >
-
-
-
- Model Setup
-
-
-
-
- Fragmenta requires at least one base model to generate audio. These models are hosted on Hugging Face and require authentication.
-
-
- {/* HF Authentication Section */}
-
-
- Hugging Face Authentication
-
- {hfAuthStatus.authenticated ? (
-
-
-
- ) : (
- <>
-
- Get your token from{' '}
-
- huggingface.co/settings/tokens
-
- {' '}(requires a free account). You must also accept the model license on its HF page.
-
-
- { setHfToken(e.target.value); setHfTokenError(''); }}
- fullWidth
- error={!!hfTokenError}
- helperText={hfTokenError}
- />
-
-
- >
- )}
-
-
- {/* Model Cards */}
- {baseModels.map((model) => (
-
-
-
- {model.displayName}
- {model.description}
- {!model.downloaded && modelPageUrls[model.name] && (
-
-
- Accept model license on Hugging Face →
-
-
- )}
-
-
- {model.downloaded ? (
-
- ) : downloadingModels[model.name] === 'downloading' ? (
-
-
- {downloadProgress[model.name]?.message || 'Downloading...'}
-
- 0 ? 'determinate' : 'indeterminate'}
- value={downloadProgress[model.name]?.percent || 0}
- sx={{ height: 8, borderRadius: 4 }}
- />
- {downloadProgress[model.name]?.percent > 0 && (
-
- {downloadProgress[model.name].percent}%
-
- )}
-
- ) : downloadingModels[model.name] === 'error' ? (
-
-
- {downloadProgress[model.name]?.message && (
-
- {downloadProgress[model.name].message}
-
- )}
-
-
- ) : (
- }
- onClick={() => handleDockerModelDownload(model.name)}
- disabled={!hfAuthStatus.authenticated}
- >
- Download
-
- )}
-
-
-
- ))}
-
-
-
-
-
- {/* Docker: About Dialog */}
- setShowAboutDialog(false)}
- maxWidth="xs"
- fullWidth
- >
- About Fragmenta
-
-
- Fragmenta — AI Audio Generation & Fine-Tuning
-
-
- Version 0.0.1
-
-
- Copyright 2025-2026 Misagh Azimi
-
-
- Licensed under the Apache License 2.0
-
-
-
- setShowAboutDialog(false)}>Close
-
-
-
- {/* Docker: Output Files Browser Dialog */}
- setShowOutputFilesDialog(false)}
- maxWidth="sm"
- fullWidth
- >
-
-
-
-
- Output Files
-
- }>
- Refresh
-
-
-
-
- {outputFiles.length === 0 ? (
-
- No output files yet. Generate some audio first!
-
- ) : (
-
- {outputFiles.map((file) => (
-
-
-
- }
- >
-
-
- ))}
-
- )}
-
- Tip: In Docker, output files are also available on your host at the mounted ./output/ volume.
-
-
-
- setShowOutputFilesDialog(false)}>Close
-
-
-
- {/* Docker: Snackbar for messages */}
- setDockerSnackbar({ open: false, message: '' })}
- message={dockerSnackbar.message}
- />
-
-
-
+
+
+
+ setShowInfoDialog(false)}>
+ Close
+
+
+
+
+ {
+ setAuthDialogOpen(false);
+ if (success) {
+ refreshAllModels();
+ }
+ }}
+ />
);
}