admin.js
1 /* global RESTaiAdmin, jQuery, wp */ 2 (function ($) { 3 "use strict"; 4 5 const apiFetch = wp.apiFetch; 6 apiFetch.use(apiFetch.createNonceMiddleware(RESTaiAdmin.nonce)); 7 8 const url = (path) => RESTaiAdmin.restUrl + path; 9 10 function setStatus($el, text, ok) { 11 $el.text(text).removeClass("ok fail").addClass(ok ? "ok" : "fail"); 12 } 13 14 // --- Test connection --- 15 $("#restai-test-connection").on("click", function () { 16 const $btn = $(this); 17 const $status = $("#restai-connection-status"); 18 $btn.prop("disabled", true); 19 $status.text("…").removeClass("ok fail"); 20 21 apiFetch({ 22 url: url("/test-connection"), 23 method: "POST", 24 data: { 25 url: $("#restai_url").val(), 26 api_key: $("#restai_api_key").val(), 27 }, 28 }) 29 .then((res) => { 30 if (res && res.ok) { 31 setStatus($status, "✓ Connected as " + (res.username || "user"), true); 32 populateTeamsDropdown(); 33 } else { 34 setStatus($status, "✗ Authentication failed", false); 35 } 36 }) 37 .catch((err) => { 38 setStatus($status, "✗ " + (err.message || "error"), false); 39 }) 40 .finally(() => $btn.prop("disabled", false)); 41 }); 42 43 function populateTeamsDropdown() { 44 const $sel = $("#restai_team_id"); 45 if (!$sel.length) return; 46 const current = $sel.data("current") || $sel.val(); 47 apiFetch({ url: url("/teams"), method: "GET" }) 48 .then((res) => { 49 const teams = (res && res.teams) || []; 50 $sel.empty().append('<option value="">— select a team —</option>'); 51 teams.forEach((t) => { 52 const opt = $("<option/>").val(t.id).text(t.name + " (#" + t.id + ")"); 53 if (String(t.id) === String(current)) opt.attr("selected", "selected"); 54 $sel.append(opt); 55 }); 56 updateProvisionGate(); 57 }) 58 .catch(() => {}); 59 } 60 61 function updateProvisionGate() { 62 const teamSelected = !!$("#restai_team_id").val(); 63 $("#restai-provision").prop("disabled", !RESTaiAdmin.connected || !teamSelected); 64 } 65 66 function populateImageGeneratorDropdown() { 67 const $sel = $("#restai_image_generator"); 68 if (!$sel.length) return; 69 if (!RESTaiAdmin.connected || !$("#restai_team_id").val()) return; 70 const current = $sel.data("current") || $sel.val(); 71 apiFetch({ url: url("/team-resources"), method: "GET" }) 72 .then((res) => { 73 const gens = (res && res.image_generators) || []; 74 $sel.empty().append('<option value="">— Auto (first available in team) —</option>'); 75 gens.forEach((g) => { 76 const opt = $("<option/>").val(g).text(g); 77 if (g === current) opt.attr("selected", "selected"); 78 $sel.append(opt); 79 }); 80 }) 81 .catch(() => {}); 82 } 83 84 $(document).on("change", "#restai_team_id", function () { 85 updateProvisionGate(); 86 populateImageGeneratorDropdown(); 87 }); 88 89 // --- Auto-provision starter projects --- 90 $("#restai-provision").on("click", function () { 91 const $btn = $(this); 92 const $status = $("#restai-provision-status"); 93 $btn.prop("disabled", true); 94 $status.html('<span class="restai-spinner"></span> ' + (RESTaiAdmin.i18n.generating || "Working…")); 95 96 apiFetch({ url: url("/provision"), method: "POST" }) 97 .then((res) => { 98 const created = Object.keys(res.created || {}).length; 99 const existing = Object.keys(res.existing || {}).length; 100 const errors = Object.keys(res.errors || {}).length; 101 const warnings = Object.keys(res.warnings || {}).length; 102 let msg = created + " created · " + existing + " adopted"; 103 if (warnings) msg += " · " + warnings + " warnings"; 104 if (errors) msg += " · " + errors + " errors"; 105 $status.text((errors ? "⚠ " : "✓ ") + msg); 106 if (errors && res.errors.no_team) { 107 $status.text("✗ " + res.errors.no_team); 108 } 109 populateProjectDropdowns(); 110 }) 111 .catch((err) => { 112 $status.text("✗ " + (err.message || "error")); 113 }) 114 .finally(() => $btn.prop("disabled", false)); 115 }); 116 117 // --- Populate project dropdowns from RESTai --- 118 function populateProjectDropdowns() { 119 if (!RESTaiAdmin.connected) return; 120 apiFetch({ url: url("/projects"), method: "GET" }) 121 .then((res) => { 122 const projects = (res && res.projects) || []; 123 $("select.restai-project-select").each(function () { 124 const $sel = $(this); 125 const current = String($sel.val()); 126 $sel.empty().append('<option value="">— not configured —</option>'); 127 projects.forEach((p) => { 128 const opt = $("<option/>").val(p.id).text(p.name + " (#" + p.id + " · " + p.type + ")"); 129 if (String(p.id) === current) opt.attr("selected", "selected"); 130 $sel.append(opt); 131 }); 132 }); 133 }) 134 .catch(() => {}); 135 } 136 137 // On page load, populate dropdowns if connected. 138 $(function () { 139 populateProjectDropdowns(); 140 if (RESTaiAdmin.connected) { 141 populateTeamsDropdown(); 142 populateImageGeneratorDropdown(); 143 } 144 updateProvisionGate(); 145 }); 146 147 // --- Analytics: Push all to Support Bot --- 148 $(document).on("click", "#restai-push-all-knowledge", function () { 149 const $btn = $(this); 150 const $status = $("#restai-push-status"); 151 if (!confirm("Push every published post and page to the Support Bot RAG project? This may take a while on big sites.")) return; 152 153 $btn.prop("disabled", true); 154 $status.html('<span class="restai-spinner"></span> Pushing…'); 155 156 apiFetch({ 157 url: url("/sync-knowledge"), 158 method: "POST", 159 data: { full: true }, 160 }) 161 .then((res) => { 162 const pushed = res.pushed || 0; 163 const errors = res.errors || 0; 164 const skipped = res.skipped || 0; 165 let msg = "✓ Pushed " + pushed; 166 if (skipped) msg += " · skipped " + skipped + " empty"; 167 if (errors) { 168 msg += " · " + errors + " errors"; 169 if (res.error_messages && res.error_messages.length) { 170 msg += " — " + res.error_messages[0]; 171 } 172 } 173 $status.text(msg); 174 }) 175 .catch((err) => { 176 $status.text("✗ " + (err.message || "error")); 177 }) 178 .finally(() => { 179 $btn.prop("disabled", false); 180 }); 181 }); 182 183 // --- Analytics page --- 184 const $analytics = $("#restai-analytics-app"); 185 if ($analytics.length) { 186 apiFetch({ url: url("/analytics"), method: "GET" }) 187 .then((res) => { 188 const summary = res.summary || {}; 189 const tokens = (res.tokens && res.tokens.tokens) || []; 190 const total = tokens.reduce((acc, t) => acc + (t.input || 0) + (t.output || 0), 0); 191 const cost = tokens.reduce((acc, t) => acc + (t.cost || 0), 0); 192 193 const html = [ 194 '<div class="stat-grid">', 195 stat("Projects", summary.projects || 0), 196 stat("Users", summary.users || 0), 197 stat("Tokens (30d)", total.toLocaleString()), 198 stat("Cost (30d)", cost.toFixed(3)), 199 stat("Avg latency", (summary.avg_latency_ms || 0) + " ms"), 200 "</div>", 201 ].join(""); 202 $analytics.html(html); 203 }) 204 .catch(() => { 205 $analytics.html("<p>Failed to load analytics.</p>"); 206 }); 207 } 208 209 function stat(label, value) { 210 return ( 211 '<div class="stat">' + 212 '<div class="label">' + label + "</div>" + 213 '<div class="value">' + value + "</div>" + 214 "</div>" 215 ); 216 } 217 })(jQuery);