/ wordpress / restai / admin / js / admin.js
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);