(function ($) { "use strict"; const qs = (selector, root = document) => root.querySelector(selector); const qsa = (selector, root = document) => Array.from(root.querySelectorAll(selector)); const config = window.wpawSettingsV2 || {}; function showToast(message, type = "success") { const toast = qs("#wpaw2-toast"); const body = qs("#wpaw2-toast-message"); if (!toast || !body) return; body.textContent = message; toast.className = "toast show toast-" + type; clearTimeout(toast._timer); toast._timer = setTimeout(() => { toast.classList.remove("show"); }, 2600); } function bindToastHover() { const toast = qs("#wpaw2-toast"); if (!toast) return; toast.addEventListener("mouseenter", () => { clearTimeout(toast._timer); }); toast.addEventListener("mouseleave", () => { toast._timer = setTimeout(() => { toast.classList.remove("show"); }, 1200); }); } function activateTab(target) { qsa("[data-aw2-tab]").forEach((button) => { const active = button.dataset.aw2Tab === target; button.setAttribute("aria-selected", active ? "true" : "false"); }); qsa(".tab-panel").forEach((panel) => { panel.classList.toggle("active", panel.id === target); }); const crumb = qs("#wpaw2-crumb-tab"); const activeButton = qs(`[data-aw2-tab="${target}"]`); if (crumb && activeButton) crumb.textContent = activeButton.dataset.label || activeButton.textContent.trim(); } function bindTabs() { qsa("[data-aw2-tab]").forEach((button) => { button.addEventListener("click", () => activateTab(button.dataset.aw2Tab), ); }); qsa("[data-aw2-subtab-target]").forEach((button) => { button.addEventListener("click", (e) => { const target = e.currentTarget.dataset.aw2SubtabTarget; const nav = e.currentTarget.closest(".subtab-nav"); const panelContainer = nav.parentElement; nav.querySelectorAll("[data-aw2-subtab-target]").forEach((btn) => { btn.setAttribute( "aria-selected", btn === e.currentTarget ? "true" : "false", ); }); panelContainer.querySelectorAll(".subtab-panel").forEach((panel) => { if (panel.id === target) { panel.classList.add("active"); } else { panel.classList.remove("active"); } }); }); }); } function bindPasswordToggles() { qsa("[data-aw2-toggle-password]").forEach((button) => { button.addEventListener("click", () => { const input = qs(button.dataset.aw2TogglePassword); if (!input) return; const isPassword = input.type === "password"; input.type = isPassword ? "text" : "password"; button.textContent = isPassword ? "Hide" : "Show"; }); }); } function bindCopyButtons() { qsa("[data-aw2-copy]").forEach((button) => { button.addEventListener("click", async () => { const source = qs(button.dataset.aw2Copy); if (!source) return; try { await navigator.clipboard.writeText(source.textContent.trim()); showToast("Copied command"); } catch (error) { showToast("Copy failed", "error"); } }); }); } function getModelsForType(type) { const bucket = config.models?.[type] || config.models?.execution || config.models?.planning || {}; const all = Array.isArray(bucket.all) ? bucket.all : []; const recommended = Array.isArray(bucket.recommended) ? bucket.recommended : []; const merged = [...all]; recommended.forEach((model) => { if (!merged.find((item) => item.id === model.id)) merged.push(model); }); return merged; } function modelTypeFromSelect(select) { const match = select.name.match(/\[(.+?)_model\]/); return match ? match[1] : "writing"; } function formatModelData(models) { return models.map((model) => ({ id: model.id, text: model.name || model.id, pricing: model.pricing || {}, is_free: Boolean(model.is_free), is_custom: Boolean(model.is_custom), })); } function formatModelOption(model) { if (!model.id) return model.text; const $row = $('
'); const $name = $('').text(model.text); $row.append($name); if (model.is_custom) { $row.append('Custom'); } else if (model.is_free) { $row.append('Free'); } else { const prompt = parseFloat(model.pricing?.prompt) || 0; const image = parseFloat(model.pricing?.image) || 0; const price = image > 0 ? image : prompt; if (price > 0) $row.append( $('').text( `$${(price * 1000000).toFixed(2)}/1M`, ), ); } return $row; } function initSelect2() { if (!$.fn.select2) return; const $languageSelect = $("#preferred_languages"); if ($languageSelect.length) { if ($languageSelect.data("select2")) { $languageSelect.select2("destroy"); } $languageSelect.select2({ width: "100%", placeholder: "Select preferred languages...", dropdownCssClass: "wpaw2-select2-dropdown", }); } qsa(".wpaw2-model-select").forEach((select) => { const type = modelTypeFromSelect(select); const models = getModelsForType(type); const currentValue = select.value || config.currentModels?.[type] || ""; const $select = $(select); if ($select.data("select2")) { $select.select2("destroy"); } $select.empty().select2({ width: "100%", data: formatModelData(models), placeholder: config.i18n?.searchPlaceholder || "Search models...", allowClear: true, dropdownCssClass: "wpaw2-select2-dropdown", templateResult: formatModelOption, templateSelection: (model) => model.text || model.id, language: { noResults: () => config.i18n?.noResults || "No models found", }, }); if (currentValue) { const model = models.find((item) => item.id === currentValue); const option = new Option( model?.name || currentValue, currentValue, true, true, ); $select.append(option).trigger("change"); } }); } function setSelectValue(select, value) { if (!select || !value) return; let option = Array.from(select.options).find( (item) => item.value === value, ); if (!option) { option = new Option(value, value, true, true); select.add(option); } select.value = value; if ($.fn.select2 && $(select).data("select2")) $(select).trigger("change"); else select.dispatchEvent(new Event("change", { bubbles: true })); } function bindPresetCards() { const presets = config.presets || {}; qsa("[data-aw2-preset]").forEach((card) => { card.addEventListener("click", () => { const preset = presets[card.dataset.aw2Preset]; if (!preset) return; Object.entries(preset).forEach(([key, value]) => { setSelectValue( qs(`[name="wp_agentic_writer_settings[${key}_model]"]`), value, ); }); qsa("[data-aw2-preset]").forEach((item) => item.classList.remove("active"), ); card.classList.add("active"); updateEstimate(); showToast("Preset applied"); }); }); } function updateEstimate() { const writing = qs('[name="wp_agentic_writer_settings[writing_model]"]')?.value || ""; const image = qs('[name="wp_agentic_writer_settings[image_model]"]')?.value || ""; let estimate = 0.14; const text = `${writing} ${image}`.toLowerCase(); if (text.includes("mistral") || text.includes("flash")) estimate = 0.06; if ( text.includes("gpt-4.1") || text.includes("opus") || text.includes("premium") ) estimate = 0.31; const output = qs("#wpaw2-cost-estimate"); if (output) output.textContent = `~$${estimate.toFixed(2)}`; } function bindEstimateInputs() { qsa(".wpaw2-model-select").forEach((select) => select.addEventListener("change", updateEstimate), ); updateEstimate(); } function bindTogglePanels() { qsa("[data-aw2-toggle-panel]").forEach((input) => { const panel = qs(input.dataset.aw2TogglePanel); if (!panel) return; const sync = () => { panel.hidden = !input.checked; }; input.addEventListener("change", sync); sync(); }); } function bindCustomModels() { const list = qs("#wpaw2-custom-models"); const add = qs("#wpaw2-add-custom-model"); if (!list || !add) return; add.addEventListener("click", () => { const row = document.createElement("div"); row.className = "custom-row"; row.innerHTML = ''; list.appendChild(row); }); list.addEventListener("click", (event) => { if (event.target.matches("[data-remove]")) event.target.closest(".custom-row")?.remove(); }); } function ajaxPost(action, data) { return $.ajax({ url: config.ajaxUrl, type: "POST", data: { action, nonce: config.nonce, ...data, }, }); } function bindAjaxButton(selector, action, getData, loadingText, onSuccess) { const button = qs(selector); if (!button) return; const original = button.textContent; button.addEventListener("click", () => { button.disabled = true; button.textContent = loadingText || "Testing..."; ajaxPost(action, getData ? getData() : {}) .done((response) => { if (response?.success && typeof onSuccess === "function") { onSuccess(response); } let message = response?.data?.message || (response?.success ? "Connection successful" : "Connection failed"); if (response?.data?.models_count) { message += ` (${response.data.models_count} models)`; } showToast(message, response?.success ? "success" : "error"); }) .fail((xhr) => { showToast( xhr.responseJSON?.data?.message || "Request failed", "error", ); }) .always(() => { button.disabled = false; button.textContent = original; }); }); } function loadCostLog() { const tbody = qs("#wpaw-cost-log-tbody"); if (!tbody) return; tbody.innerHTML = 'Loading cost data...'; ajaxPost("wpaw_get_cost_log_data", { page: 1, per_page: 25 }) .done((response) => { if (!response?.success) { tbody.innerHTML = 'Unable to load cost data.'; return; } const records = response.data?.records || []; if (!records.length) { tbody.innerHTML = 'Cost data will appear after OpenRouter generations.'; return; } tbody.innerHTML = records.map(renderCostLogRecord).join(""); bindCostLogToggles(tbody); }) .fail(() => { tbody.innerHTML = 'Unable to load cost data.'; }); } function renderCostLogRecord(record) { const postId = String(record.post_id ?? "0"); const rowId = `wpaw-cost-child-${postId.replace(/[^a-zA-Z0-9_-]/g, "-")}`; const title = escapeHtml(record.post_title || "System/Other"); const titleHtml = record.post_link ? `${title}` : title; const totalCost = escapeHtml(record.total_cost || "0.0000"); const callCount = Number(record.call_count || 0); const actionRows = buildActionSummaries(record.details || []); const hasActions = actionRows.length > 0; const actionSummary = hasActions ? `${actionRows.length} actions / ${callCount} calls` : `${callCount} calls`; const toggle = hasActions ? `` : ''; return ` ${toggle}${titleHtml} ${escapeHtml(actionSummary)} $${totalCost} ${renderActionSummaryTable(actionRows)} `; } function buildActionSummaries(details) { const grouped = new Map(); details.forEach((detail) => { const action = detail.action || "unknown"; const current = grouped.get(action) || { action, calls: 0, cost: 0, inputTokens: 0, outputTokens: 0, models: new Set(), }; current.calls += 1; current.cost += Number.parseFloat(detail.cost || 0) || 0; current.inputTokens += Number(detail.input_tokens || 0); current.outputTokens += Number(detail.output_tokens || 0); if (detail.model) current.models.add(detail.model); grouped.set(action, current); }); return Array.from(grouped.values()).sort((a, b) => b.cost - a.cost); } function renderActionSummaryTable(actionRows) { if (!actionRows.length) { return '
No action details available for this post.
'; } const rows = actionRows .map((item) => { const modelList = Array.from(item.models); const models = modelList.length ? modelList .map((m) => `${escapeHtml(m)}`) .join("") : "—"; const hasTokens = item.inputTokens > 0 || item.outputTokens > 0; const inLabel = hasTokens ? escapeHtml(String(item.inputTokens)) : "—"; const outLabel = hasTokens ? escapeHtml(String(item.outputTokens)) : "—"; return ` ${escapeHtml(formatActionLabel(item.action))} ${escapeHtml(String(item.calls))} ${models} ${inLabel} ${outLabel} $${escapeHtml(item.cost.toFixed(4))} `; }) .join(""); return `${rows}
ActionCallsModelsInputOutputCost
`; } function bindCostLogToggles(root) { // Accordion mode: expanding one row collapses all others. qsa("[data-cost-child]", root).forEach((button) => { button.addEventListener("click", () => { const child = qs(button.dataset.costChild); if (!child) return; const wasExpanded = button.getAttribute("aria-expanded") === "true"; // Collapse every open row first. qsa("[data-cost-child][aria-expanded='true']", root).forEach((btn) => { btn.setAttribute("aria-expanded", "false"); btn.textContent = "▸"; const sibling = qs(btn.dataset.costChild); if (sibling) sibling.hidden = true; }); // If the clicked row was not already open, expand it. if (!wasExpanded) { button.setAttribute("aria-expanded", "true"); button.textContent = "▾"; child.hidden = false; } }); }); } function formatActionLabel(action) { const normalized = String(action || "unknown").toLowerCase(); const labels = { image_generation: "Generate Image", generate_image: "Generate Image", block_refinement: "Block Refinement", chat: "Chat", }; if (labels[normalized]) return labels[normalized]; return normalized .replace(/[_-]+/g, " ") .replace(/\b\w/g, (letter) => letter.toUpperCase()); } function escapeHtml(value) { const div = document.createElement("div"); div.textContent = String(value ?? ""); return div.innerHTML; } function escapeAttr(value) { return escapeHtml(value).replace(/"/g, """); } function bindAjaxCheckers() { bindAjaxButton("#wpaw-test-api-key", "wpaw_test_api_connection", () => ({ api_key: qs("#openrouter_api_key")?.value || "", })); bindAjaxButton( "#wpaw-test-local-backend", "wpaw_test_local_backend", () => { // Find the first non-empty per-task model code for the test const modelInputs = qsa(".wpaw-ce-model"); let model = ""; for (const input of modelInputs) { if (input.value.trim()) { model = input.value.trim(); break; } } // Also sync the hidden legacy field const legacy = qs("#local_backend_model"); if (legacy) legacy.value = model; return { url: qs("#local_backend_url")?.value || "", key: qs("#local_backend_key")?.value || "", model: model, }; }, ); bindAjaxButton("#wpaw-test-memanto", "wpaw_test_memanto", () => ({ url: qs("#memanto_url")?.value || "", key: qs("#memanto_moorcheh_key")?.value || "", })); bindAjaxButton( "#wpaw-refresh-models", "wpaw_refresh_models", () => ({ api_key: qs("#openrouter_api_key")?.value || "", }), config.i18n?.refreshing || "Refreshing...", (response) => { if (response?.data?.models) { config.models = response.data.models; initSelect2(); updateEstimate(); } }, ); } function bindSaveShortcut() { document.addEventListener("keydown", (event) => { if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "s") { event.preventDefault(); qs("#wpaw2-settings-form")?.requestSubmit(); } }); } function init() { bindTabs(); bindPasswordToggles(); bindCopyButtons(); initSelect2(); bindPresetCards(); bindEstimateInputs(); bindTogglePanels(); bindCustomModels(); bindAjaxCheckers(); loadCostLog(); bindSaveShortcut(); bindToastHover(); qs("#wpaw2-toast-close")?.addEventListener("click", () => qs("#wpaw2-toast")?.classList.remove("show"), ); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })(jQuery);