feat(analysis): live SSE updates in the admin coverage dashboard

The Analysis section now reflects worker progress in real time:

- Opens an EventSource on the live stream while mounted; a "Live" pill
  reflects connection state and reconnects on drop (probes after repeated
  failures so a lost session logs out).
- Applies events incrementally: enqueued marks in-scope loaded pages
  queued; started flips a chip to a pulsing "analyzing"; completed flips
  it green and live-bumps the manga + chapter coverage badges (guarded so
  no double count); failed flips it red. A compact activity ticker shows
  the last few events.
- Server numbers stay authoritative: re-loading the overview or a page
  grid clears the matching live overrides.

API client: analysisStatusStreamUrl() + AnalysisEvent type.

Tests: vitest for the stream URL; Playwright asserts SSE frames flow into
the ticker (existing coverage/drill/queue specs get a default quiet
stream). svelte-check + build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-13 21:12:56 +02:00
parent d0cd31c9a7
commit 8b5bd99446
4 changed files with 334 additions and 9 deletions

View File

@@ -32,7 +32,8 @@ import {
getAnalysisMangaCoverage,
getAnalysisChapterCoverage,
getAnalysisChapterPages,
getAnalysisPageDetail
getAnalysisPageDetail,
analysisStatusStreamUrl
} from './admin';
function ok(body: unknown, status = 200): Response {
@@ -580,4 +581,10 @@ describe('admin crawler api client', () => {
expect(url).toMatch(/\/v1\/admin\/analysis\/pages\/p1$/);
expect(d.status).toBe('none');
});
it('analysisStatusStreamUrl points at the SSE endpoint', () => {
expect(analysisStatusStreamUrl()).toMatch(
/\/v1\/admin\/analysis\/status\/stream$/
);
});
});

View File

@@ -532,3 +532,27 @@ export async function getAnalysisPageDetail(
`/v1/admin/analysis/pages/${encodeURIComponent(pageId)}`
);
}
/** One live analysis event from the SSE stream. */
export type AnalysisEvent =
| {
kind: 'enqueued';
count: number;
manga_id: string | null;
chapter_id: string | null;
}
| {
kind: 'started' | 'completed' | 'failed';
page_id: string;
manga_id: string;
chapter_id: string;
page_number: number;
};
/** URL of the live analysis SSE stream. Open with `new EventSource(...)`
* while the admin Analysis page is mounted and close it on navigate-away.
* Each message is a named `analysis` event whose `data` is an
* {@link AnalysisEvent}; a `lagged` event signals dropped frames. */
export function analysisStatusStreamUrl(): string {
return apiUrl('/v1/admin/analysis/status/stream');
}