(() => { const strip = document.querySelector("[data-story-intelligence-global-strip]"); if (!strip || !window.signalR) { return; } const title = strip.querySelector("[data-story-intelligence-global-title]"); const detail = strip.querySelector("[data-story-intelligence-global-detail]"); const bar = strip.querySelector("[data-story-intelligence-global-bar]"); const readNumber = (value) => { const parsed = Number.parseInt(value || "", 10); return Number.isInteger(parsed) ? parsed : null; }; const trackedBookId = readNumber(strip.dataset.bookId); const trackedProjectId = readNumber(strip.dataset.projectId); const formatCount = (value) => new Intl.NumberFormat().format(Math.max(0, value || 0)); const estimateRemaining = (elapsedMs, completed, total) => { if (!total || !completed) { return "Calculating"; } if (completed >= total) { return "Finalising"; } const remainingMs = (elapsedMs / completed) * (total - completed); const remainingSeconds = Math.max(1, Math.round(remainingMs / 1000)); if (remainingSeconds >= 60) { return `${Math.max(1, Math.round(remainingSeconds / 60))} min`; } return `${remainingSeconds} sec`; }; const update = (progress) => { if (!progress) { return; } const bookId = readNumber(progress.bookID ?? progress.bookId); const projectId = readNumber(progress.projectID ?? progress.projectId); if (trackedBookId && bookId && trackedBookId !== bookId) { return; } if (!trackedBookId && trackedProjectId && projectId && trackedProjectId !== projectId) { return; } const status = progress.status || progress.Status || ""; const total = readNumber(progress.totalDetectedScenes ?? progress.TotalDetectedScenes) || 0; const completed = readNumber(progress.completedScenes ?? progress.CompletedScenes) || 0; const failed = readNumber(progress.failedScenes ?? progress.FailedScenes) || 0; const elapsedMs = readNumber(progress.totalDurationMs ?? progress.TotalDurationMs) || 0; const percent = total > 0 ? Math.max(0, Math.min(100, Math.round((completed / total) * 100))) : 0; if (bar) { bar.style.width = `${percent}%`; } if (detail) { const remaining = estimateRemaining(elapsedMs, completed, total); detail.textContent = `${formatCount(completed)} of ${formatCount(total)} scenes analysed \u00b7 ${remaining} remaining`; } if (title && status) { if (status === "Failed") { title.textContent = "Story Intelligence needs attention"; } else if (status === "Cancelled") { title.textContent = "Story Intelligence cancelled"; } else if (status === "Completed" || status === "CompletedWithWarnings") { title.textContent = failed > 0 ? "Story Intelligence completed with notes" : "Story Intelligence complete"; window.setTimeout(() => { strip.hidden = true; }, 10000); } } }; const connection = new signalR.HubConnectionBuilder() .withUrl("/hubs/story-intelligence") .withAutomaticReconnect() .build(); connection.on("StoryIntelligenceRunProgressChanged", update); connection.start().catch(() => { strip.dataset.connection = "offline"; }); })();