168 lines
6.5 KiB
JavaScript
168 lines
6.5 KiB
JavaScript
(() => {
|
|
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 body = new URLSearchParams();
|
|
for (const [key, value] of new FormData(form).entries()) {
|
|
if (typeof value === "string") body.append(key, value);
|
|
}
|
|
const response = await fetch(form.action, {
|
|
method: form.method || "POST",
|
|
headers: {
|
|
Accept: "application/json",
|
|
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
|
|
},
|
|
body
|
|
});
|
|
const result = await response.json().catch(() => ({}));
|
|
if (!response.ok || result.success === false) throw new Error(result.message || result.error || "The feedback could not be updated.");
|
|
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;
|
|
stream = new EventSource("/api/events");
|
|
stream.addEventListener("ai:improvement_changed", (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.");
|
|
}
|
|
});
|
|
stream.onerror = () => {
|
|
showStatus("Live updates are reconnecting. Your saved actions are still safe.", "warning");
|
|
};
|
|
}
|
|
|
|
connectLiveUpdates();
|
|
})();
|