From 424bd3fd257e4c7ccfe7fadacd76b5a22643f1b1 Mon Sep 17 00:00:00 2001 From: riversedge Date: Wed, 4 Feb 2026 23:15:00 -0500 Subject: [PATCH] Various UI improvements --- App.tsx | 24 +++++++-- components/CreatePanel.tsx | 90 +++++++++++++++++++++++-------- components/RightSidebar.tsx | 51 +++++++++--------- components/SongList.tsx | 12 ++++- server/scripts/simple_generate.py | 9 +++- server/src/routes/generate.ts | 60 +++++++++++++++++++++ server/src/services/acestep.ts | 79 +++++++++++++++++++++------ services/api.ts | 2 + types.ts | 2 + 9 files changed, 262 insertions(+), 67 deletions(-) diff --git a/App.tsx b/App.tsx index 01f477c..978aa77 100644 --- a/App.tsx +++ b/App.tsx @@ -565,6 +565,14 @@ export default function App() { viewCount: s.view_count || 0, userId: s.user_id, creator: s.creator, + generationParams: (() => { + try { + if (!s.generation_params) return undefined; + return typeof s.generation_params === 'string' ? JSON.parse(s.generation_params) : s.generation_params; + } catch { + return undefined; + } + })(), })); // Preserve any generating songs that aren't in the loaded list @@ -579,6 +587,11 @@ export default function App() { // Sort by creation date, newest first return mergedSongs.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); }); + + // If the current selection was a temp/generating song, replace it with newest real song + if (selectedSong?.isGenerating || (selectedSong && !loadedSongs.some(s => s.id === selectedSong.id))) { + setSelectedSong(loadedSongs[0] ?? null); + } } catch (error) { console.error('Failed to refresh songs:', error); } @@ -623,7 +636,7 @@ export default function App() { title: params.title, instrumental: params.instrumental, vocalLanguage: params.vocalLanguage, - duration: params.duration, + duration: params.duration && params.duration > 0 ? params.duration : undefined, bpm: params.bpm, keyScale: params.keyScale, timeSignature: params.timeSignature, @@ -643,6 +656,8 @@ export default function App() { lmNegativePrompt: params.lmNegativePrompt, referenceAudioUrl: params.referenceAudioUrl, sourceAudioUrl: params.sourceAudioUrl, + referenceAudioTitle: params.referenceAudioTitle, + sourceAudioTitle: params.sourceAudioTitle, audioCodes: params.audioCodes, repaintingStart: params.repaintingStart, repaintingEnd: params.repaintingEnd, @@ -672,6 +687,9 @@ export default function App() { const pollInterval = setInterval(async () => { try { const status = await generateApi.getStatus(job.jobId, token); + const normalizedProgress = Number.isFinite(Number(status.progress)) + ? (Number(status.progress) > 1 ? Number(status.progress) / 100 : Number(status.progress)) + : undefined; // Update queue position on the temp song setSongs(prev => prev.map(s => { @@ -679,8 +697,8 @@ export default function App() { return { ...s, queuePosition: status.status === 'queued' ? status.queuePosition : undefined, - progress: status.status === 'running' ? status.progress : undefined, - stage: status.status === 'running' ? status.stage : undefined, + progress: normalizedProgress ?? s.progress, + stage: status.stage ?? s.stage, }; } return s; diff --git a/components/CreatePanel.tsx b/components/CreatePanel.tsx index 6ea16a0..7012221 100644 --- a/components/CreatePanel.tsx +++ b/components/CreatePanel.tsx @@ -146,6 +146,8 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati // Expert Parameters (now in Advanced section) const [referenceAudioUrl, setReferenceAudioUrl] = useState(''); const [sourceAudioUrl, setSourceAudioUrl] = useState(''); + const [referenceAudioTitle, setReferenceAudioTitle] = useState(''); + const [sourceAudioTitle, setSourceAudioTitle] = useState(''); const [audioCodes, setAudioCodes] = useState(''); const [repaintingStart, setRepaintingStart] = useState(0); const [repaintingEnd, setRepaintingEnd] = useState(-1); @@ -169,11 +171,14 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati const [trackName, setTrackName] = useState(''); const [completeTrackClasses, setCompleteTrackClasses] = useState(''); const [isFormatCaption, setIsFormatCaption] = useState(false); + const [maxDurationWithLm, setMaxDurationWithLm] = useState(240); + const [maxDurationWithoutLm, setMaxDurationWithoutLm] = useState(240); const [isUploadingReference, setIsUploadingReference] = useState(false); const [isUploadingSource, setIsUploadingSource] = useState(false); const [uploadError, setUploadError] = useState(null); - const [isFormatting, setIsFormatting] = useState(false); + const [isFormattingStyle, setIsFormattingStyle] = useState(false); + const [isFormattingLyrics, setIsFormattingLyrics] = useState(false); const [isDraggingFile, setIsDraggingFile] = useState(false); const referenceInputRef = useRef(null); const sourceInputRef = useRef(null); @@ -202,10 +207,12 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati const getAudioLabel = (url: string) => { try { const parsed = new URL(url); - return decodeURIComponent(parsed.pathname.split('/').pop() || parsed.hostname); + const name = decodeURIComponent(parsed.pathname.split('/').pop() || parsed.hostname); + return name.replace(/\.[^/.]+$/, '') || name; } catch { const parts = url.split('/'); - return decodeURIComponent(parts[parts.length - 1] || url); + const name = decodeURIComponent(parts[parts.length - 1] || url); + return name.replace(/\.[^/.]+$/, '') || name; } }; @@ -275,6 +282,34 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati }; }, [isResizing]); + useEffect(() => { + const loadLimits = async () => { + try { + const response = await fetch('/api/generate/limits'); + if (!response.ok) return; + const data = await response.json(); + if (typeof data.max_duration_with_lm === 'number') { + setMaxDurationWithLm(data.max_duration_with_lm); + } + if (typeof data.max_duration_without_lm === 'number') { + setMaxDurationWithoutLm(data.max_duration_without_lm); + } + } catch { + // ignore limits fetch failures + } + }; + + loadLimits(); + }, []); + + const activeMaxDuration = thinking ? maxDurationWithLm : maxDurationWithoutLm; + + useEffect(() => { + if (duration > activeMaxDuration) { + setDuration(activeMaxDuration); + } + }, [duration, activeMaxDuration]); + useEffect(() => { const isFileDrag = (e: DragEvent) => !!(e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files')); @@ -357,7 +392,11 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati // Format handler - uses LLM to enhance style/lyrics and auto-fill parameters const handleFormat = async (target: 'style' | 'lyrics') => { if (!token || !style.trim()) return; - setIsFormatting(true); + if (target === 'style') { + setIsFormattingStyle(true); + } else { + setIsFormattingLyrics(true); + } try { const result = await generateApi.formatInput({ caption: style, @@ -389,7 +428,11 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati console.error('Format error:', err); alert('Format failed. The LLM may not be available.'); } finally { - setIsFormatting(false); + if (target === 'style') { + setIsFormattingStyle(false); + } else { + setIsFormattingLyrics(false); + } } }; @@ -445,7 +488,7 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati // Also set as current reference/source const selectedTarget = target ?? audioModalTarget; - applyAudioTargetUrl(selectedTarget, data.track.audio_url); + applyAudioTargetUrl(selectedTarget, data.track.audio_url, data.track.filename); setShowAudioModal(false); } catch (err) { const message = err instanceof Error ? err.message : 'Upload failed'; @@ -477,7 +520,7 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati }; const useReferenceTrack = (track: ReferenceTrack) => { - applyAudioTargetUrl(audioModalTarget, track.audio_url); + applyAudioTargetUrl(audioModalTarget, track.audio_url, track.filename); setShowAudioModal(false); setPlayingTrackId(null); }; @@ -504,13 +547,16 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati setTempAudioUrl(''); }; - const applyAudioTargetUrl = (target: 'reference' | 'source', url: string) => { + const applyAudioTargetUrl = (target: 'reference' | 'source', url: string, title?: string) => { + const derivedTitle = title ? title.replace(/\.[^/.]+$/, '') : getAudioLabel(url); if (target === 'reference') { setReferenceAudioUrl(url); + setReferenceAudioTitle(derivedTitle); setReferenceTime(0); setReferenceDuration(0); } else { setSourceAudioUrl(url); + setSourceAudioTitle(derivedTitle); setSourceTime(0); setSourceDuration(0); if (taskType === 'text2music') { @@ -601,6 +647,8 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati lmNegativePrompt, referenceAudioUrl: referenceAudioUrl.trim() || undefined, sourceAudioUrl: sourceAudioUrl.trim() || undefined, + referenceAudioTitle: referenceAudioTitle.trim() || undefined, + sourceAudioTitle: sourceAudioTitle.trim() || undefined, audioCodes: audioCodes.trim() || undefined, repaintingStart, repaintingEnd, @@ -768,7 +816,7 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati setDuration(Number(e.target.value))} @@ -907,7 +955,7 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati
- {getAudioLabel(referenceAudioUrl)} + {referenceAudioTitle || getAudioLabel(referenceAudioUrl)}
{formatTime(referenceTime)} @@ -933,7 +981,7 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati
- {getAudioLabel(sourceAudioUrl)} + {sourceAudioTitle || getAudioLabel(sourceAudioUrl)}
{formatTime(sourceTime)} @@ -986,7 +1034,7 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati