import { createAgentState, advanceAgent, replayAgent, describeAgent, eligibleAgentVisibility, } from "./agent-state.mjs"; const labels = Object.freeze({ queued: ["Waiting for an agent", "Ready to prepare", "Not started", "Queued"], assigned: [ "Journal agent assigned", "Preparing this task", "Agent assigned", "Agent assigned", ], filling: [ "Journal agent preparing", "Adding entry details", "Preparing", "Preparing entry", ], review: [ "Prepared by Journal agent", "Waiting for your review", "Ready for review", "Ready for review", ], }); export function initializeAgent(root, env = window) { const media = env.matchMedia("(prefers-reduced-motion: reduce)"); let state = createAgentState(env.location.search, media.matches); let visible = false; let frame = null; let previousTime = null; let lastRendered = ""; const replay = root.querySelector("[data-agent-replay]"); const announcement = root.parentElement.querySelector( "[data-agent-announcement]", ); const fields = [...root.querySelectorAll("[data-agent-field]")]; const slots = ["assignment", "queue-note", "status"].map((name) => root.querySelector(`[data-agent-${name}]`), ); function render() { const { phase, groups } = describeAgent(state); const signature = `${phase}:${groups}:${state.locked}`; if (signature === lastRendered) return; lastRendered = signature; root.dataset.agentPhase = phase; root.dataset.agentGroups = String(groups); slots.forEach((node, index) => { node.textContent = labels[phase][index]; }); fields.forEach((node) => { node.dataset.filled = String(Number(node.dataset.agentField) <= groups); }); announcement.textContent = labels[phase][3]; replay.disabled = state.locked || phase !== "review"; } function active() { return ( visible && !env.document.hidden && !state.locked && state.elapsed < 5000 ); } function tick(time) { frame = null; if (!active()) { previousTime = null; return; } state = advanceAgent( state, previousTime === null ? 0 : time - previousTime, true, ); previousTime = time; render(); if (active()) frame = env.requestAnimationFrame(tick); } function syncPlayback() { if (!active()) { if (frame !== null) env.cancelAnimationFrame(frame); frame = null; previousTime = null; } else if (frame === null) frame = env.requestAnimationFrame(tick); } const observer = new env.IntersectionObserver( (entries) => { const entry = entries[entries.length - 1]; visible = entry.isIntersecting && eligibleAgentVisibility( entry.boundingClientRect.height, entry.intersectionRect.height, env.innerHeight, ); syncPlayback(); }, { threshold: Array.from({ length: 101 }, (_, index) => index / 100) }, ); observer.observe(root); function handleReplay() { state = replayAgent(state); previousTime = null; render(); syncPlayback(); } function handleMotionChange(event) { if (event.matches) { state = createAgentState(env.location.search, true); render(); syncPlayback(); } } replay.addEventListener("click", handleReplay); env.document.addEventListener("visibilitychange", syncPlayback); media.addEventListener("change", handleMotionChange); render(); return () => { observer.disconnect(); if (frame !== null) env.cancelAnimationFrame(frame); replay.removeEventListener("click", handleReplay); env.document.removeEventListener("visibilitychange", syncPlayback); media.removeEventListener("change", handleMotionChange); }; }