Fix percentages on creating
This commit is contained in:
@@ -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
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user