(() => { 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 `

${encode(message || "Creating summaries and extracting character details...")}${encode(count)}

`; }; 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('Analysing'); 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(`View Characters`); 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(); })();