feat(ingestion,frontend): add live scholar progress counter to dashboard
Emit scholar_progress SSE events during two-pass ingestion so the Activity Monitor shows "X / N visited · Y finished · Z new publications" in real time instead of static post-run counts. - scholar_processing: add on_progress callback to _run_first_pass and _run_depth_pass; build _emit closure in run_scholar_iteration that publishes visited/finished/total via run_events; batch-emit scholars finished in first pass before depth pass; skip emission on abort/cancel - run_status store: add scholarProgress state, reset on stream open and reset(); subscribe to scholar_progress SSE events with safe parsing - DashboardPage: show live counter during run, fall back to static text when run is not active or no progress received yet Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f50b609a36
commit
4620978dce
3 changed files with 55 additions and 3 deletions
|
|
@ -486,9 +486,16 @@ watch(
|
|||
</RouterLink>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-secondary">
|
||||
Started {{ formatDate(displayedLatestRun.start_dt) }}. Processed
|
||||
{{ displayedLatestRun.scholar_count }} scholars and discovered
|
||||
{{ displayedLatestRun.new_publication_count }} new publications.
|
||||
Started {{ formatDate(displayedLatestRun.start_dt) }}.
|
||||
<template v-if="runStatus.isLikelyRunning && runStatus.scholarProgress">
|
||||
{{ runStatus.scholarProgress.visited }} / {{ runStatus.scholarProgress.total }} visited
|
||||
· {{ runStatus.scholarProgress.finished }} finished
|
||||
· {{ displayedLatestRun.new_publication_count }} new publications.
|
||||
</template>
|
||||
<template v-else>
|
||||
Processed {{ displayedLatestRun.scholar_count }} scholars and discovered
|
||||
{{ displayedLatestRun.new_publication_count }} new publications.
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
<AppEmptyState
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ export const useRunStatusStore = defineStore("runStatus", {
|
|||
lastErrorRequestId: null as string | null,
|
||||
lastSyncAt: null as number | null,
|
||||
livePublications: [] as Array<PublicationItem>,
|
||||
scholarProgress: null as { visited: number; finished: number; total: number } | null,
|
||||
}),
|
||||
getters: {
|
||||
isRunActive(state): boolean {
|
||||
|
|
@ -257,6 +258,7 @@ export const useRunStatusStore = defineStore("runStatus", {
|
|||
}
|
||||
activeStreamRunId = targetRunId;
|
||||
this.livePublications = [];
|
||||
this.scholarProgress = null;
|
||||
eventSource = new EventSource(`/api/v1/runs/${targetRunId}/stream`);
|
||||
eventSource.addEventListener("publication_discovered", (e) => {
|
||||
try {
|
||||
|
|
@ -312,6 +314,19 @@ export const useRunStatusStore = defineStore("runStatus", {
|
|||
console.error("Failed to parse SSE event", err);
|
||||
}
|
||||
});
|
||||
eventSource.addEventListener("scholar_progress", (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.data);
|
||||
const visited = typeof data.visited === "number" ? Math.max(0, Math.trunc(data.visited)) : null;
|
||||
const finished = typeof data.finished === "number" ? Math.max(0, Math.trunc(data.finished)) : null;
|
||||
const total = typeof data.total === "number" ? Math.max(1, Math.trunc(data.total)) : null;
|
||||
if (visited !== null && finished !== null && total !== null) {
|
||||
this.scholarProgress = { visited, finished, total };
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to parse SSE event", err);
|
||||
}
|
||||
});
|
||||
eventSource.onerror = () => {
|
||||
// Reconnecting is handled automatically by EventSource,
|
||||
// but if it's permanently closed, we could do something here.
|
||||
|
|
@ -491,6 +506,7 @@ export const useRunStatusStore = defineStore("runStatus", {
|
|||
this.lastSyncAt = null;
|
||||
this.safetyState = createDefaultSafetyState();
|
||||
this.livePublications = [];
|
||||
this.scholarProgress = null;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue