Fix percentages on creating

This commit is contained in:
riversedge
2026-02-04 23:25:38 -05:00
parent 424bd3fd25
commit ce6256d931
3 changed files with 133 additions and 69 deletions
+120 -51
View File
@@ -597,6 +597,72 @@ export default function App() {
}
}, [token]);
const beginPollingJob = useCallback((jobId: string, tempId: string) => {
if (!token) return;
if (activeJobsRef.current.has(jobId)) return;
const pollInterval = setInterval(async () => {
try {
const status = await generateApi.getStatus(jobId, token);
const normalizedProgress = Number.isFinite(Number(status.progress))
? (Number(status.progress) > 1 ? Number(status.progress) / 100 : Number(status.progress))
: undefined;
setSongs(prev => prev.map(s => {
if (s.id === tempId) {
return {
...s,
queuePosition: status.status === 'queued' ? status.queuePosition : undefined,
progress: normalizedProgress ?? s.progress,
stage: status.stage ?? s.stage,
};
}
return s;
}));
if (status.status === 'succeeded' && status.result) {
cleanupJob(jobId, tempId);
await refreshSongsList();
if (window.innerWidth < 768) {
setMobileShowList(true);
}
} else if (status.status === 'failed') {
cleanupJob(jobId, tempId);
console.error(`Job ${jobId} failed:`, status.error);
showToast(`Generation failed: ${status.error || 'Unknown error'}`, 'error');
}
} catch (pollError) {
console.error(`Polling error for job ${jobId}:`, pollError);
cleanupJob(jobId, tempId);
}
}, 2000);
activeJobsRef.current.set(jobId, { tempId, pollInterval });
setActiveJobCount(activeJobsRef.current.size);
setTimeout(() => {
if (activeJobsRef.current.has(jobId)) {
console.warn(`Job ${jobId} timed out`);
cleanupJob(jobId, tempId);
showToast('Generation timed out', 'error');
}
}, 600000);
}, [token, cleanupJob, refreshSongsList]);
const buildTempSongFromParams = (params: GenerationParams, tempId: string, createdAt?: string) => ({
id: tempId,
title: params.title || 'Generating...',
lyrics: '',
style: params.style || params.songDescription || '',
coverUrl: 'https://picsum.photos/200/200?blur=10',
duration: '--:--',
createdAt: createdAt ? new Date(createdAt) : new Date(),
isGenerating: true,
tags: params.customMode ? ['custom'] : ['simple'],
isPublic: true,
});
// Handlers
const handleGenerate = async (params: GenerationParams) => {
if (!isAuthenticated || !token) {
@@ -683,57 +749,7 @@ export default function App() {
isFormatCaption: params.isFormatCaption,
}, token);
// Poll for completion - each job has its own polling interval
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 => {
if (s.id === tempId) {
return {
...s,
queuePosition: status.status === 'queued' ? status.queuePosition : undefined,
progress: normalizedProgress ?? s.progress,
stage: status.stage ?? s.stage,
};
}
return s;
}));
if (status.status === 'succeeded' && status.result) {
cleanupJob(job.jobId, tempId);
await refreshSongsList();
if (window.innerWidth < 768) {
setMobileShowList(true);
}
} else if (status.status === 'failed') {
cleanupJob(job.jobId, tempId);
console.error(`Job ${job.jobId} failed:`, status.error);
showToast(`Generation failed: ${status.error || 'Unknown error'}`, 'error');
}
} catch (pollError) {
console.error(`Polling error for job ${job.jobId}:`, pollError);
cleanupJob(job.jobId, tempId);
}
}, 2000);
// Track this job
activeJobsRef.current.set(job.jobId, { tempId, pollInterval });
setActiveJobCount(activeJobsRef.current.size);
// Timeout after 10 minutes
setTimeout(() => {
if (activeJobsRef.current.has(job.jobId)) {
console.warn(`Job ${job.jobId} timed out`);
cleanupJob(job.jobId, tempId);
showToast('Generation timed out', 'error');
}
}, 600000);
beginPollingJob(job.jobId, tempId);
} catch (e) {
console.error('Generation error:', e);
@@ -747,6 +763,59 @@ export default function App() {
}
};
// Resume active jobs on refresh so progress keeps updating
useEffect(() => {
if (!isAuthenticated || !token) return;
const resumeJobs = async () => {
try {
const history = await generateApi.getHistory(token);
const jobs = Array.isArray(history.jobs) ? history.jobs : [];
const activeStatuses = new Set(['pending', 'queued', 'running']);
const jobsToResume = jobs.filter((job: any) => activeStatuses.has(job.status));
if (jobsToResume.length === 0) return;
setSongs(prev => {
const existingIds = new Set(prev.map(s => s.id));
const next = [...prev];
for (const job of jobsToResume) {
const jobId = job.id || job.jobId;
if (!jobId) continue;
const tempId = `job_${jobId}`;
if (existingIds.has(tempId)) continue;
const params = (() => {
try {
if (!job.params) return {};
return typeof job.params === 'string' ? JSON.parse(job.params) : job.params;
} catch {
return {};
}
})();
next.unshift(buildTempSongFromParams(params, tempId, job.created_at));
existingIds.add(tempId);
}
return next;
});
for (const job of jobsToResume) {
const jobId = job.id || job.jobId;
if (!jobId) continue;
const tempId = `job_${jobId}`;
beginPollingJob(jobId, tempId);
}
} catch (error) {
console.error('Failed to resume jobs:', error);
}
};
resumeJobs();
}, [isAuthenticated, token, beginPollingJob]);
const togglePlay = () => {
if (!currentSong) return;
setIsPlaying(!isPlaying);
+9 -18
View File
@@ -358,25 +358,16 @@ const SongItem: React.FC<SongItemProps> = ({
{song.isGenerating && (
<div className="pt-2">
<div className="h-1 rounded-full bg-zinc-200/70 dark:bg-white/10 overflow-hidden">
{song.progress !== undefined ? (
<div
className="h-full bg-gradient-to-r from-pink-500 to-purple-600 transition-all"
style={{
width: `${Math.min(
100,
Math.max(0, (song.progress > 1 ? song.progress / 100 : song.progress) * 100)
)}%`,
}}
/>
) : (
<div className="h-full w-1/3 bg-gradient-to-r from-pink-500 to-purple-600 animate-pulse" />
)}
<div
className={`h-full bg-gradient-to-r from-pink-500 to-purple-600 transition-all ${song.progress === undefined ? 'opacity-40' : ''}`}
style={{
width: `${Math.min(
100,
Math.max(0, ((song.progress ?? 0) > 1 ? (song.progress ?? 0) / 100 : (song.progress ?? 0)) * 100)
)}%`,
}}
/>
</div>
{song.progress !== undefined && (
<div className="mt-1 text-[10px] text-zinc-500 dark:text-zinc-400">
{Math.round((song.progress > 1 ? song.progress / 100 : song.progress) * 100)}%
</div>
)}
</div>
)}
</div>
+4
View File
@@ -445,6 +445,8 @@ router.get('/status/:jobId', authMiddleware, async (req: AuthenticatedRequest, r
status: aceStatus.status,
queuePosition: aceStatus.queuePosition,
etaSeconds: aceStatus.etaSeconds,
progress: aceStatus.progress,
stage: aceStatus.stage,
result: aceStatus.result,
error: aceStatus.error,
});
@@ -458,6 +460,8 @@ router.get('/status/:jobId', authMiddleware, async (req: AuthenticatedRequest, r
res.json({
jobId: req.params.jobId,
status: job.status,
progress: undefined,
stage: undefined,
result: job.result && typeof job.result === 'string' ? JSON.parse(job.result) : job.result,
error: job.error,
});