PlotDirector/PlotLine/wwwroot/js/story-review-centre-progress.js

124 lines
4.6 KiB
JavaScript

(() => {
const characterTile = document.querySelector("[data-character-enrichment-tile]");
const plotTile = document.querySelector("[data-plot-synthesis-tile]");
if (!characterTile || typeof signalR === "undefined") {
return;
}
const bookId = Number(characterTile.dataset.bookId || plotTile?.dataset.bookId || 0);
const projectId = Number(characterTile.dataset.projectId || 0);
if (!bookId) {
return;
}
const encode = (value) => {
const div = document.createElement("div");
div.textContent = value || "";
return div.innerHTML;
};
const progressHtml = (total, processed, message) => {
const hasTotal = Number(total) > 0;
const percent = hasTotal ? Math.max(0, Math.min(100, Math.round((Number(processed) || 0) * 100 / Number(total)))) : null;
const count = hasTotal ? ` ${Number(processed || 0).toLocaleString()} / ${Number(total).toLocaleString()}` : "";
return `
<div class="story-review-centre-progress" data-progress-kind="${percent === null ? "indeterminate" : "determinate"}">
<div class="story-review-centre-progress__bar" role="progressbar" aria-valuemin="0" aria-valuemax="100"${percent === null ? "" : ` aria-valuenow="${percent}"`}>
<span style="width: ${percent === null ? 100 : percent}%"></span>
</div>
<p>${encode(message || "Creating summaries and extracting character details...")}${encode(count)}</p>
</div>`;
};
const setAction = (html) => {
const action = characterTile.querySelector("[data-character-enrichment-action]");
if (action) {
action.innerHTML = html;
}
};
const applyCharacter = (snapshot) => {
if (!snapshot || Number(snapshot.bookID || snapshot.bookId) !== bookId) {
return;
}
const statusValue = snapshot.status || snapshot.Status || "";
const status = characterTile.querySelector("[data-character-enrichment-status]");
const description = characterTile.querySelector("[data-character-enrichment-description]");
const progress = characterTile.querySelector("[data-character-enrichment-progress]");
const total = snapshot.totalCharacters ?? snapshot.TotalCharacters ?? 0;
const processed = snapshot.processedCharacters ?? snapshot.ProcessedCharacters ?? 0;
const message = snapshot.currentMessage || snapshot.CurrentMessage;
if (statusValue === "Queued" || statusValue === "Running") {
if (status) {
status.textContent = "Analysing";
status.className = "story-review-centre-tile__status story-review-centre-tile__status--ready";
}
if (description) {
description.textContent = "PlotDirector is creating character summaries and extracting supported details.";
}
if (progress) {
progress.innerHTML = progressHtml(total, processed, message);
}
setAction('<span class="btn btn-outline-primary btn-sm disabled" aria-disabled="true">Analysing</span>');
return;
}
if (statusValue === "Completed") {
if (status) {
status.textContent = "Complete";
status.className = "story-review-centre-tile__status story-review-centre-tile__status--ready";
}
if (description) {
description.textContent = "Character identities, summaries, and supported details are ready.";
}
if (progress) {
progress.innerHTML = "";
}
setAction(`<a class="btn btn-outline-primary btn-sm" href="/Characters?projectId=${projectId}">View Characters</a>`);
return;
}
if (statusValue === "Failed") {
if (status) {
status.textContent = "Failed";
status.className = "story-review-centre-tile__status story-review-centre-tile__status--warning";
}
if (description) {
description.textContent = snapshot.errorMessage || snapshot.ErrorMessage || "Character enrichment failed and can be retried.";
}
if (progress) {
progress.innerHTML = "";
}
}
};
const connection = new signalR.HubConnectionBuilder()
.withUrl("/hubs/story-intelligence")
.withAutomaticReconnect()
.build();
const refresh = () => {
connection.invoke("WatchCharacterEnrichment", bookId)
.then(applyCharacter)
.catch(() => {});
};
connection.on("CharacterEnrichmentProgressChanged", applyCharacter);
connection.on("StoryIntelligenceGlobalProgressChanged", () => {
if (plotTile?.querySelector(".story-review-centre-progress")) {
window.setTimeout(() => window.location.reload(), 700);
}
});
connection.onreconnected(refresh);
const start = () => {
connection.start()
.then(refresh)
.catch(() => window.setTimeout(start, 2000));
};
start();
})();