(() => { const progressRoots = [...document.querySelectorAll("[data-story-intelligence-progress]")]; const dashboardRoots = [...document.querySelectorAll("[data-story-intelligence-dashboard]")]; const roots = [...progressRoots, ...dashboardRoots]; if (roots.length === 0 || !window.signalR) { return; } const field = (state, camel, pascal) => state?.[camel] ?? state?.[pascal] ?? null; const jobIds = [...new Set(roots.map((root) => root.dataset.storyIntelligenceJobId).filter(Boolean))]; const updateRoot = (root, state) => { const percent = field(state, "progressPercent", "ProgressPercent") ?? 0; const status = field(state, "status", "Status") || "Pending"; const stage = field(state, "currentStage", "CurrentStage") || status; const message = field(state, "currentMessage", "CurrentMessage") || "Story Intelligence is preparing."; const elapsed = field(state, "elapsedTime", "ElapsedTime") || "0 sec"; const remaining = field(state, "estimatedRemaining", "EstimatedRemaining") || "Calculating"; root.querySelectorAll("[data-story-intelligence-message]").forEach((node) => { node.textContent = message; }); root.querySelectorAll("[data-story-intelligence-percent]").forEach((node) => { node.textContent = `${percent}%`; }); root.querySelectorAll("[data-story-intelligence-progress-bar]").forEach((node) => { node.style.width = `${Math.max(0, Math.min(100, Number.parseInt(percent, 10) || 0))}%`; }); root.querySelectorAll("[data-story-intelligence-stage]").forEach((node) => { node.textContent = stage; }); root.querySelectorAll("[data-story-intelligence-status]").forEach((node) => { node.textContent = status; }); root.querySelectorAll("[data-story-intelligence-elapsed]").forEach((node) => { node.textContent = elapsed; }); root.querySelectorAll("[data-story-intelligence-remaining]").forEach((node) => { node.textContent = remaining; }); const completed = !!field(state, "isCompleted", "IsCompleted"); root.querySelectorAll("[data-story-intelligence-complete-link]").forEach((node) => { node.classList.toggle("disabled", !completed); node.setAttribute("aria-disabled", String(!completed)); }); }; const applyProgress = (state) => { const jobId = field(state, "jobID", "JobID") || field(state, "jobId", "JobId"); if (!jobId) { return; } roots .filter((root) => root.dataset.storyIntelligenceJobId?.toLowerCase() === String(jobId).toLowerCase()) .forEach((root) => updateRoot(root, state)); if (progressRoots.length > 0 && field(state, "isCompleted", "IsCompleted")) { window.location.href = `/onboarding/story-intelligence/complete?jobId=${encodeURIComponent(jobId)}`; } }; const connection = new signalR.HubConnectionBuilder() .withUrl("/hubs/story-intelligence") .withAutomaticReconnect() .build(); connection.on("StoryIntelligenceProgressChanged", applyProgress); connection.onreconnected(() => { jobIds.forEach((jobId) => connection.invoke("WatchStoryIntelligenceJob", jobId).then(applyProgress).catch(() => {})); }); connection.start() .then(() => Promise.all(jobIds.map((jobId) => connection.invoke("WatchStoryIntelligenceJob", jobId)))) .then((states) => states.filter(Boolean).forEach(applyProgress)) .catch(() => {}); })();