refactor: Cleanup git state - commit all staged changes

Major refactoring cleanup:
- Add new controller architecture (class-controller-*.php)
- Add new settings-v2 UI (views/settings-v2/)
- Add new CSS architecture (agentic-sidebar.css, tokens)
- Add esbuild build pipeline (scripts/build.js, package.json)
- Add composer dependencies (vendor/)
- Add frontend src directory (assets/js/src/index.jsx)
- Add documentation files
- Remove old/obsolete files (class-settings.php, old CSS)

This commits all pending changes from previous refactoring efforts.
This commit is contained in:
Dwindi Ramadhana
2026-06-17 05:27:58 +07:00
parent d3f142222c
commit 690991c526
7963 changed files with 941566 additions and 67372 deletions

View File

@@ -0,0 +1,599 @@
(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 = $('<div class="wpaw2-model-option"></div>');
const $name = $('<span class="wpaw2-model-name"></span>').text(model.text);
$row.append($name);
if (model.is_custom) {
$row.append('<span class="wpaw2-model-badge">Custom</span>');
} else if (model.is_free) {
$row.append('<span class="wpaw2-model-badge free">Free</span>');
} 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(
$('<span class="wpaw2-model-price"></span>').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 =
'<input class="field-control" type="text" data-field="id" placeholder="provider/model-id"><input class="field-control" type="text" data-field="name" placeholder="Display name"><select class="field-control" data-field="type"><option value="text">Text</option><option value="image">Image</option></select><button type="button" class="btn btn-danger btn-small" data-remove>Remove</button>';
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 = '<tr><td colspan="3">Loading cost data...</td></tr>';
ajaxPost("wpaw_get_cost_log_data", { page: 1, per_page: 25 })
.done((response) => {
if (!response?.success) {
tbody.innerHTML =
'<tr><td colspan="3">Unable to load cost data.</td></tr>';
return;
}
const records = response.data?.records || [];
if (!records.length) {
tbody.innerHTML =
'<tr><td colspan="3">Cost data will appear after OpenRouter generations.</td></tr>';
return;
}
tbody.innerHTML = records.map(renderCostLogRecord).join("");
bindCostLogToggles(tbody);
})
.fail(() => {
tbody.innerHTML =
'<tr><td colspan="3">Unable to load cost data.</td></tr>';
});
}
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
? `<a href="${escapeAttr(record.post_link)}">${title}</a>`
: 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
? `<button type="button" class="cost-row-toggle" aria-expanded="false" aria-controls="${escapeAttr(rowId)}" data-cost-child="#${escapeAttr(rowId)}">▸</button>`
: '<span class="cost-row-toggle-placeholder"></span>';
return `
<tr class="cost-parent-row">
<td>${toggle}<span class="cost-post-title">${titleHtml}</span></td>
<td>${escapeHtml(actionSummary)}</td>
<td>$${totalCost}</td>
</tr>
<tr class="cost-child-row" id="${escapeAttr(rowId)}" hidden>
<td colspan="3">
${renderActionSummaryTable(actionRows)}
</td>
</tr>`;
}
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 '<div class="cost-child-empty">No action details available for this post.</div>';
}
const rows = actionRows
.map((item) => {
const modelList = Array.from(item.models);
const models = modelList.length
? modelList
.map((m) => `<span class="model-pill">${escapeHtml(m)}</span>`)
.join("")
: "—";
const hasTokens = item.inputTokens > 0 || item.outputTokens > 0;
const inLabel = hasTokens ? escapeHtml(String(item.inputTokens)) : "—";
const outLabel = hasTokens
? escapeHtml(String(item.outputTokens))
: "—";
return `<tr>
<td>${escapeHtml(formatActionLabel(item.action))}</td>
<td>${escapeHtml(String(item.calls))}</td>
<td>${models}</td>
<td>${inLabel}</td>
<td>${outLabel}</td>
<td>$${escapeHtml(item.cost.toFixed(4))}</td>
</tr>`;
})
.join("");
return `<table class="cost-action-table">
<thead><tr><th>Action</th><th>Calls</th><th>Models</th><th>Input</th><th>Output</th><th>Cost</th></tr></thead>
<tbody>${rows}</tbody>
</table>`;
}
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, "&quot;");
}
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);