fix(whisper): handle model warmup events
- Ignore backend model lifecycle webhooks so model warmup does not mark jobs done early - Parse batched SSE messages and relay model load states during submit retries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -6,62 +6,85 @@ import { cleanupJobTmp } from '$lib/server/downloader.js';
|
||||
import { emitProgress } from '$lib/server/pipeline.js';
|
||||
import type { Segment, WhisperJob } from '$lib/types.js';
|
||||
|
||||
const WHISPER_JOB_STATUSES = new Set<WhisperJob['status']>([
|
||||
'queued',
|
||||
'running',
|
||||
'done',
|
||||
'failed',
|
||||
'cancelled'
|
||||
]);
|
||||
|
||||
function isWhisperJobWebhook(payload: unknown): payload is WhisperJob {
|
||||
if (!payload || typeof payload !== 'object') return false;
|
||||
const candidate = payload as Record<string, unknown>;
|
||||
return (
|
||||
typeof candidate.id === 'string' &&
|
||||
typeof candidate.status === 'string' &&
|
||||
WHISPER_JOB_STATUSES.has(candidate.status as WhisperJob['status'])
|
||||
);
|
||||
}
|
||||
|
||||
export async function POST({ params, request }) {
|
||||
const jobId = params.jobId;
|
||||
const job = getJob(jobId);
|
||||
if (!job) throw error(404, 'Job not found');
|
||||
const jobId = params.jobId;
|
||||
const job = getJob(jobId);
|
||||
if (!job) throw error(404, 'Job not found');
|
||||
|
||||
const whisperJob = (await request.json()) as WhisperJob;
|
||||
const payload = (await request.json()) as unknown;
|
||||
if (!isWhisperJobWebhook(payload)) {
|
||||
// whisper-rtx2080 also fires model lifecycle events to registered job webhooks.
|
||||
return json({ ok: true, ignored: 'not_a_job_event' });
|
||||
}
|
||||
const whisperJob = payload;
|
||||
|
||||
// Discard the result if the job was cancelled locally while whisper was running
|
||||
if (job.status === 'cancelled') {
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
// Ignore stale callbacks from a previous whisper job after a local retry/reset.
|
||||
if (job.whisperJobId && whisperJob.id !== job.whisperJobId) {
|
||||
return json({ ok: true, ignored: 'stale_whisper_job' });
|
||||
}
|
||||
|
||||
// Ignore replayed success callbacks after the transcript is already persisted.
|
||||
if (job.status === 'done' && job.segmentsJson) {
|
||||
return json({ ok: true, ignored: 'duplicate_webhook' });
|
||||
}
|
||||
|
||||
if (whisperJob.status === 'failed' || whisperJob.status === 'cancelled') {
|
||||
const msg = whisperJob.error ?? `Whisper job ${whisperJob.status}`;
|
||||
updateJob({ id: jobId, status: 'failed', error: msg });
|
||||
emitProgress(jobId, { type: 'error', message: msg });
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
try {
|
||||
setJobStatus(jobId, 'processing', 90);
|
||||
emitProgress(jobId, { type: 'status', status: 'processing', progress: 90 });
|
||||
|
||||
const segments = (whisperJob.segments ?? []) as Segment[];
|
||||
|
||||
const paths = await writeOutputs(segments, job.title, jobId);
|
||||
const outputDir = paths.srt.replace(/\/[^/]+$/, '');
|
||||
|
||||
updateJob({
|
||||
id: jobId,
|
||||
status: 'done',
|
||||
progress: 100,
|
||||
segmentsJson: JSON.stringify(segments),
|
||||
outputDir
|
||||
});
|
||||
|
||||
emitProgress(jobId, { type: 'done', status: 'done' });
|
||||
|
||||
await sendNotification(jobId, '✅ Transcript ready', job.title);
|
||||
await cleanupJobTmp(jobId);
|
||||
|
||||
return json({ ok: true });
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
updateJob({ id: jobId, status: 'failed', error: message });
|
||||
emitProgress(jobId, { type: 'error', message });
|
||||
return json({ ok: false, error: message }, { status: 500 });
|
||||
}
|
||||
// Discard the result if the job was cancelled locally while whisper was running
|
||||
if (job.status === 'cancelled') {
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
// Ignore stale callbacks from a previous whisper job after a local retry/reset.
|
||||
if (job.whisperJobId && whisperJob.id !== job.whisperJobId) {
|
||||
return json({ ok: true, ignored: 'stale_whisper_job' });
|
||||
}
|
||||
|
||||
// Ignore replayed success callbacks after the transcript is already persisted.
|
||||
if (job.status === 'done' && job.segmentsJson) {
|
||||
return json({ ok: true, ignored: 'duplicate_webhook' });
|
||||
}
|
||||
|
||||
if (whisperJob.status === 'failed' || whisperJob.status === 'cancelled') {
|
||||
const msg = whisperJob.error ?? `Whisper job ${whisperJob.status}`;
|
||||
updateJob({ id: jobId, status: 'failed', error: msg });
|
||||
emitProgress(jobId, { type: 'error', message: msg });
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
try {
|
||||
setJobStatus(jobId, 'processing', 90);
|
||||
emitProgress(jobId, { type: 'status', status: 'processing', progress: 90 });
|
||||
|
||||
const segments = (whisperJob.segments ?? []) as Segment[];
|
||||
|
||||
const paths = await writeOutputs(segments, job.title, jobId);
|
||||
const outputDir = paths.srt.replace(/\/[^/]+$/, '');
|
||||
|
||||
updateJob({
|
||||
id: jobId,
|
||||
status: 'done',
|
||||
progress: 100,
|
||||
segmentsJson: JSON.stringify(segments),
|
||||
outputDir
|
||||
});
|
||||
|
||||
emitProgress(jobId, { type: 'done', status: 'done' });
|
||||
|
||||
await sendNotification(jobId, '✅ Transcript ready', job.title);
|
||||
await cleanupJobTmp(jobId);
|
||||
|
||||
return json({ ok: true });
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
updateJob({ id: jobId, status: 'failed', error: message });
|
||||
emitProgress(jobId, { type: 'error', message });
|
||||
return json({ ok: false, error: message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user