PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.23
MxChat – AI Chatbot & Content Generation for WordPress v3.2.23
3.2.25 3.2.24 3.2.23 3.2.22 3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 All 156 releases
mxchat-basic / js / mxchat_actions.js

mxchat_actions.js in MxChat – AI Chatbot & Content Generation for WordPress 3.2.23, at js/mxchat_actions.js

1,556 lines 65.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * MxChat Actions Page JavaScript - v2.0
3 * Split-panel layout with action list and editor
4 */
5 jQuery(document).ready(function($) {
6 // ==========================================================================
7 // Sidebar Navigation
8 // ==========================================================================
9
10 // Desktop sidebar navigation
11 $('.mxch-nav-link').on('click', function(e) {
12 e.preventDefault();
13 const target = $(this).data('target');
14
15 // Update active states
16 $('.mxch-nav-link').removeClass('active');
17 $(this).addClass('active');
18
19 // Show target section
20 $('.mxch-section').removeClass('active');
21 $('#' + target).addClass('active');
22
23 // Also update mobile nav if open
24 $('.mxch-mobile-nav-link').removeClass('active');
25 $('.mxch-mobile-nav-link[data-target="' + target + '"]').addClass('active');
26
27 // Load actions when switching to all-actions
28 if (target === 'all-actions' && !actionsLoaded) {
29 loadActionList(1, '', '');
30 }
31 });
32
33 // Mobile menu toggle
34 $('.mxch-mobile-menu-btn').on('click', function() {
35 $('.mxch-mobile-menu').addClass('open');
36 $('.mxch-mobile-overlay').addClass('open');
37 });
38
39 // Close mobile menu
40 $('.mxch-mobile-menu-close, .mxch-mobile-overlay').on('click', function() {
41 $('.mxch-mobile-menu').removeClass('open');
42 $('.mxch-mobile-overlay').removeClass('open');
43 });
44
45 // Mobile navigation
46 $('.mxch-mobile-nav-link').on('click', function(e) {
47 e.preventDefault();
48 const target = $(this).data('target');
49
50 $('.mxch-mobile-nav-link').removeClass('active');
51 $(this).addClass('active');
52
53 $('.mxch-section').removeClass('active');
54 $('#' + target).addClass('active');
55
56 $('.mxch-nav-link').removeClass('active');
57 $('.mxch-nav-link[data-target="' + target + '"]').addClass('active');
58
59 $('.mxch-mobile-menu').removeClass('open');
60 $('.mxch-mobile-overlay').removeClass('open');
61 });
62
63 // ==========================================================================
64 // AI Tools: autosave the function-calling toggle + per-tool checklist
65 // (matches MxChat's autosave UX — no Save button). plan a41dee.
66 // ==========================================================================
67 var $fc = $('#mxch-fc-settings');
68 if ($fc.length) {
69 var fcNonce = $fc.data('fc-nonce');
70 var $fcStatus = $('#mxch-fc-save-status');
71 var fcTimer = null;
72
73 function fcCollectAndSave() {
74 // Presence = active (plan d450a7): the global on/off toggle is gone, so
75 // the gate is derived from whether any tool is enabled. The server
76 // derives this too; we send it for an immediate, correct nav badge.
77 var enabled = $fc.find('input[name="mxchat_fc_tools[]"]:checked').length > 0 ? '1' : '0';
78 var allTools = $fc.find('input[name="mxchat_fc_all_tools[]"]').map(function () { return $(this).val(); }).get();
79 var checked = $fc.find('input[name="mxchat_fc_tools[]"]:checked').map(function () { return $(this).val(); }).get();
80 // Per-tool "when to use" hints, keyed by callback.
81 var hints = {};
82 $fc.find('.mxch-fc-hint-input').each(function () {
83 var cb = $(this).data('fc-callback');
84 if (cb) { hints[cb] = $(this).val(); }
85 });
86
87 if ($fcStatus.length) { $fcStatus.text('Saving…').removeClass('is-saved is-error'); }
88
89 $.post(mxchActionsData.ajaxUrl, {
90 action: 'mxchat_fc_autosave',
91 nonce: fcNonce,
92 enabled: enabled,
93 all_tools: allTools,
94 tools: checked,
95 hints: hints
96 }).done(function () {
97 if ($fcStatus.length) {
98 $fcStatus.text('Saved').addClass('is-saved');
99 setTimeout(function () { $fcStatus.text('').removeClass('is-saved'); }, 2000);
100 }
101 }).fail(function () {
102 if ($fcStatus.length) { $fcStatus.text('Save failed — try again').addClass('is-error'); }
103 });
104 }
105
106 // The hidden store's checkboxes are toggled programmatically by the
107 // list/detail/modal flow (which calls fcCollectAndSave() directly). Keep
108 // a change-bound autosave as a safety net for any direct mutation.
109 $fc.on('change', 'input[name="mxchat_fc_tools[]"]', function () {
110 clearTimeout(fcTimer);
111 fcTimer = setTimeout(fcCollectAndSave, 200);
112 });
113
114 // ======================================================================
115 // AI Tools MASTER-DETAIL list + detail panel (plan d450a7).
116 // Clones the Trigger Phrases list/detail UX: the left list shows the
117 // ACTIVE tools (a tool in the list = active — no global toggle, no
118 // per-tool enable checkbox); selecting one shows its detail on the right
119 // with Edit (usage note) + Remove. "Add" opens the same two-step
120 // capability picker. Pure view layer over the hidden #mxch-fc-tool-store
121 // inputs — the unchanged mxchat_fc_autosave contract (mxchat_fc_tools[] /
122 // mxchat_fc_all_tools[] + hints) stays the source of truth. FC-specific
123 // IDs/classes keep the Trigger Phrases JS off these elements.
124 // ======================================================================
125 var $fcStore = $('#mxch-fc-tool-store');
126 if ($fcStore.length) {
127 var $fcPanel = $('#mxch-fc-panel');
128 var $fcList = $('#mxch-fc-list');
129 var $fcCount = $('#mxch-fc-count');
130 var $fcEmpty = $('#mxch-fc-empty');
131 var $fcView = $('#mxch-fc-view');
132 var $fcDetail = $('#mxch-fc-detail-panel');
133 var $fcSearch = $('#mxch-fc-search');
134 var $fcModal = $('#mxch-fc-tool-modal');
135 var fcCurrentCb = null; // tool being added/edited in the modal
136 var fcViewCb = null; // tool currently shown in the detail panel
137 var fcGuardedClose = null; // dirty-checked close, re-armed on each open
138 var fcSelected = new Set(); // callbacks ticked for bulk remove (plan 5f7409)
139
140 var fcI18n = {
141 tools: $fcPanel.data('i18n-tools') || 'tools',
142 tool: $fcPanel.data('i18n-tool') || 'tool',
143 active: $fcPanel.data('i18n-active') || 'Active',
144 addon: $fcPanel.data('i18n-addon') || 'Add-on',
145 sensitive: $fcPanel.data('i18n-sensitive') || 'Sensitive',
146 noHint: $fcPanel.data('i18n-nohint') || 'No usage note — the AI decides on its own when to use this.'
147 };
148 // Localized Add/Edit titles + button labels read from the modal DOM.
149 var $fcStep2Title = $('#mxch-fc-modal-step2-title');
150 var $fcSaveBtn = $('#mxch-fc-modal-save');
151 var FC_TITLE_ADD = $fcStep2Title.data('add-title') || 'Add Tool';
152 var FC_TITLE_EDIT = $fcStep2Title.data('edit-title') || 'Edit Tool';
153 var FC_BTN_ADD = $fcSaveBtn.data('add-label') || 'Add Tool';
154 var FC_BTN_SAVE = $fcSaveBtn.data('save-label') || 'Save Changes';
155
156 function fcEsc(s) { return $('<div>').text(s == null ? '' : String(s)).html(); }
157 function fcEntry(cb) {
158 return $fcStore.find('.mxch-fc-tool').filter(function () {
159 return String($(this).data('fc-callback')) === String(cb);
160 });
161 }
162 function fcIsOn($t) { return $t.find('input[name="mxchat_fc_tools[]"]').is(':checked'); }
163 function fcIsMobile() { return window.innerWidth <= 782; }
164
165 // Show the empty "Select a tool" state in the detail panel.
166 function fcShowEmpty() {
167 fcViewCb = null;
168 if ($fcView.length) $fcView.hide();
169 if ($fcEmpty.length) $fcEmpty.show();
170 $fcList.find('.mxch-fc-list-item').removeClass('active');
171 if (fcIsMobile()) { $fcDetail.removeClass('mobile-active'); $('body').removeClass('mxch-mobile-panel-open'); }
172 }
173
174 // Build the left list from the store (active tools only), applying the
175 // current search filter. Updates the count + resets the view if the
176 // shown tool was just removed.
177 function fcRenderList() {
178 if (!$fcList.length) return;
179 var q = $.trim(($fcSearch.val() || '')).toLowerCase();
180 $fcList.empty();
181 var total = 0;
182 $fcStore.find('.mxch-fc-tool').each(function () {
183 var $t = $(this);
184 if (!fcIsOn($t)) return;
185 total++;
186 var cb = $t.data('fc-callback');
187 var label = $t.data('fc-label') || cb;
188 var desc = $t.data('fc-desc') || '';
189 var icon = $t.data('fc-icon') || 'admin-generic';
190 var isAddon = String($t.data('fc-addon')) === '1';
191 var isCautious = String($t.data('fc-cautious')) === '1';
192 if (q && String(label).toLowerCase().indexOf(q) === -1 && String(desc).toLowerCase().indexOf(q) === -1) return;
193 var typeBits = [];
194 if (isAddon) typeBits.push(fcI18n.addon);
195 if (isCautious) typeBits.push(fcI18n.sensitive);
196 var typeLabel = typeBits.length ? typeBits.join(' · ') : fcI18n.active;
197 var activeCls = (String(cb) === String(fcViewCb)) ? ' active' : '';
198 var isSel = fcSelected.has(String(cb));
199 var selCls = isSel ? ' selected' : '';
200 $fcList.append(
201 '<div class="mxch-fc-list-item' + activeCls + selCls + '" data-fc-callback="' + fcEsc(cb) + '">' +
202 '<input type="checkbox" class="mxch-fc-checkbox"' + (isSel ? ' checked' : '') + '>' +
203 '<div class="mxch-action-icon"><span class="dashicons dashicons-' + fcEsc(icon) + '"></span></div>' +
204 '<div class="mxch-action-info">' +
205 '<div class="mxch-action-name">' + fcEsc(label) + '</div>' +
206 '<div class="mxch-action-type-label">' + fcEsc(typeLabel) + '</div>' +
207 '</div>' +
208 '<div class="mxch-action-meta">' +
209 '<span class="mxch-action-status enabled">' + fcEsc(fcI18n.active) + '</span>' +
210 '</div>' +
211 '</div>'
212 );
213 });
214 if ($fcCount.length) {
215 $fcCount.text(total + ' ' + (total === 1 ? fcI18n.tool : fcI18n.tools));
216 }
217 // Drop any selected callbacks that are no longer in the list (e.g.
218 // filtered out or removed) so the bulk count stays truthful.
219 var fcPresent = {};
220 $fcList.find('.mxch-fc-list-item').each(function () {
221 fcPresent[String($(this).data('fc-callback'))] = true;
222 });
223 fcSelected.forEach(function (selCb) {
224 if (!fcPresent[String(selCb)]) { fcSelected.delete(selCb); }
225 });
226 fcUpdateSelectionUI();
227 // If the tool currently in the detail panel is no longer active, reset.
228 if (fcViewCb && !fcIsOn(fcEntry(fcViewCb))) { fcShowEmpty(); }
229 }
230
231 // Populate + show the detail view for one tool.
232 function fcSelectTool(cb) {
233 var $t = fcEntry(cb);
234 if (!$t.length || !fcIsOn($t)) { fcShowEmpty(); return; }
235 fcViewCb = String(cb);
236 var label = $t.data('fc-label') || cb;
237 var desc = $t.data('fc-desc') || '';
238 var icon = $t.data('fc-icon') || 'admin-generic';
239 var isAddon = String($t.data('fc-addon')) === '1';
240 var isCautious = String($t.data('fc-cautious')) === '1';
241 var hint = $.trim($t.find('.mxch-fc-hint-input').val() || '');
242 var typeBits = [];
243 if (isAddon) typeBits.push(fcI18n.addon);
244 if (isCautious) typeBits.push(fcI18n.sensitive);
245 $('#mxch-fc-view-icon').html('<span class="dashicons dashicons-' + fcEsc(icon) + '"></span>');
246 $('#mxch-fc-view-label').text(label);
247 $('#mxch-fc-view-type').text(typeBits.join(' · '));
248 $('#mxch-fc-view-desc').text(desc);
249 var $hint = $('#mxch-fc-view-hint');
250 if (hint) { $hint.text(hint).removeClass('is-muted'); }
251 else { $hint.text(fcI18n.noHint).addClass('is-muted'); }
252 // Setup requirement (plan 183856): show the tool's setup note (e.g. the
253 // Brave-key requirement on Web/Image Search) when present, else hide it.
254 var setup = $.trim(String($t.data('fc-setup') || ''));
255 var $setupWrap = $('#mxch-fc-view-setup-wrap');
256 if (setup) { $('#mxch-fc-view-setup').text(setup); $setupWrap.show(); }
257 else { $setupWrap.hide(); }
258 $fcEmpty.hide();
259 $fcView.show();
260 $fcList.find('.mxch-fc-list-item').removeClass('active')
261 .filter(function () { return String($(this).data('fc-callback')) === String(cb); }).addClass('active');
262 if (fcIsMobile()) { $fcDetail.addClass('mobile-active'); $('body').addClass('mxch-mobile-panel-open'); }
263 }
264
265 // ---- Add/Edit modal (two-step capability picker) ----
266 function fcShowStep(n) {
267 $fcModal.find('.mxch-fc-modal-step').hide();
268 $fcModal.find('.mxch-fc-modal-step[data-step="' + n + '"]').show();
269 }
270 function fcCloseModal() { $fcModal.hide(); fcCurrentCb = null; fcGuardedClose = null; }
271
272 // The modal holds an editable "when to use" hint, so it never closes on the
273 // backdrop and confirms before discarding. fcArmGuard must run AFTER a step
274 // populates its fields: mxchatGuardedClose snapshots at call time, so arming
275 // it earlier would measure "dirty" against empty and confirm on every close.
276 function fcArmGuard() {
277 if (typeof mxchatGuardedClose !== 'function' || !$fcModal.length) return;
278 fcGuardedClose = mxchatGuardedClose($fcModal[0], fcCloseModal);
279 }
280 function fcRequestClose(e) { (fcGuardedClose || fcCloseModal)(e); }
281
282 // Step 1: grid of tools not yet added.
283 function fcOpenAddModal() {
284 // Back-to-step-1 keeps the step-2 baseline: re-arming here would treat a
285 // hint typed before Back as clean and discard it silently on close.
286 var fcWasOpen = $fcModal.is(':visible');
287 var $grid = $('#mxch-fc-modal-grid').empty();
288 var addable = 0;
289 $fcStore.find('.mxch-fc-tool').each(function () {
290 var $t = $(this);
291 if (fcIsOn($t)) return; // already active
292 addable++;
293 var cb = $t.data('fc-callback');
294 var label = $t.data('fc-label') || cb;
295 var desc = $t.data('fc-desc') || '';
296 var icon = $t.data('fc-icon') || 'admin-generic';
297 var isAddon = String($t.data('fc-addon')) === '1';
298 var isCautious = String($t.data('fc-cautious')) === '1';
299 var badges = '';
300 if (isAddon) { badges += ' <span class="mxch-fc-badge mxch-fc-badge-addon">' + fcEsc(fcI18n.addon) + '</span>'; }
301 if (isCautious) { badges += ' <span class="mxch-fc-badge mxch-fc-badge-sensitive">' + fcEsc(fcI18n.sensitive) + '</span>'; }
302 $grid.append(
303 '<div class="mxch-type-card mxch-fc-type-card" data-fc-callback="' + fcEsc(cb) + '">' +
304 '<div class="mxch-type-icon"><span class="dashicons dashicons-' + fcEsc(icon) + '"></span></div>' +
305 '<div class="mxch-type-info">' +
306 '<h4>' + fcEsc(label) + badges + '</h4>' +
307 '<p>' + fcEsc(desc) + '</p>' +
308 '</div>' +
309 '</div>'
310 );
311 });
312 $('#mxch-fc-modal-allset').toggle(addable === 0);
313 $grid.toggle(addable > 0);
314 fcShowStep(1);
315 $fcModal.css('display', 'flex');
316 if (!fcWasOpen) fcArmGuard();
317 }
318
319 // Step 2: confirm + when-to-use (shared by Add and Edit).
320 function fcOpenConfigStep(cb) {
321 var $t = fcEntry(cb);
322 if (!$t.length) return;
323 fcCurrentCb = cb;
324 var editing = fcIsOn($t);
325 $('#mxch-fc-modal-selected-icon').html('<span class="dashicons dashicons-' + fcEsc($t.data('fc-icon') || 'admin-generic') + '"></span>');
326 $('#mxch-fc-modal-selected-label').text($t.data('fc-label') || cb);
327 $('#mxch-fc-modal-selected-desc').text($t.data('fc-desc') || '');
328 $('#mxch-fc-modal-hint').val($t.find('.mxch-fc-hint-input').val() || '');
329 $('#mxch-fc-modal-sensitive').toggle(String($t.data('fc-cautious')) === '1');
330 $fcStep2Title.text(editing ? FC_TITLE_EDIT : FC_TITLE_ADD);
331 $fcSaveBtn.text(editing ? FC_BTN_SAVE : FC_BTN_ADD);
332 fcShowStep(2);
333 $fcModal.css('display', 'flex');
334 fcArmGuard(); // after the hint is loaded: that value is the clean baseline
335 }
336
337 // ---- wiring ----
338 // "Add" buttons (panel header + empty state) — delegated on $fc.
339 $fc.on('click', '.js-mxch-fc-add', fcOpenAddModal);
340 $fcModal.on('click', '.mxch-fc-type-card', function () { fcOpenConfigStep($(this).data('fc-callback')); });
341 $('#mxch-fc-modal-back').on('click', fcOpenAddModal);
342 $fcModal.on('click', '[data-fc-modal-close]', fcRequestClose); // X + Cancel
343 // No backdrop close: a click dispatches on the common ancestor of its mousedown
344 // and mouseup, so releasing a selection past the dialog edge targets the backdrop
345 // and would discard the hint — and a plain backdrop click discarded it outright.
346 $(document).on('keydown', function (e) { if (e.key === 'Escape' && $fcModal.is(':visible')) fcRequestClose(e); });
347
348 // Modal Save (Add or Edit): write the store inputs, autosave, refresh
349 // the list, and show the tool's detail on the right.
350 $fcSaveBtn.on('click', function () {
351 if (!fcCurrentCb) return;
352 var $t = fcEntry(fcCurrentCb);
353 if (!$t.length) return;
354 var cb = fcCurrentCb;
355 $t.find('.mxch-fc-hint-input').val($('#mxch-fc-modal-hint').val() || '');
356 $t.find('input[name="mxchat_fc_tools[]"]').prop('checked', true);
357 fcCloseModal();
358 fcRenderList();
359 fcSelectTool(cb);
360 fcCollectAndSave();
361 });
362
363 // List item click → show that tool's detail.
364 $fcList.on('click', '.mxch-fc-list-item', function () {
365 fcSelectTool($(this).data('fc-callback'));
366 });
367
368 // Detail Edit → reopen the config step for the viewed tool.
369 $('#mxch-fc-edit-btn').on('click', function () {
370 if (fcViewCb) fcOpenConfigStep(fcViewCb);
371 });
372 // Detail Remove → disable the tool (keep its hint for a later re-add),
373 // refresh the list, and return to the empty state.
374 $('#mxch-fc-remove-btn').on('click', function () {
375 if (!fcViewCb) return;
376 var $t = fcEntry(fcViewCb);
377 if (!$t.length) return;
378 $t.find('input[name="mxchat_fc_tools[]"]').prop('checked', false);
379 fcShowEmpty();
380 fcRenderList();
381 fcCollectAndSave();
382 });
383
384 // ---- Bulk selection + bulk remove ----
385 // Clones the Trigger Phrases #all-actions bulk toolbar behavior, but
386 // wires removal to the SAME path the single Remove button uses (uncheck
387 // each tool's store checkbox, then one autosave). FC-specific IDs keep
388 // this isolated from the Trigger Phrases bulk JS (plan 5f7409).
389 var $fcSelectAll = $('#mxch-fc-select-all');
390 var $fcBulkDelete = $('#mxch-fc-delete-selected');
391 var $fcSelCount = $('#mxch-fc-selected-count');
392
393 function fcUpdateSelectionUI() {
394 var count = fcSelected.size;
395 if (count > 0) {
396 $fcSelCount.text(count + ' selected').addClass('has-selection');
397 $fcBulkDelete.prop('disabled', false);
398 $fcList.addClass('selection-mode');
399 } else {
400 $fcSelCount.removeClass('has-selection');
401 $fcBulkDelete.prop('disabled', true);
402 $fcList.removeClass('selection-mode');
403 }
404 var totalItems = $fcList.find('.mxch-fc-checkbox').length;
405 var checkedItems = $fcList.find('.mxch-fc-checkbox:checked').length;
406 $fcSelectAll.prop('checked', totalItems > 0 && checkedItems === totalItems);
407 $fcSelectAll.prop('indeterminate', checkedItems > 0 && checkedItems < totalItems);
408 }
409
410 // Select-all toggles every visible tool row.
411 $fcSelectAll.on('change', function () {
412 var on = $(this).is(':checked');
413 fcSelected.clear();
414 $fcList.find('.mxch-fc-list-item').each(function () {
415 var $row = $(this);
416 var cb = String($row.data('fc-callback'));
417 $row.find('.mxch-fc-checkbox').prop('checked', on);
418 if (on) { fcSelected.add(cb); $row.addClass('selected'); }
419 else { $row.removeClass('selected'); }
420 });
421 fcUpdateSelectionUI();
422 });
423
424 // Per-row checkbox: toggle selection without opening the detail view.
425 $fcList.on('click', '.mxch-fc-checkbox', function (e) {
426 e.stopPropagation();
427 var $row = $(this).closest('.mxch-fc-list-item');
428 var cb = String($row.data('fc-callback'));
429 if ($(this).is(':checked')) { fcSelected.add(cb); $row.addClass('selected'); }
430 else { fcSelected.delete(cb); $row.removeClass('selected'); }
431 fcUpdateSelectionUI();
432 });
433
434 // Bulk remove: same removal path as single Remove — uncheck each tool's
435 // store checkbox (presence = active), then refresh + autosave once.
436 $fcBulkDelete.on('click', function () {
437 if (!fcSelected.size) return;
438 var msg = (window.mxchActionsData && mxchActionsData.i18n && mxchActionsData.i18n.confirmBulkDelete)
439 || 'Remove the selected tools? They stay available to add back later.';
440 if (!window.confirm(msg)) return;
441 var removingViewed = false;
442 fcSelected.forEach(function (cb) {
443 var $t = fcEntry(cb);
444 if ($t.length) { $t.find('input[name="mxchat_fc_tools[]"]').prop('checked', false); }
445 if (String(cb) === String(fcViewCb)) { removingViewed = true; }
446 });
447 fcSelected.clear();
448 if (removingViewed) { fcShowEmpty(); }
449 fcRenderList();
450 fcCollectAndSave();
451 });
452
453 // Search filter.
454 $fcSearch.on('input', function () { fcRenderList(); });
455
456 // Refresh — re-render the list from the store (parity with the Trigger
457 // Phrases refresh affordance; the list is client-built so this re-syncs
458 // the view rather than re-fetching).
459 $('#mxch-fc-refresh').on('click', function () {
460 var $btn = $(this);
461 $btn.addClass('spinning');
462 fcRenderList();
463 setTimeout(function () { $btn.removeClass('spinning'); }, 500);
464 });
465
466 fcRenderList();
467 }
468 }
469
470 // Quick action buttons
471 $('.mxch-quick-action-btn[data-action="view-actions"]').on('click', function() {
472 $('.mxch-nav-link[data-target="all-actions"]').trigger('click');
473 });
474
475 // Dashboard explainer cards — generic "jump to this section" (e.g. Set up AI Tools).
476 $('.mxch-approach-btn[data-approach-target]').on('click', function() {
477 var target = $(this).data('approach-target');
478 $('.mxch-nav-link[data-target="' + target + '"]').trigger('click');
479 });
480
481 // "Add Trigger Phrase" on the dashboard → jump to Trigger Phrases + open the editor.
482 $('#mxch-add-action-dashboard-btn').on('click', function() {
483 $('.mxch-nav-link[data-target="all-actions"]').trigger('click');
484 setTimeout(function() {
485 openActionEditor();
486 }, 100);
487 });
488
489 // ==========================================================================
490 // Mobile Detection & Panel Management
491 // ==========================================================================
492
493 function isMobile() {
494 return window.innerWidth <= 782;
495 }
496
497 function showMobileDetailPanel() {
498 if (isMobile()) {
499 $('.mxch-action-detail-panel').addClass('mobile-active');
500 $('body').addClass('mxch-mobile-panel-open');
501 }
502 }
503
504 function hideMobileDetailPanel() {
505 $('.mxch-action-detail-panel').removeClass('mobile-active');
506 $('body').removeClass('mxch-mobile-panel-open');
507 }
508
509 // Mobile back button handler
510 $(document).on('click', '.mxch-mobile-back-btn', function(e) {
511 e.preventDefault();
512 hideMobileDetailPanel();
513 resetEditorPanel();
514 $('.mxch-action-item').removeClass('active');
515 currentActionId = null;
516 });
517
518 // ==========================================================================
519 // Action List - Split Panel
520 // ==========================================================================
521
522 let currentPage = 1;
523 const perPage = 50;
524 let totalPages = 1;
525 let currentActionId = null;
526 let actionsLoaded = false;
527 let selectedActions = new Set();
528 let currentSortOrder = 'desc';
529 let currentFilter = '';
530
531 // ==========================================================================
532 // Phrase Tag Management
533 // ==========================================================================
534
535 let currentPhrases = []; // Array of {id, text, isLegacy}
536
537 function renderPhraseTags() {
538 const $container = $('#mxch-phrase-tags');
539 let html = '';
540 currentPhrases.forEach(function(phrase, index) {
541 const legacyClass = phrase.isLegacy ? ' mxch-phrase-legacy' : '';
542 const badge = phrase.isLegacy ? ' <span class="mxch-legacy-badge">legacy</span>' : '';
543 html += '<span class="mxch-phrase-pill' + legacyClass + '" data-index="' + index + '" data-phrase-id="' + (phrase.id || '') + '">'
544 + escapeHtml(phrase.text) + badge
545 + ' <button type="button" class="mxch-phrase-remove" title="Remove">&times;</button>'
546 + '</span>';
547 });
548 $container.html(html);
549 }
550
551 function addPhraseFromInput() {
552 const $input = $('#mxch-phrase-input');
553 const text = $input.val().trim();
554 if (!text) return;
555
556 const formMode = $('#mxch-form-mode').val();
557 const intentId = $('#mxch-action-id').val();
558
559 if (formMode === 'edit' && intentId) {
560 // Edit mode: AJAX add immediately
561 const $btn = $('#mxch-add-phrase-btn');
562 $btn.prop('disabled', true).text('Adding...');
563
564 $.ajax({
565 url: mxchActionsData.ajaxUrl,
566 type: 'POST',
567 data: {
568 action: 'mxchat_add_phrase',
569 intent_id: intentId,
570 phrase: text,
571 security: mxchActionsData.addPhraseNonce
572 },
573 success: function(response) {
574 $btn.prop('disabled', false).text('Add');
575 if (response.success) {
576 currentPhrases.push({id: response.data.id, text: text, isLegacy: false});
577 renderPhraseTags();
578 $input.val('').focus();
579 } else {
580 alert(response.data || 'Failed to add phrase.');
581 }
582 },
583 error: function() {
584 $btn.prop('disabled', false).text('Add');
585 alert('Failed to add phrase. Please try again.');
586 }
587 });
588 } else {
589 // Add mode: just push locally
590 currentPhrases.push({id: null, text: text, isLegacy: false});
591 renderPhraseTags();
592 $input.val('').focus();
593 }
594 }
595
596 // Add phrase on Enter key
597 $(document).on('keydown', '#mxch-phrase-input', function(e) {
598 if (e.key === 'Enter') {
599 e.preventDefault();
600 addPhraseFromInput();
601 }
602 });
603
604 // Add phrase on button click
605 $(document).on('click', '#mxch-add-phrase-btn', function() {
606 addPhraseFromInput();
607 });
608
609 // Remove phrase on X click
610 $(document).on('click', '.mxch-phrase-remove', function(e) {
611 e.stopPropagation();
612 const $pill = $(this).closest('.mxch-phrase-pill');
613 const index = parseInt($pill.data('index'));
614 const phrase = currentPhrases[index];
615
616 if (!phrase) return;
617
618 if (phrase.isLegacy) {
619 // Delete legacy phrases from main table
620 const intentId = $('#mxch-action-id').val();
621 if (!intentId) {
622 currentPhrases.splice(index, 1);
623 renderPhraseTags();
624 return;
625 }
626
627 if (!confirm('Remove all legacy grouped phrases? You can re-add them individually for better matching.')) return;
628
629 $.ajax({
630 url: mxchActionsData.ajaxUrl,
631 type: 'POST',
632 data: {
633 action: 'mxchat_delete_legacy_phrases',
634 intent_id: intentId,
635 security: mxchActionsData.deleteLegacyNonce
636 },
637 success: function(response) {
638 if (response.success) {
639 currentPhrases.splice(index, 1);
640 renderPhraseTags();
641 } else {
642 alert(response.data || 'Failed to remove legacy phrases.');
643 }
644 }
645 });
646 } else if (phrase.id) {
647 // Delete individual phrase via AJAX
648 $.ajax({
649 url: mxchActionsData.ajaxUrl,
650 type: 'POST',
651 data: {
652 action: 'mxchat_delete_phrase',
653 phrase_id: phrase.id,
654 security: mxchActionsData.deletePhraseNonce
655 },
656 success: function(response) {
657 if (response.success) {
658 currentPhrases.splice(index, 1);
659 renderPhraseTags();
660 } else {
661 alert(response.data || 'Failed to delete phrase.');
662 }
663 }
664 });
665 } else {
666 // Local-only phrase (add mode, not yet saved)
667 currentPhrases.splice(index, 1);
668 renderPhraseTags();
669 }
670 });
671
672 function fetchIndividualPhrases(intentId, callback) {
673 $.ajax({
674 url: mxchActionsData.ajaxUrl,
675 type: 'POST',
676 data: {
677 action: 'mxchat_get_phrases',
678 intent_id: intentId,
679 security: mxchActionsData.getPhrasesNonce
680 },
681 success: function(response) {
682 if (response.success) {
683 callback(response.data.phrases || []);
684 } else {
685 callback([]);
686 }
687 },
688 error: function() {
689 callback([]);
690 }
691 });
692 }
693
694 // Load action list on page load
695 loadActionList(1, '', '');
696
697 // Search functionality with debounce
698 let searchTimeout;
699 $('#mxch-search-actions').on('input', function() {
700 clearTimeout(searchTimeout);
701 const searchTerm = $(this).val().toLowerCase();
702
703 searchTimeout = setTimeout(function() {
704 currentPage = 1;
705 loadActionList(currentPage, searchTerm, currentFilter);
706 }, 300);
707 });
708
709 // Filter by type
710 $('#mxch-filter-actions').on('change', function() {
711 currentFilter = $(this).val();
712 currentPage = 1;
713 loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter);
714 });
715
716 // Refresh button
717 $('#mxch-refresh-actions').on('click', function() {
718 const $btn = $(this);
719 $btn.addClass('spinning');
720 loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter);
721 setTimeout(() => $btn.removeClass('spinning'), 500);
722 });
723
724 // ==========================================================================
725 // Bulk Selection & Actions
726 // ==========================================================================
727
728 // Select all checkbox
729 $('#mxch-select-all-actions').on('change', function() {
730 const isChecked = $(this).is(':checked');
731 $('.mxch-action-checkbox').prop('checked', isChecked);
732
733 if (isChecked) {
734 $('.mxch-action-item').each(function() {
735 selectedActions.add($(this).data('action-id'));
736 $(this).addClass('selected');
737 });
738 $('#mxch-action-list').addClass('selection-mode');
739 } else {
740 selectedActions.clear();
741 $('.mxch-action-item').removeClass('selected');
742 $('#mxch-action-list').removeClass('selection-mode');
743 }
744
745 updateSelectionUI();
746 });
747
748 // Update selection UI
749 function updateSelectionUI() {
750 const count = selectedActions.size;
751 const $countEl = $('#mxch-selected-action-count');
752 const $deleteBtn = $('#mxch-delete-selected-actions');
753
754 if (count > 0) {
755 $countEl.text(count + ' selected').addClass('has-selection');
756 $deleteBtn.prop('disabled', false);
757 $('#mxch-action-list').addClass('selection-mode');
758 } else {
759 $countEl.removeClass('has-selection');
760 $deleteBtn.prop('disabled', true);
761 $('#mxch-action-list').removeClass('selection-mode');
762 }
763
764 // Update select all checkbox state
765 const totalItems = $('.mxch-action-checkbox').length;
766 const checkedItems = $('.mxch-action-checkbox:checked').length;
767 $('#mxch-select-all-actions').prop('checked', totalItems > 0 && checkedItems === totalItems);
768 $('#mxch-select-all-actions').prop('indeterminate', checkedItems > 0 && checkedItems < totalItems);
769 }
770
771 // Delete selected button
772 $('#mxch-delete-selected-actions').on('click', function() {
773 const count = selectedActions.size;
774 if (count === 0) return;
775
776 if (!confirm(mxchActionsData.i18n.confirmBulkDelete)) {
777 return;
778 }
779
780 deleteMultipleActions(Array.from(selectedActions));
781 });
782
783 // Delete multiple actions
784 function deleteMultipleActions(actionIds) {
785 showLoading();
786
787 $.ajax({
788 url: mxchActionsData.ajaxUrl,
789 type: 'POST',
790 data: {
791 action: 'mxchat_bulk_delete_actions',
792 action_ids: actionIds,
793 security: mxchActionsData.deleteNonce
794 },
795 success: function(response) {
796 hideLoading();
797
798 if (response.success) {
799 // Clear selection
800 selectedActions.clear();
801 $('#mxch-select-all-actions').prop('checked', false);
802 updateSelectionUI();
803
804 // If current action was deleted, reset panel
805 if (actionIds.includes(currentActionId)) {
806 currentActionId = null;
807 resetEditorPanel();
808 }
809
810 // Reload list
811 loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter);
812 } else {
813 alert(response.data || mxchActionsData.i18n.error);
814 }
815 },
816 error: function() {
817 hideLoading();
818 alert(mxchActionsData.i18n.error);
819 }
820 });
821 }
822
823 // Load action list function
824 function loadActionList(page, searchTerm, filter) {
825 const $container = $('#mxch-action-list');
826 $container.html('<div class="mxch-list-loading"><span class="spinner is-active"></span></div>');
827
828 $.ajax({
829 url: mxchActionsData.ajaxUrl,
830 type: 'POST',
831 data: {
832 action: 'mxchat_fetch_actions_list',
833 page: page,
834 per_page: perPage,
835 search: searchTerm,
836 callback_filter: filter,
837 sort_order: currentSortOrder,
838 security: mxchActionsData.nonce
839 },
840 success: function(response) {
841 actionsLoaded = true;
842
843 if (response.success && response.data.actions && response.data.actions.length > 0) {
844 renderActionList(response.data.actions);
845 currentPage = response.data.page;
846 totalPages = response.data.total_pages;
847 updateActionCount(response.data.showing_start, response.data.showing_end, response.data.total_actions);
848 renderPagination(response.data.page, response.data.total_pages, searchTerm, filter);
849 } else {
850 $container.html('<div class="mxch-list-empty"><p>No trigger phrases found</p></div>');
851 updateActionCount(0, 0, 0);
852 $('#mxch-actions-pagination').html('');
853 }
854 },
855 error: function() {
856 $container.html('<div class="mxch-list-empty"><p>Error loading trigger phrases</p></div>');
857 }
858 });
859 }
860
861 // Render action list items
862 function renderActionList(actions) {
863 const $container = $('#mxch-action-list');
864 let html = '';
865
866 actions.forEach(function(action) {
867 const isActive = action.id == currentActionId ? ' active' : '';
868 const isSelected = selectedActions.has(action.id) ? ' selected' : '';
869 const isChecked = selectedActions.has(action.id) ? ' checked' : '';
870 const isDisabled = !action.enabled ? ' disabled' : '';
871 const statusClass = action.enabled ? 'enabled' : 'disabled';
872 const statusText = action.enabled ? 'Enabled' : 'Disabled';
873
874 html += `
875 <div class="mxch-action-item${isActive}${isSelected}${isDisabled}"
876 data-action-id="${action.id}"
877 data-label="${escapeHtml(action.label)}"
878 data-phrases="${escapeHtml(action.phrases)}"
879 data-threshold="${action.threshold}"
880 data-callback="${escapeHtml(action.callback_function)}"
881 data-enabled="${action.enabled ? '1' : '0'}"
882 data-enabled-bots='${escapeHtml(JSON.stringify(action.enabled_bots))}'
883 data-has-legacy="${action.has_legacy_vector ? '1' : '0'}"
884 data-individual-count="${action.individual_phrase_count || 0}">
885 <input type="checkbox" class="mxch-action-checkbox"${isChecked}>
886 <div class="mxch-action-icon">
887 <span class="dashicons dashicons-${escapeHtml(action.icon || 'admin-generic')}"></span>
888 </div>
889 <div class="mxch-action-info">
890 <div class="mxch-action-name">${escapeHtml(action.label)}</div>
891 <div class="mxch-action-type-label">${escapeHtml(action.callback_label)}</div>
892 </div>
893 <div class="mxch-action-meta">
894 <span class="mxch-action-status ${statusClass}">${statusText}</span>
895 </div>
896 </div>
897 `;
898 });
899
900 $container.html(html);
901
902 // Attach checkbox handlers
903 $('.mxch-action-checkbox').on('click', function(e) {
904 e.stopPropagation();
905 const $item = $(this).closest('.mxch-action-item');
906 const actionId = $item.data('action-id');
907
908 if ($(this).is(':checked')) {
909 selectedActions.add(actionId);
910 $item.addClass('selected');
911 } else {
912 selectedActions.delete(actionId);
913 $item.removeClass('selected');
914 }
915
916 updateSelectionUI();
917 });
918
919 // Attach click handlers for selecting action
920 $('.mxch-action-item').on('click', function(e) {
921 if ($(e.target).is('.mxch-action-checkbox')) return;
922
923 const actionId = $(this).data('action-id');
924 selectAction(actionId, $(this));
925
926 // Update active state
927 $('.mxch-action-item').removeClass('active');
928 $(this).addClass('active');
929 });
930
931 // Update selection UI after render
932 updateSelectionUI();
933 }
934
935 // Update action count display
936 function updateActionCount(start, end, total) {
937 if (total === 0) {
938 $('#mxch-action-count').text('0 trigger phrases');
939 } else {
940 $('#mxch-action-count').text(`${start}-${end} / ${total} trigger phrases`);
941 }
942 }
943
944 // Render pagination
945 function renderPagination(currentPage, totalPages, searchTerm, filter) {
946 const $container = $('#mxch-actions-pagination');
947
948 if (totalPages <= 1) {
949 $container.html('');
950 return;
951 }
952
953 let html = '<div class="mxch-pagination-btns">';
954
955 if (currentPage > 1) {
956 html += `<button class="mxch-page-btn" data-page="${currentPage - 1}">&laquo;</button>`;
957 }
958
959 html += `<span class="mxch-page-info">${currentPage} / ${totalPages}</span>`;
960
961 if (currentPage < totalPages) {
962 html += `<button class="mxch-page-btn" data-page="${currentPage + 1}">&raquo;</button>`;
963 }
964
965 html += '</div>';
966 $container.html(html);
967
968 // Pagination click handlers
969 $('.mxch-page-btn').on('click', function() {
970 const pageNum = $(this).data('page');
971 loadActionList(pageNum, searchTerm, filter);
972 });
973 }
974
975 // ==========================================================================
976 // Action Editor Panel
977 // ==========================================================================
978
979 // Select and view an action
980 function selectAction(actionId, $item) {
981 currentActionId = actionId;
982
983 // Get data from item attributes
984 const data = {
985 id: actionId,
986 label: $item.data('label'),
987 phrases: $item.data('phrases'),
988 threshold: $item.data('threshold'),
989 callback_function: $item.data('callback'),
990 enabled: $item.data('enabled') == '1',
991 enabled_bots: $item.data('enabled-bots') || ['default'],
992 has_legacy: $item.data('has-legacy') == '1',
993 individualPhrases: []
994 };
995
996 // Fetch individual phrases then show view
997 fetchIndividualPhrases(actionId, function(phrases) {
998 data.individualPhrases = phrases;
999 showActionView(data);
1000 });
1001 }
1002
1003 // Show action view (details)
1004 function showActionView(action) {
1005 $('#mxch-action-empty').hide();
1006 $('#mxch-action-editor').show();
1007
1008 // Hide editor steps, show view
1009 $('.mxch-editor-step').removeClass('active');
1010 $('#mxch-action-view').addClass('active');
1011
1012 // Show detail panel on mobile
1013 showMobileDetailPanel();
1014
1015 // Populate view
1016 $('#mxch-view-title').text(action.label);
1017 $('#mxch-view-label').text(action.label);
1018
1019 // Get callback label from filter dropdown
1020 const callbackLabel = $('#mxch-filter-actions option[value="' + action.callback_function + '"]').text() || action.callback_function;
1021 $('#mxch-view-type').text(callbackLabel);
1022
1023 // Get icon from type grid
1024 const $typeCard = $('.mxch-type-card[data-value="' + action.callback_function + '"]');
1025 const iconClass = $typeCard.find('.dashicons').attr('class') || 'dashicons dashicons-admin-generic';
1026 $('#mxch-view-icon').html('<span class="' + iconClass + '"></span>');
1027 $('#mxch-detail-icon').html('<span class="' + iconClass + '"></span>');
1028
1029 // Phrases - show legacy grouped phrases and individual phrases
1030 let phrasesHtml = '';
1031 const legacyPhrases = action.phrases ? String(action.phrases).trim() : '';
1032 if (legacyPhrases) {
1033 phrasesHtml += '<span class="mxch-phrase-tag mxch-phrase-legacy">' + escapeHtml(legacyPhrases) + ' <span class="mxch-legacy-badge">legacy</span></span>';
1034 }
1035 if (action.individualPhrases && action.individualPhrases.length) {
1036 action.individualPhrases.forEach(function(p) {
1037 phrasesHtml += '<span class="mxch-phrase-tag">' + escapeHtml(p.phrase) + '</span>';
1038 });
1039 }
1040 if (!phrasesHtml) {
1041 phrasesHtml = '<span class="mxch-no-phrases">No trigger phrases configured</span>';
1042 }
1043 $('#mxch-view-phrases').html(phrasesHtml);
1044
1045 // Settings
1046 $('#mxch-view-threshold').text(action.threshold + '%');
1047
1048 const statusText = action.enabled ? 'Enabled' : 'Disabled';
1049 const statusClass = action.enabled ? 'mxch-status-enabled' : 'mxch-status-disabled';
1050 $('#mxch-view-status').text(statusText).removeClass('mxch-status-enabled mxch-status-disabled').addClass(statusClass);
1051
1052 // Toggle switch
1053 $('#mxch-action-enabled-toggle').prop('checked', action.enabled);
1054
1055 // Bots
1056 let botsHtml = '';
1057 const enabledBots = Array.isArray(action.enabled_bots) ? action.enabled_bots : ['default'];
1058 enabledBots.forEach(function(bot) {
1059 const botName = bot === 'default' ? 'Default Bot' : bot;
1060 botsHtml += '<span class="mxch-bot-tag">' + escapeHtml(botName) + '</span>';
1061 });
1062 $('#mxch-view-bots').html(botsHtml);
1063
1064 // Store current action data for editing
1065 $('#mxch-action-view').data('current-action', action);
1066 }
1067
1068 // Open action editor (for creating new)
1069 function openActionEditor() {
1070 currentActionId = null;
1071
1072 $('#mxch-action-empty').hide();
1073 $('#mxch-action-editor').show();
1074
1075 // Show step 1
1076 $('.mxch-editor-step').removeClass('active');
1077 $('#mxch-editor-step-1').addClass('active');
1078
1079 // Reset form
1080 resetForm();
1081
1082 // Show detail panel on mobile
1083 showMobileDetailPanel();
1084 }
1085
1086 // Open action editor for editing
1087 function openActionEditorForEdit(action) {
1088 currentActionId = action.id;
1089
1090 $('#mxch-action-empty').hide();
1091 $('#mxch-action-editor').show();
1092
1093 // Go directly to step 2
1094 $('.mxch-editor-step').removeClass('active');
1095 $('#mxch-editor-step-2').addClass('active');
1096
1097 // Populate form
1098 $('#mxch-action-id').val(action.id);
1099 $('#mxch-callback-function').val(action.callback_function);
1100 $('#mxch-form-mode').val('edit');
1101 $('#mxch-action-label').val(action.label);
1102 $('#mxch-action-threshold').val(action.threshold);
1103 $('#mxch-threshold-value').text(action.threshold + '%');
1104
1105 // Populate phrase tags
1106 currentPhrases = [];
1107 const legacyPhrases = action.phrases ? String(action.phrases).trim() : '';
1108 if (legacyPhrases) {
1109 currentPhrases.push({id: 'legacy', text: legacyPhrases, isLegacy: true});
1110 }
1111 if (action.individualPhrases && action.individualPhrases.length) {
1112 action.individualPhrases.forEach(function(p) {
1113 currentPhrases.push({id: p.id, text: p.phrase, isLegacy: false});
1114 });
1115 }
1116 renderPhraseTags();
1117
1118 // Set selected type display
1119 const $typeCard = $('.mxch-type-card[data-value="' + action.callback_function + '"]');
1120 const iconHtml = $typeCard.find('.mxch-type-icon').html();
1121 const typeLabel = $typeCard.data('label');
1122 const typeDesc = $typeCard.find('p').text();
1123
1124 $('#mxch-selected-type-icon').html(iconHtml);
1125 $('#mxch-selected-type-label').text(typeLabel);
1126 $('#mxch-selected-type-desc').text(typeDesc);
1127 $('#mxch-config-title').text('Edit Trigger Phrase');
1128
1129 // Set enabled bots
1130 const enabledBots = Array.isArray(action.enabled_bots) ? action.enabled_bots : ['default'];
1131 $('#mxch-bot-selector input[type="checkbox"]').each(function() {
1132 $(this).prop('checked', enabledBots.includes($(this).val()));
1133 });
1134
1135 // Update save button text
1136 $('#mxch-save-action').text('Update Trigger Phrase');
1137 }
1138
1139 // Reset form
1140 function resetForm() {
1141 $('#mxch-action-id').val('');
1142 $('#mxch-callback-function').val('');
1143 $('#mxch-form-mode').val('add');
1144 $('#mxch-action-label').val('');
1145 $('#mxch-action-threshold').val(85);
1146
1147 // Clear phrase tags
1148 currentPhrases = [];
1149 renderPhraseTags();
1150 $('#mxch-threshold-value').text('85%');
1151 $('#mxch-config-title').text('Configure Trigger Phrase');
1152 $('#mxch-save-action').text('Save Trigger Phrase');
1153
1154 // Reset bot selection
1155 $('#mxch-bot-selector input[type="checkbox"]').prop('checked', false);
1156 $('#mxch-bot-selector input[value="default"]').prop('checked', true);
1157
1158 // Reset type selection
1159 $('.mxch-type-card').removeClass('selected');
1160 }
1161
1162 // Reset editor panel to empty state
1163 function resetEditorPanel() {
1164 $('#mxch-action-editor').hide();
1165 $('#mxch-action-empty').show();
1166 $('.mxch-editor-step').removeClass('active');
1167
1168 // Hide mobile panel
1169 hideMobileDetailPanel();
1170 }
1171
1172 // ==========================================================================
1173 // Editor Event Handlers
1174 // ==========================================================================
1175
1176 // Add new action buttons
1177 $('#mxch-add-action-btn, #mxch-create-first-action-btn').on('click', function() {
1178 openActionEditor();
1179 });
1180
1181 // Close editor buttons
1182 $('#mxch-close-editor, #mxch-close-editor-2').on('click', function() {
1183 resetEditorPanel();
1184 $('.mxch-action-item').removeClass('active');
1185 currentActionId = null;
1186 });
1187
1188 // Cancel button
1189 $('#mxch-cancel-action').on('click', function() {
1190 if (currentActionId && $('#mxch-form-mode').val() === 'edit') {
1191 // Go back to view mode
1192 const action = $('#mxch-action-view').data('current-action');
1193 if (action) {
1194 showActionView(action);
1195 }
1196 } else {
1197 resetEditorPanel();
1198 }
1199 });
1200
1201 // Back to step 1
1202 $('#mxch-back-to-step-1').on('click', function() {
1203 if ($('#mxch-form-mode').val() === 'edit') {
1204 // If editing, go back to view
1205 const action = $('#mxch-action-view').data('current-action');
1206 if (action) {
1207 showActionView(action);
1208 }
1209 } else {
1210 // If creating, go back to step 1
1211 $('.mxch-editor-step').removeClass('active');
1212 $('#mxch-editor-step-1').addClass('active');
1213 }
1214 });
1215
1216 // Edit action button
1217 $('#mxch-edit-action-btn').on('click', function() {
1218 const action = $('#mxch-action-view').data('current-action');
1219 if (action) {
1220 openActionEditorForEdit(action);
1221 }
1222 });
1223
1224 // Toggle enabled status
1225 $('#mxch-action-enabled-toggle').on('change', function() {
1226 if (!currentActionId) return;
1227
1228 const isEnabled = $(this).is(':checked');
1229 toggleActionStatus(currentActionId, isEnabled);
1230 });
1231
1232 // Delete action button
1233 $('#mxch-delete-action-btn').on('click', function() {
1234 if (!currentActionId) return;
1235 showDeleteModal(currentActionId);
1236 });
1237
1238 // ==========================================================================
1239 // Action Type Selection (Step 1)
1240 // ==========================================================================
1241
1242 // Type search
1243 $('#mxch-type-search-input').on('input', function() {
1244 const searchTerm = $(this).val().toLowerCase();
1245 filterTypeCards(searchTerm, 'all');
1246 });
1247
1248 // Category buttons
1249 $('.mxch-category-btn').on('click', function() {
1250 $('.mxch-category-btn').removeClass('active');
1251 $(this).addClass('active');
1252
1253 const category = $(this).data('category');
1254 const searchTerm = $('#mxch-type-search-input').val().toLowerCase();
1255 filterTypeCards(searchTerm, category);
1256 });
1257
1258 // Filter type cards
1259 function filterTypeCards(searchTerm, category) {
1260 $('.mxch-type-card').each(function() {
1261 const $card = $(this);
1262 const cardCategory = $card.data('category');
1263 const cardLabel = $card.data('label').toLowerCase();
1264 const cardDesc = $card.find('p').text().toLowerCase();
1265
1266 const matchesCategory = category === 'all' || cardCategory === category;
1267 const matchesSearch = !searchTerm || cardLabel.includes(searchTerm) || cardDesc.includes(searchTerm);
1268
1269 if (matchesCategory && matchesSearch) {
1270 $card.show();
1271 } else {
1272 $card.hide();
1273 }
1274 });
1275 }
1276
1277 // Type card click
1278 $('.mxch-type-card').on('click', function() {
1279 const $card = $(this);
1280
1281 // Check if pro required
1282 if ($card.hasClass('mxch-type-pro') && !mxchActionsData.isActivated) {
1283 alert(mxchActionsData.i18n.proRequired);
1284 return;
1285 }
1286
1287 // Check if addon required
1288 if ($card.hasClass('mxch-type-addon') && $card.data('installed') !== true) {
1289 alert(mxchActionsData.i18n.addonRequired);
1290 return;
1291 }
1292
1293 // Select this card
1294 $('.mxch-type-card').removeClass('selected');
1295 $card.addClass('selected');
1296
1297 // Get card data
1298 const callback = $card.data('value');
1299 const label = $card.data('label');
1300 const iconHtml = $card.find('.mxch-type-icon').html();
1301 const desc = $card.find('p').text();
1302
1303 // Populate step 2
1304 $('#mxch-callback-function').val(callback);
1305 $('#mxch-selected-type-icon').html(iconHtml);
1306 $('#mxch-selected-type-label').text(label);
1307 $('#mxch-selected-type-desc').text(desc);
1308
1309 // Go to step 2
1310 $('.mxch-editor-step').removeClass('active');
1311 $('#mxch-editor-step-2').addClass('active');
1312 });
1313
1314 // ==========================================================================
1315 // Form Handling
1316 // ==========================================================================
1317
1318 // Threshold slider
1319 $('#mxch-action-threshold').on('input', function() {
1320 $('#mxch-threshold-value').text($(this).val() + '%');
1321 });
1322
1323 // Form submission
1324 $('#mxch-action-form').on('submit', function(e) {
1325 e.preventDefault();
1326
1327 const formMode = $('#mxch-form-mode').val();
1328 const actionId = $('#mxch-action-id').val();
1329 const callbackFunction = $('#mxch-callback-function').val();
1330 const label = $('#mxch-action-label').val().trim();
1331 const threshold = $('#mxch-action-threshold').val();
1332
1333 // Validation
1334 if (!callbackFunction) {
1335 alert('Please select an action type.');
1336 return;
1337 }
1338
1339 if (!label) {
1340 alert('Please enter an action label.');
1341 $('#mxch-action-label').focus();
1342 return;
1343 }
1344
1345 // Check phrases exist (from tag input)
1346 if (currentPhrases.length === 0) {
1347 alert('Please add at least one trigger phrase.');
1348 $('#mxch-phrase-input').focus();
1349 return;
1350 }
1351
1352 // Get enabled bots
1353 const enabledBots = [];
1354 $('#mxch-bot-selector input[type="checkbox"]:checked').each(function() {
1355 enabledBots.push($(this).val());
1356 });
1357
1358 if (enabledBots.length === 0) {
1359 enabledBots.push('default');
1360 }
1361
1362 showLoading();
1363
1364 const data = {
1365 action: formMode === 'edit' ? 'mxchat_edit_intent_ajax' : 'mxchat_add_intent_ajax',
1366 intent_label: label,
1367 similarity_threshold: threshold,
1368 callback_function: callbackFunction,
1369 enabled_bots: enabledBots,
1370 security: formMode === 'edit' ? mxchActionsData.editNonce : mxchActionsData.addNonce
1371 };
1372
1373 if (formMode === 'edit') {
1374 data.intent_id = actionId;
1375 // In edit mode, phrases are managed via individual AJAX calls already
1376 // Send empty phrases to skip legacy re-embedding
1377 data.phrases = '';
1378 } else {
1379 // Add mode: send individual phrases for per-phrase embedding
1380 const nonLegacyPhrases = currentPhrases.filter(p => !p.isLegacy).map(p => p.text);
1381 if (nonLegacyPhrases.length > 0) {
1382 data.individual_phrases = nonLegacyPhrases;
1383 data.phrases = '';
1384 } else {
1385 data.phrases = '';
1386 }
1387 }
1388
1389 $.ajax({
1390 url: mxchActionsData.ajaxUrl,
1391 type: 'POST',
1392 data: data,
1393 success: function(response) {
1394 hideLoading();
1395
1396 if (response.success) {
1397 // Reload list
1398 loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter);
1399
1400 // Reset and close editor
1401 resetEditorPanel();
1402 currentActionId = null;
1403
1404 // Show success message (could use a toast notification)
1405 // For now, just reload
1406 } else {
1407 alert(response.data || mxchActionsData.i18n.error);
1408 }
1409 },
1410 error: function() {
1411 hideLoading();
1412 alert(mxchActionsData.i18n.error);
1413 }
1414 });
1415 });
1416
1417 // ==========================================================================
1418 // Toggle Action Status
1419 // ==========================================================================
1420
1421 function toggleActionStatus(actionId, isEnabled) {
1422 // Optimistic UI update - update immediately before AJAX completes
1423 const $item = $('.mxch-action-item[data-action-id="' + actionId + '"]');
1424 const $status = $item.find('.mxch-action-status');
1425 const $toggle = $('#mxch-action-enabled-toggle');
1426
1427 // Immediately update UI
1428 $item.data('enabled', isEnabled ? '1' : '0');
1429 if (isEnabled) {
1430 $status.removeClass('disabled').addClass('enabled').text('Enabled');
1431 $item.removeClass('disabled');
1432 } else {
1433 $status.removeClass('enabled').addClass('disabled').text('Disabled');
1434 $item.addClass('disabled');
1435 }
1436
1437 // Update view status immediately
1438 const statusText = isEnabled ? 'Enabled' : 'Disabled';
1439 const statusClass = isEnabled ? 'mxch-status-enabled' : 'mxch-status-disabled';
1440 $('#mxch-view-status').text(statusText).removeClass('mxch-status-enabled mxch-status-disabled').addClass(statusClass);
1441
1442 // Update stored data immediately
1443 const currentData = $('#mxch-action-view').data('current-action');
1444 if (currentData) {
1445 currentData.enabled = isEnabled;
1446 $('#mxch-action-view').data('current-action', currentData);
1447 }
1448
1449 $.ajax({
1450 url: mxchActionsData.ajaxUrl,
1451 type: 'POST',
1452 data: {
1453 action: 'mxchat_toggle_action_status',
1454 action_id: actionId,
1455 enabled: isEnabled ? 1 : 0,
1456 security: mxchActionsData.toggleNonce
1457 },
1458 success: function(response) {
1459 if (!response.success) {
1460 // Revert optimistic update on failure
1461 revertToggleUI(!isEnabled, $item, $status, $toggle);
1462 alert(response.data || mxchActionsData.i18n.error);
1463 }
1464 },
1465 error: function() {
1466 // Revert optimistic update on error
1467 revertToggleUI(!isEnabled, $item, $status, $toggle);
1468 alert(mxchActionsData.i18n.error);
1469 }
1470 });
1471 }
1472
1473 // Helper to revert toggle UI on error
1474 function revertToggleUI(revertEnabled, $item, $status, $toggle) {
1475 $toggle.prop('checked', revertEnabled);
1476 $item.data('enabled', revertEnabled ? '1' : '0');
1477 if (revertEnabled) {
1478 $status.removeClass('disabled').addClass('enabled').text('Enabled');
1479 $item.removeClass('disabled');
1480 } else {
1481 $status.removeClass('enabled').addClass('disabled').text('Disabled');
1482 $item.addClass('disabled');
1483 }
1484 const statusText = revertEnabled ? 'Enabled' : 'Disabled';
1485 const statusClass = revertEnabled ? 'mxch-status-enabled' : 'mxch-status-disabled';
1486 $('#mxch-view-status').text(statusText).removeClass('mxch-status-enabled mxch-status-disabled').addClass(statusClass);
1487 }
1488
1489 // ==========================================================================
1490 // Delete Modal
1491 // ==========================================================================
1492
1493 function showDeleteModal(actionId) {
1494 $('#mxch-delete-modal').show();
1495 $('#mxch-confirm-delete').data('action-id', actionId);
1496 }
1497
1498 $('#mxch-delete-modal .mxch-modal-close, #mxch-cancel-delete').on('click', function() {
1499 $('#mxch-delete-modal').hide();
1500 });
1501
1502 $('#mxch-confirm-delete').on('click', function() {
1503 const actionId = $(this).data('action-id');
1504 $('#mxch-delete-modal').hide();
1505 deleteAction(actionId);
1506 });
1507
1508 function deleteAction(actionId) {
1509 showLoading();
1510
1511 $.ajax({
1512 url: mxchActionsData.ajaxUrl,
1513 type: 'POST',
1514 data: {
1515 action: 'mxchat_delete_intent_ajax',
1516 intent_id: actionId,
1517 security: mxchActionsData.deleteNonce
1518 },
1519 success: function(response) {
1520 hideLoading();
1521
1522 if (response.success) {
1523 currentActionId = null;
1524 resetEditorPanel();
1525 loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter);
1526 } else {
1527 alert(response.data || mxchActionsData.i18n.error);
1528 }
1529 },
1530 error: function() {
1531 hideLoading();
1532 alert(mxchActionsData.i18n.error);
1533 }
1534 });
1535 }
1536
1537 // ==========================================================================
1538 // Utility Functions
1539 // ==========================================================================
1540
1541 function showLoading() {
1542 $('#mxch-action-loading').show();
1543 }
1544
1545 function hideLoading() {
1546 $('#mxch-action-loading').hide();
1547 }
1548
1549 function escapeHtml(text) {
1550 if (!text) return '';
1551 const div = document.createElement('div');
1552 div.appendChild(document.createTextNode(text));
1553 return div.innerHTML;
1554 }
1555 });
1556