PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.10
MxChat – AI Chatbot & Content Generation for WordPress v3.2.10
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 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
mxchat-basic / js / mxchat_actions.js

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

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