PlotDirector/PlotLine/wwwroot/js/story-intelligence-global-progress.js

114 lines
4.2 KiB
JavaScript

(() => {
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]");
let refreshTimer = null;
const readNumber = (value) => {
const parsed = Number.parseInt(value || "", 10);
return Number.isInteger(parsed) ? parsed : null;
};
const formatCount = (value) => new Intl.NumberFormat().format(Math.max(0, value || 0));
const estimateRemaining = (elapsedMs, completed, failed, total, status) => {
if (status === "Finalising") {
return "Finalising";
}
if (!total || !completed) {
return "Calculating";
}
const remainingScenes = Math.max(0, total - completed - failed);
if (remainingScenes === 0) {
return "Finalising";
}
const remainingMs = (elapsedMs / completed) * remainingScenes;
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 render = (progress) => {
const books = progress?.books || progress?.Books || [];
if (!books.length) {
strip.hidden = true;
return;
}
strip.hidden = false;
const primary = books.find((book) => book.hasActiveRuns || book.HasActiveRuns) || books[0];
const activeBookCount = readNumber(progress.activeBookCount ?? progress.ActiveBookCount) || books.length;
const status = progress.status || progress.Status || primary.status || primary.Status || "";
const total = readNumber(progress.totalExpectedScenes ?? progress.TotalExpectedScenes) || 0;
const completed = readNumber(progress.completedScenes ?? progress.CompletedScenes) || 0;
const failed = readNumber(progress.failedScenes ?? progress.FailedScenes) || 0;
const active = readNumber(progress.activeScenes ?? progress.ActiveScenes) || 0;
const elapsedMs = readNumber(primary.totalDurationMs ?? primary.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, failed, total, status);
const failureText = failed > 0 ? ` \u00b7 ${formatCount(failed)} failed` : "";
const activeText = active > 0 ? ` \u00b7 ${formatCount(active)} active` : "";
detail.textContent = `${formatCount(completed)} of ${formatCount(total)} scenes analysed${failureText}${activeText} \u00b7 ${remaining} remaining`;
}
if (title && status) {
if (status === "Finalising") {
title.textContent = "Finalising Story Intelligence";
} else 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);
} else if (activeBookCount > 1) {
title.textContent = `${formatCount(activeBookCount)} books analysing`;
} else {
title.textContent = primary.summaryText || primary.SummaryText || title.textContent;
}
}
};
const refresh = () => {
connection.invoke("WatchGlobalStoryIntelligenceProgress")
.then(render)
.catch(() => {
strip.dataset.connection = "offline";
});
};
const scheduleRefresh = () => {
window.clearTimeout(refreshTimer);
refreshTimer = window.setTimeout(refresh, 350);
};
const connection = new signalR.HubConnectionBuilder()
.withUrl("/hubs/story-intelligence")
.withAutomaticReconnect()
.build();
connection.on("StoryIntelligenceRunProgressChanged", scheduleRefresh);
connection.start()
.then(refresh)
.catch(() => {
strip.dataset.connection = "offline";
});
})();