diff --git a/.changelog/next/changed-minimax-music-progress.md b/.changelog/next/changed-minimax-music-progress.md new file mode 100644 index 0000000000..c2d760bdf3 --- /dev/null +++ b/.changelog/next/changed-minimax-music-progress.md @@ -0,0 +1 @@ +- Music generation now shows active processing feedback and elapsed time during long renders. diff --git a/client/src/components/music/MusicGenPanel.jsx b/client/src/components/music/MusicGenPanel.jsx index afb5341fca..487b90177f 100644 --- a/client/src/components/music/MusicGenPanel.jsx +++ b/client/src/components/music/MusicGenPanel.jsx @@ -40,6 +40,7 @@ export default function MusicGenPanel({ track, title = '', artistId = '', artist const [modelId, setModelId] = useState(''); const [durationSec, setDurationSec] = useState(null); const [generating, setGenerating] = useState(false); + const [generationElapsedSec, setGenerationElapsedSec] = useState(0); // Inline HF model install. const [installRepo, setInstallRepo] = useState(''); const [installing, setInstalling] = useState(false); @@ -64,6 +65,16 @@ export default function MusicGenPanel({ track, title = '', artistId = '', artist useEffect(() => { loadEngines(); }, []); + useEffect(() => { + if (!generating) return undefined; + const startedAt = Date.now(); + setGenerationElapsedSec(0); + const timer = setInterval(() => { + if (mountedRef.current) setGenerationElapsedSec(Math.floor((Date.now() - startedAt) / 1000)); + }, 1000); + return () => clearInterval(timer); + }, [generating]); + const engine = useMemo(() => engines.find((e) => e.id === engineId) || null, [engines, engineId]); // Remix: seed the engine / model / duration from a past render. Keyed on @@ -282,6 +293,26 @@ export default function MusicGenPanel({ track, title = '', artistId = '', artist ) : null} + {generating ? ( +
+
+ + {engine?.cudaRequired ? 'Processing on the GPU' : 'Rendering audio'} + + + {Math.floor(generationElapsedSec / 60)}:{String(generationElapsedSec % 60).padStart(2, '0')} elapsed + +
+ + ) : null} {/* Install an additional model from HuggingFace — only for engines that can render an arbitrary checkpoint (musicgen/audioldm2). ACE-Step uses diff --git a/client/src/components/music/MusicGenPanel.test.jsx b/client/src/components/music/MusicGenPanel.test.jsx index 1b1bddb8d2..dc779352d0 100644 --- a/client/src/components/music/MusicGenPanel.test.jsx +++ b/client/src/components/music/MusicGenPanel.test.jsx @@ -193,4 +193,26 @@ describe('MusicGenPanel', () => { expect(screen.getByRole('button', { name: /install model/i })).toBeInTheDocument(); expect(screen.queryByRole('button', { name: /install runtime/i })).not.toBeInTheDocument(); }); + + it('shows honest elapsed GPU feedback while MiniMax generation is running', async () => { + api.listMusicEngines.mockResolvedValue({ + defaultEngine: 'minimax-music3', + engines: [engine({ + id: 'minimax-music3', name: 'MiniMax Music 3 (CUDA only)', ready: true, + cudaRequired: true, cudaState: 'available', customModels: false, lyrics: true, + maxDurationSec: 300, defaultDurationSec: 60, + models: [{ id: 'minimax-music3', repo: 'MiniMaxAI/MiniMax-Music3', name: 'MiniMax Music 3' }], + defaultModelId: 'minimax-music3', + })], + }); + api.generateMusic.mockReturnValue(new Promise(() => {})); + + render(); + + fireEvent.click(await screen.findByRole('button', { name: /generate/i })); + expect(await screen.findByRole('status')).toHaveTextContent('Processing on the GPU'); + expect(screen.getByRole('status')).toHaveTextContent('0:00 elapsed'); + expect(screen.getByRole('status')).toHaveTextContent(/does not report an exact percentage/i); + expect(screen.getByRole('status')).toHaveTextContent(/tens of minutes on a 24 GB GPU/i); + }); });