(() => { let stream = null; let refreshTimer = null; let refreshing = false; let queuedRefresh = false; const status = () => document.querySelector("[data-improvement-status]"); const workspace = () => document.querySelector("[data-improvement-workspace]"); const historyDialog = () => document.getElementById("improvement-history"); function showStatus(message, tone = "info") { const target = status(); if (!target) return; target.textContent = message || ""; target.dataset.tone = tone; target.hidden = !message; if (message && tone !== "error") window.setTimeout(() => { if (target.textContent === message) target.hidden = true; }, 5000); } function historyParams() { const dialog = historyDialog(); const form = dialog?.querySelector("[data-improvement-history-search]"); const params = new URLSearchParams(); const query = form?.elements?.history_q?.value?.trim(); const outcome = form?.elements?.history_status?.value; if (query) params.set("history_q", query); if (outcome) params.set("history_status", outcome); return params; } async function refreshWorkspace(url = null, options = {}) { if (refreshing) { queuedRefresh = true; return; } refreshing = true; const wasHistoryOpen = options.openHistory ?? Boolean(historyDialog()?.open); const activeElementId = document.activeElement?.id || ""; try { const params = url ? new URL(url, window.location.origin).searchParams : historyParams(); const response = await fetch(`/plugins/lumi_ai/improvement_center?${params.toString()}`, { headers: { Accept: "text/html", "X-Lumi-Live-Refresh": "1" }, cache: "no-store" }); if (!response.ok) throw new Error("The latest feedback queue could not be loaded."); const html = await response.text(); const documentCopy = new DOMParser().parseFromString(html, "text/html"); const next = documentCopy.querySelector("[data-improvement-workspace]"); const current = workspace(); if (!next || !current) throw new Error("The refreshed feedback view was incomplete."); current.replaceWith(next); window.LumiInteractions?.init?.(next); if (wasHistoryOpen) historyDialog()?.showModal(); if (activeElementId) document.getElementById(activeElementId)?.focus?.(); } catch (error) { showStatus(error.message || "The feedback queue could not be refreshed.", "error"); } finally { refreshing = false; if (queuedRefresh) { queuedRefresh = false; refreshWorkspace(null, { openHistory: wasHistoryOpen }); } } } function scheduleRefresh(message) { if (message) showStatus(message); window.clearTimeout(refreshTimer); refreshTimer = window.setTimeout(() => refreshWorkspace(), 120); } document.addEventListener("click", (event) => { const opener = event.target.closest("[data-open-dialog]"); if (opener) { document.getElementById(opener.dataset.openDialog)?.showModal(); return; } if (event.target.closest("[data-open-history]")) { historyDialog()?.showModal(); historyDialog()?.querySelector("input[type='search']")?.focus(); return; } const closer = event.target.closest("[data-close-dialog]"); if (closer) { closer.closest("dialog")?.close(); return; } if (event.target.closest("[data-history-clear]")) { const form = historyDialog()?.querySelector("[data-improvement-history-search]"); form?.reset(); refreshWorkspace("/plugins/lumi_ai/improvement_center", { openHistory: true }); return; } const pageLink = event.target.closest("[data-improvement-page], [data-improvement-history-page]"); if (pageLink && !pageLink.classList.contains("disabled")) { event.preventDefault(); refreshWorkspace(pageLink.href, { openHistory: pageLink.matches("[data-improvement-history-page]") }); } }); document.addEventListener("submit", async (event) => { const form = event.target; if (!(form instanceof HTMLFormElement)) return; if (form.matches("[data-improvement-history-search]")) { event.preventDefault(); const params = new URLSearchParams(new FormData(form)); params.delete("history_page"); return refreshWorkspace(`/plugins/lumi_ai/improvement_center?${params.toString()}`, { openHistory: true }); } if (!form.matches("[data-improvement-action]")) return; // The shared timed-confirmation handler runs in the capture phase. Let it // issue a token before AJAX handles permanent deletes. if (event.defaultPrevented && !form.elements.confirmation_token?.value) return; event.preventDefault(); const submitter = event.submitter || form.querySelector("button[type='submit']"); const originalLabel = submitter?.textContent || ""; if (submitter) { submitter.disabled = true; submitter.textContent = "Saving…"; } try { const action = window.LumiForms?.action(form) || form.getAttribute("action") || window.location.href; const body = new URLSearchParams(); for (const [key, value] of new FormData(form).entries()) { if (typeof value === "string") body.append(key, value); } const response = await fetch(action, { method: window.LumiForms?.method(form) || form.getAttribute("method") || "POST", headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", "X-Lumi-Response-Format": "json" }, body, credentials: "same-origin", cache: "no-store" }); const responseText = await response.text(); let result = {}; try { result = responseText ? JSON.parse(responseText) : {}; } catch {} if (response.redirected) { throw new Error("Your session changed while saving. Refresh the page, sign in again if needed, and retry."); } if (!response.ok || result.success === false) { const fallback = response.status === 403 ? "You no longer have permission to perform this feedback action." : response.status === 404 ? "This feedback record is no longer available. Refresh the queue and try again." : `The feedback could not be updated (HTTP ${response.status}).`; throw new Error(result.message || result.error || fallback); } if (!result.message && !responseText) { throw new Error("Lumi returned an empty response while saving feedback. Check the server diagnostics and try again."); } form.closest("dialog")?.close(); showStatus(result.message || "Feedback updated.", "success"); await refreshWorkspace(null, { openHistory: Boolean(historyDialog()?.open) }); } catch (error) { showStatus(error.message || "The feedback could not be updated.", "error"); if (submitter) { submitter.disabled = false; submitter.textContent = originalLabel; } } }); function connectLiveUpdates() { if (!window.EventSource || stream) return; const handleChange = (event) => { try { const data = JSON.parse(event.data || "{}"); scheduleRefresh(data.message || "The feedback queue was updated by another reviewer."); } catch { scheduleRefresh("The feedback queue was updated by another reviewer."); } }; if (window.LumiEvents?.subscribe) { const unsubscribe = window.LumiEvents.subscribe("ai:improvement_changed", handleChange); const handleStatus = (event) => { if (event.detail?.status === "disconnected") { showStatus("Live updates are reconnecting. Your saved actions are still safe.", "warning"); } }; window.addEventListener("lumi:event-status", handleStatus); stream = { close() { unsubscribe(); window.removeEventListener("lumi:event-status", handleStatus); } }; return; } stream = new EventSource("/api/events"); stream.addEventListener("ai:improvement_changed", handleChange); stream.onerror = () => { showStatus("Live updates are reconnecting. Your saved actions are still safe.", "warning"); }; window.addEventListener("pagehide", () => stream?.close(), { once: true }); } connectLiveUpdates(); })();