(function () { if (window.GIAComposePanelSend) { return; } const core = window.GIAComposePanelCore; if (!core) { return; } const createController = function (config) { const panel = config.panel; const panelId = config.panelId; const state = config.state; const thread = config.thread; const form = config.form; const textarea = config.textarea; const statusBox = config.statusBox; const manualConfirm = config.manualConfirm; const armInput = config.armInput; const confirmInput = config.confirmInput; const sendButton = config.sendButton; const sendCapable = config.sendCapable; const csrfToken = config.csrfToken; const queryParams = config.queryParams; const poll = config.poll; const clearReplyTarget = config.clearReplyTarget; const autosize = config.autosize; const flashCompose = config.flashCompose; const setStatus = config.setStatus; let transientCancelButton = null; let persistentCancelWrap = null; const postFormJson = async function (url, params) { const response = await fetch(url, { method: "POST", credentials: "same-origin", headers: { "X-CSRFToken": csrfToken, "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", }, body: params.toString(), }); if (!response.ok) { throw new Error("Request failed"); } return response.json(); }; const cancelSendRequest = function (commandId) { return postFormJson( String(panel.dataset.cancelSendUrl || ""), queryParams({ command_id: String(commandId || "") }) ); }; const hideTransientCancelButton = function () { if (!transientCancelButton) { return; } transientCancelButton.remove(); transientCancelButton = null; }; const showTransientCancelButton = function () { if (!statusBox || transientCancelButton) { return; } transientCancelButton = core.createNode( "button", "button is-danger is-light is-small compose-cancel-send-btn", "Cancel Send" ); transientCancelButton.type = "button"; transientCancelButton.addEventListener("click", async function () { try { await cancelSendRequest(""); } catch (_err) { // Ignore cancel failures. } finally { hideTransientCancelButton(); } }); statusBox.appendChild(transientCancelButton); }; const hidePersistentCancelButton = function () { if (!persistentCancelWrap) { return; } persistentCancelWrap.remove(); persistentCancelWrap = null; }; const stopPendingCommandPolling = function () { if (state.pendingCommandPoll) { clearInterval(state.pendingCommandPoll); state.pendingCommandPoll = null; } state.pendingCommandId = ""; state.pendingCommandAttempts = 0; state.pendingCommandStartedAt = 0; state.pendingCommandInFlight = false; }; const showPersistentCancelButton = function (commandId) { hidePersistentCancelButton(); if (!statusBox) { return; } persistentCancelWrap = core.createNode("div", "compose-persistent-cancel"); const button = core.createNode( "button", "button is-danger is-light is-small compose-persistent-cancel-btn", "Cancel Queued Send" ); button.type = "button"; button.addEventListener("click", async function () { try { await cancelSendRequest(commandId); stopPendingCommandPolling(); hidePersistentCancelButton(); setStatus("Send cancelled.", "warning"); await poll(true); } catch (_err) { hidePersistentCancelButton(); } }); persistentCancelWrap.appendChild(button); statusBox.appendChild(persistentCancelWrap); }; const pollPendingCommandResult = async function (commandId) { const url = new URL( String(panel.dataset.commandResultUrl || ""), window.location.origin ); url.searchParams.set("service", thread.dataset.service || ""); url.searchParams.set("command_id", commandId); url.searchParams.set("format", "json"); const response = await fetch(url.toString(), { credentials: "same-origin", headers: { "HX-Request": "true" }, }); if (!response.ok || response.status === 204) { return null; } return response.json(); }; const startPendingCommandPolling = function (commandId) { if (!commandId) { return; } stopPendingCommandPolling(); state.pendingCommandId = commandId; state.pendingCommandStartedAt = Date.now(); showPersistentCancelButton(commandId); state.pendingCommandPoll = setInterval(async function () { if (state.pendingCommandInFlight) { return; } state.pendingCommandAttempts += 1; if ( state.pendingCommandAttempts > 14 || (Date.now() - state.pendingCommandStartedAt) > 45000 ) { stopPendingCommandPolling(); hidePersistentCancelButton(); setStatus( "Send timed out waiting for runtime result. Please retry.", "warning" ); return; } try { state.pendingCommandInFlight = true; const payload = await pollPendingCommandResult(commandId); if (!payload || payload.pending !== false) { return; } const result = payload.result || {}; stopPendingCommandPolling(); hidePersistentCancelButton(); if (result.ok) { setStatus("", "success"); textarea.value = ""; clearReplyTarget(); autosize(); flashCompose("is-send-success"); await poll(true); return; } setStatus(String(result.error || "Send failed."), "danger"); flashCompose("is-send-fail"); await poll(true); } catch (_err) { // Ignore transient failures; the next poll can recover. } finally { state.pendingCommandInFlight = false; } }, 3500); }; const updateManualSafety = function () { const confirmed = !!(manualConfirm && manualConfirm.checked); if (armInput) { armInput.value = confirmed ? "1" : "0"; } if (confirmInput) { confirmInput.value = confirmed ? "1" : "0"; } if (sendButton) { sendButton.disabled = !sendCapable || !confirmed; } }; const bindSendEvents = function () { textarea.addEventListener("keydown", function (event) { if (event.key !== "Enter" || event.shiftKey) { return; } event.preventDefault(); if (sendButton && sendButton.disabled) { setStatus("Enable send confirmation before sending.", "warning"); return; } form.requestSubmit(); }); form.addEventListener("submit", function () { if (sendButton && sendButton.disabled) { return; } showTransientCancelButton(); }); form.addEventListener("htmx:afterRequest", function () { hideTransientCancelButton(); textarea.focus(); }); }; const bindDocumentEvents = function () { state.eventHandler = function (event) { const detail = (event && event.detail) || {}; const sourcePanelId = String(detail.panel_id || ""); if (sourcePanelId && sourcePanelId !== panelId) { return; } poll(true); }; document.body.addEventListener("composeMessageSent", state.eventHandler); state.sendResultHandler = function (event) { const detail = (event && event.detail) || {}; const sourcePanelId = String(detail.panel_id || ""); if (sourcePanelId && sourcePanelId !== panelId) { return; } hideTransientCancelButton(); if (detail.ok) { flashCompose("is-send-success"); textarea.value = ""; clearReplyTarget(); autosize(); poll(true); } else { flashCompose("is-send-fail"); if (detail.message) { setStatus(detail.message, detail.level || "danger"); } } textarea.focus(); }; document.body.addEventListener("composeSendResult", state.sendResultHandler); state.commandIdHandler = function (event) { const detail = (event && event.detail) || {}; const commandId = String( detail.command_id || (detail.composeSendCommandId && detail.composeSendCommandId.command_id) || "" ).trim(); if (commandId) { startPendingCommandPolling(commandId); } }; document.body.addEventListener( "composeSendCommandId", state.commandIdHandler ); }; const init = function () { bindSendEvents(); bindDocumentEvents(); if (manualConfirm) { manualConfirm.addEventListener("change", updateManualSafety); manualConfirm.dispatchEvent(new Event("change")); } else { updateManualSafety(); } }; return { init: init, resetForContextSwitch: function () { stopPendingCommandPolling(); hidePersistentCancelButton(); hideTransientCancelButton(); }, }; }; window.GIAComposePanelSend = { createController: createController, }; })();