feat: model-on-demand lifecycle — retry on 503, live status pill, warming indicator
- whisper.ts: add getModelStatus(); fix submitJob() to retry on 503 using
Retry-After header instead of throwing; optional onModelWaiting callback
lets the pipeline surface model state to the UI during the wait
- pipeline.ts: pass onModelWaiting callback → emits model_warming SSE event
so the job detail page can show 'Warming up model…' while waiting
- types.ts: add ModelStateTag union and ModelStatus interface
- api/model/status: GET route proxies whisper /model/status (falls back to
{state:'unloaded'} if whisper unreachable)
- api/model/events: GET route relays whisper SSE stream to the browser;
AbortController tied to request.signal cleans up on disconnect
- layout.svelte: status pill is now live — initial fetch + EventSource on
/api/model/events; dot colour + label reflect real model state with a
pulsing animation while loading or waiting_for_gpu
- jobs/[id]/+page.svelte: handle model_warming event type → show a yellow
'Warming up model…' sub-label with spinner inside the progress card
- whisper.test.ts: update submitJob mocks to status:202 to match real API
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -31,6 +31,7 @@ describe('submitJob', () => {
|
||||
it('POSTs to /jobs and returns job_id', async () => {
|
||||
mocks.fetch.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 202,
|
||||
json: () => Promise.resolve({ job_id: 'whisper-job-abc' })
|
||||
});
|
||||
const id = await submitJob('/tmp/audio.wav', 'http://host/api/webhook/job-1');
|
||||
@@ -41,6 +42,7 @@ describe('submitJob', () => {
|
||||
vi.stubEnv('WHISPER_URL', 'http://localhost:8091');
|
||||
mocks.fetch.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 202,
|
||||
json: () => Promise.resolve({ job_id: 'x' })
|
||||
});
|
||||
await submitJob('/tmp/audio.wav', 'http://host/api/webhook/job-1');
|
||||
@@ -54,6 +56,7 @@ describe('submitJob', () => {
|
||||
it('includes task=transcribe in the form', async () => {
|
||||
mocks.fetch.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 202,
|
||||
json: () => Promise.resolve({ job_id: 'x' })
|
||||
});
|
||||
await submitJob('/tmp/audio.wav', 'http://host/webhook');
|
||||
@@ -63,6 +66,7 @@ describe('submitJob', () => {
|
||||
it('includes webhook_url in the form', async () => {
|
||||
mocks.fetch.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 202,
|
||||
json: () => Promise.resolve({ job_id: 'x' })
|
||||
});
|
||||
await submitJob('/tmp/audio.wav', 'http://192.168.1.10:3000/api/webhook/job-99');
|
||||
@@ -75,6 +79,7 @@ describe('submitJob', () => {
|
||||
it('includes language when provided', async () => {
|
||||
mocks.fetch.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 202,
|
||||
json: () => Promise.resolve({ job_id: 'x' })
|
||||
});
|
||||
await submitJob('/tmp/audio.wav', 'http://host/webhook', 'en');
|
||||
@@ -84,6 +89,7 @@ describe('submitJob', () => {
|
||||
it('omits language field when not provided', async () => {
|
||||
mocks.fetch.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 202,
|
||||
json: () => Promise.resolve({ job_id: 'x' })
|
||||
});
|
||||
await submitJob('/tmp/audio.wav', 'http://host/webhook');
|
||||
|
||||
Reference in New Issue
Block a user