PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.8
MxChat – AI Chatbot & Content Generation for WordPress v3.2.8
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.8, at js/mxchat_actions.js

1,142 lines 42.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 // Quick action buttons
64 $('.mxch-quick-action-btn[data-action="view-actions"]').on('click', function() {
65 $('.mxch-nav-link[data-target="all-actions"]').trigger('click');
66 });
67
68 $('#mxch-add-action-dashboard-btn').on('click', function() {
69 $('.mxch-nav-link[data-target="all-actions"]').trigger('click');
70 setTimeout(function() {
71 openActionEditor();
72 }, 100);
73 });
74
75 // ==========================================================================
76 // Mobile Detection & Panel Management
77 // ==========================================================================
78
79 function isMobile() {
80 return window.innerWidth <= 782;
81 }
82
83 function showMobileDetailPanel() {
84 if (isMobile()) {
85 $('.mxch-action-detail-panel').addClass('mobile-active');
86 $('body').addClass('mxch-mobile-panel-open');
87 }
88 }
89
90 function hideMobileDetailPanel() {
91 $('.mxch-action-detail-panel').removeClass('mobile-active');
92 $('body').removeClass('mxch-mobile-panel-open');
93 }
94
95 // Mobile back button handler
96 $(document).on('click', '.mxch-mobile-back-btn', function(e) {
97 e.preventDefault();
98 hideMobileDetailPanel();
99 resetEditorPanel();
100 $('.mxch-action-item').removeClass('active');
101 currentActionId = null;
102 });
103
104 // ==========================================================================
105 // Action List - Split Panel
106 // ==========================================================================
107
108 let currentPage = 1;
109 const perPage = 50;
110 let totalPages = 1;
111 let currentActionId = null;
112 let actionsLoaded = false;
113 let selectedActions = new Set();
114 let currentSortOrder = 'desc';
115 let currentFilter = '';
116
117 // ==========================================================================
118 // Phrase Tag Management
119 // ==========================================================================
120
121 let currentPhrases = []; // Array of {id, text, isLegacy}
122
123 function renderPhraseTags() {
124 const $container = $('#mxch-phrase-tags');
125 let html = '';
126 currentPhrases.forEach(function(phrase, index) {
127 const legacyClass = phrase.isLegacy ? ' mxch-phrase-legacy' : '';
128 const badge = phrase.isLegacy ? ' <span class="mxch-legacy-badge">legacy</span>' : '';
129 html += '<span class="mxch-phrase-pill' + legacyClass + '" data-index="' + index + '" data-phrase-id="' + (phrase.id || '') + '">'
130 + escapeHtml(phrase.text) + badge
131 + ' <button type="button" class="mxch-phrase-remove" title="Remove">&times;</button>'
132 + '</span>';
133 });
134 $container.html(html);
135 }
136
137 function addPhraseFromInput() {
138 const $input = $('#mxch-phrase-input');
139 const text = $input.val().trim();
140 if (!text) return;
141
142 const formMode = $('#mxch-form-mode').val();
143 const intentId = $('#mxch-action-id').val();
144
145 if (formMode === 'edit' && intentId) {
146 // Edit mode: AJAX add immediately
147 const $btn = $('#mxch-add-phrase-btn');
148 $btn.prop('disabled', true).text('Adding...');
149
150 $.ajax({
151 url: mxchActionsData.ajaxUrl,
152 type: 'POST',
153 data: {
154 action: 'mxchat_add_phrase',
155 intent_id: intentId,
156 phrase: text,
157 security: mxchActionsData.addPhraseNonce
158 },
159 success: function(response) {
160 $btn.prop('disabled', false).text('Add');
161 if (response.success) {
162 currentPhrases.push({id: response.data.id, text: text, isLegacy: false});
163 renderPhraseTags();
164 $input.val('').focus();
165 } else {
166 alert(response.data || 'Failed to add phrase.');
167 }
168 },
169 error: function() {
170 $btn.prop('disabled', false).text('Add');
171 alert('Failed to add phrase. Please try again.');
172 }
173 });
174 } else {
175 // Add mode: just push locally
176 currentPhrases.push({id: null, text: text, isLegacy: false});
177 renderPhraseTags();
178 $input.val('').focus();
179 }
180 }
181
182 // Add phrase on Enter key
183 $(document).on('keydown', '#mxch-phrase-input', function(e) {
184 if (e.key === 'Enter') {
185 e.preventDefault();
186 addPhraseFromInput();
187 }
188 });
189
190 // Add phrase on button click
191 $(document).on('click', '#mxch-add-phrase-btn', function() {
192 addPhraseFromInput();
193 });
194
195 // Remove phrase on X click
196 $(document).on('click', '.mxch-phrase-remove', function(e) {
197 e.stopPropagation();
198 const $pill = $(this).closest('.mxch-phrase-pill');
199 const index = parseInt($pill.data('index'));
200 const phrase = currentPhrases[index];
201
202 if (!phrase) return;
203
204 if (phrase.isLegacy) {
205 // Delete legacy phrases from main table
206 const intentId = $('#mxch-action-id').val();
207 if (!intentId) {
208 currentPhrases.splice(index, 1);
209 renderPhraseTags();
210 return;
211 }
212
213 if (!confirm('Remove all legacy grouped phrases? You can re-add them individually for better matching.')) return;
214
215 $.ajax({
216 url: mxchActionsData.ajaxUrl,
217 type: 'POST',
218 data: {
219 action: 'mxchat_delete_legacy_phrases',
220 intent_id: intentId,
221 security: mxchActionsData.deleteLegacyNonce
222 },
223 success: function(response) {
224 if (response.success) {
225 currentPhrases.splice(index, 1);
226 renderPhraseTags();
227 } else {
228 alert(response.data || 'Failed to remove legacy phrases.');
229 }
230 }
231 });
232 } else if (phrase.id) {
233 // Delete individual phrase via AJAX
234 $.ajax({
235 url: mxchActionsData.ajaxUrl,
236 type: 'POST',
237 data: {
238 action: 'mxchat_delete_phrase',
239 phrase_id: phrase.id,
240 security: mxchActionsData.deletePhraseNonce
241 },
242 success: function(response) {
243 if (response.success) {
244 currentPhrases.splice(index, 1);
245 renderPhraseTags();
246 } else {
247 alert(response.data || 'Failed to delete phrase.');
248 }
249 }
250 });
251 } else {
252 // Local-only phrase (add mode, not yet saved)
253 currentPhrases.splice(index, 1);
254 renderPhraseTags();
255 }
256 });
257
258 function fetchIndividualPhrases(intentId, callback) {
259 $.ajax({
260 url: mxchActionsData.ajaxUrl,
261 type: 'POST',
262 data: {
263 action: 'mxchat_get_phrases',
264 intent_id: intentId,
265 security: mxchActionsData.getPhrasesNonce
266 },
267 success: function(response) {
268 if (response.success) {
269 callback(response.data.phrases || []);
270 } else {
271 callback([]);
272 }
273 },
274 error: function() {
275 callback([]);
276 }
277 });
278 }
279
280 // Load action list on page load
281 loadActionList(1, '', '');
282
283 // Search functionality with debounce
284 let searchTimeout;
285 $('#mxch-search-actions').on('input', function() {
286 clearTimeout(searchTimeout);
287 const searchTerm = $(this).val().toLowerCase();
288
289 searchTimeout = setTimeout(function() {
290 currentPage = 1;
291 loadActionList(currentPage, searchTerm, currentFilter);
292 }, 300);
293 });
294
295 // Filter by type
296 $('#mxch-filter-actions').on('change', function() {
297 currentFilter = $(this).val();
298 currentPage = 1;
299 loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter);
300 });
301
302 // Refresh button
303 $('#mxch-refresh-actions').on('click', function() {
304 const $btn = $(this);
305 $btn.addClass('spinning');
306 loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter);
307 setTimeout(() => $btn.removeClass('spinning'), 500);
308 });
309
310 // ==========================================================================
311 // Bulk Selection & Actions
312 // ==========================================================================
313
314 // Select all checkbox
315 $('#mxch-select-all-actions').on('change', function() {
316 const isChecked = $(this).is(':checked');
317 $('.mxch-action-checkbox').prop('checked', isChecked);
318
319 if (isChecked) {
320 $('.mxch-action-item').each(function() {
321 selectedActions.add($(this).data('action-id'));
322 $(this).addClass('selected');
323 });
324 $('#mxch-action-list').addClass('selection-mode');
325 } else {
326 selectedActions.clear();
327 $('.mxch-action-item').removeClass('selected');
328 $('#mxch-action-list').removeClass('selection-mode');
329 }
330
331 updateSelectionUI();
332 });
333
334 // Update selection UI
335 function updateSelectionUI() {
336 const count = selectedActions.size;
337 const $countEl = $('#mxch-selected-action-count');
338 const $deleteBtn = $('#mxch-delete-selected-actions');
339
340 if (count > 0) {
341 $countEl.text(count + ' selected').addClass('has-selection');
342 $deleteBtn.prop('disabled', false);
343 $('#mxch-action-list').addClass('selection-mode');
344 } else {
345 $countEl.removeClass('has-selection');
346 $deleteBtn.prop('disabled', true);
347 $('#mxch-action-list').removeClass('selection-mode');
348 }
349
350 // Update select all checkbox state
351 const totalItems = $('.mxch-action-checkbox').length;
352 const checkedItems = $('.mxch-action-checkbox:checked').length;
353 $('#mxch-select-all-actions').prop('checked', totalItems > 0 && checkedItems === totalItems);
354 $('#mxch-select-all-actions').prop('indeterminate', checkedItems > 0 && checkedItems < totalItems);
355 }
356
357 // Delete selected button
358 $('#mxch-delete-selected-actions').on('click', function() {
359 const count = selectedActions.size;
360 if (count === 0) return;
361
362 if (!confirm(mxchActionsData.i18n.confirmBulkDelete)) {
363 return;
364 }
365
366 deleteMultipleActions(Array.from(selectedActions));
367 });
368
369 // Delete multiple actions
370 function deleteMultipleActions(actionIds) {
371 showLoading();
372
373 $.ajax({
374 url: mxchActionsData.ajaxUrl,
375 type: 'POST',
376 data: {
377 action: 'mxchat_bulk_delete_actions',
378 action_ids: actionIds,
379 security: mxchActionsData.deleteNonce
380 },
381 success: function(response) {
382 hideLoading();
383
384 if (response.success) {
385 // Clear selection
386 selectedActions.clear();
387 $('#mxch-select-all-actions').prop('checked', false);
388 updateSelectionUI();
389
390 // If current action was deleted, reset panel
391 if (actionIds.includes(currentActionId)) {
392 currentActionId = null;
393 resetEditorPanel();
394 }
395
396 // Reload list
397 loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter);
398 } else {
399 alert(response.data || mxchActionsData.i18n.error);
400 }
401 },
402 error: function() {
403 hideLoading();
404 alert(mxchActionsData.i18n.error);
405 }
406 });
407 }
408
409 // Load action list function
410 function loadActionList(page, searchTerm, filter) {
411 const $container = $('#mxch-action-list');
412 $container.html('<div class="mxch-list-loading"><span class="spinner is-active"></span></div>');
413
414 $.ajax({
415 url: mxchActionsData.ajaxUrl,
416 type: 'POST',
417 data: {
418 action: 'mxchat_fetch_actions_list',
419 page: page,
420 per_page: perPage,
421 search: searchTerm,
422 callback_filter: filter,
423 sort_order: currentSortOrder,
424 security: mxchActionsData.nonce
425 },
426 success: function(response) {
427 actionsLoaded = true;
428
429 if (response.success && response.data.actions && response.data.actions.length > 0) {
430 renderActionList(response.data.actions);
431 currentPage = response.data.page;
432 totalPages = response.data.total_pages;
433 updateActionCount(response.data.showing_start, response.data.showing_end, response.data.total_actions);
434 renderPagination(response.data.page, response.data.total_pages, searchTerm, filter);
435 } else {
436 $container.html('<div class="mxch-list-empty"><p>No actions found</p></div>');
437 updateActionCount(0, 0, 0);
438 $('#mxch-actions-pagination').html('');
439 }
440 },
441 error: function() {
442 $container.html('<div class="mxch-list-empty"><p>Error loading actions</p></div>');
443 }
444 });
445 }
446
447 // Render action list items
448 function renderActionList(actions) {
449 const $container = $('#mxch-action-list');
450 let html = '';
451
452 actions.forEach(function(action) {
453 const isActive = action.id == currentActionId ? ' active' : '';
454 const isSelected = selectedActions.has(action.id) ? ' selected' : '';
455 const isChecked = selectedActions.has(action.id) ? ' checked' : '';
456 const isDisabled = !action.enabled ? ' disabled' : '';
457 const statusClass = action.enabled ? 'enabled' : 'disabled';
458 const statusText = action.enabled ? 'Enabled' : 'Disabled';
459
460 html += `
461 <div class="mxch-action-item${isActive}${isSelected}${isDisabled}"
462 data-action-id="${action.id}"
463 data-label="${escapeHtml(action.label)}"
464 data-phrases="${escapeHtml(action.phrases)}"
465 data-threshold="${action.threshold}"
466 data-callback="${escapeHtml(action.callback_function)}"
467 data-enabled="${action.enabled ? '1' : '0'}"
468 data-enabled-bots='${escapeHtml(JSON.stringify(action.enabled_bots))}'
469 data-has-legacy="${action.has_legacy_vector ? '1' : '0'}"
470 data-individual-count="${action.individual_phrase_count || 0}">
471 <input type="checkbox" class="mxch-action-checkbox"${isChecked}>
472 <div class="mxch-action-icon">
473 <span class="dashicons dashicons-${escapeHtml(action.icon || 'admin-generic')}"></span>
474 </div>
475 <div class="mxch-action-info">
476 <div class="mxch-action-name">${escapeHtml(action.label)}</div>
477 <div class="mxch-action-type-label">${escapeHtml(action.callback_label)}</div>
478 </div>
479 <div class="mxch-action-meta">
480 <span class="mxch-action-status ${statusClass}">${statusText}</span>
481 </div>
482 </div>
483 `;
484 });
485
486 $container.html(html);
487
488 // Attach checkbox handlers
489 $('.mxch-action-checkbox').on('click', function(e) {
490 e.stopPropagation();
491 const $item = $(this).closest('.mxch-action-item');
492 const actionId = $item.data('action-id');
493
494 if ($(this).is(':checked')) {
495 selectedActions.add(actionId);
496 $item.addClass('selected');
497 } else {
498 selectedActions.delete(actionId);
499 $item.removeClass('selected');
500 }
501
502 updateSelectionUI();
503 });
504
505 // Attach click handlers for selecting action
506 $('.mxch-action-item').on('click', function(e) {
507 if ($(e.target).is('.mxch-action-checkbox')) return;
508
509 const actionId = $(this).data('action-id');
510 selectAction(actionId, $(this));
511
512 // Update active state
513 $('.mxch-action-item').removeClass('active');
514 $(this).addClass('active');
515 });
516
517 // Update selection UI after render
518 updateSelectionUI();
519 }
520
521 // Update action count display
522 function updateActionCount(start, end, total) {
523 if (total === 0) {
524 $('#mxch-action-count').text('0 actions');
525 } else {
526 $('#mxch-action-count').text(`${start}-${end} / ${total} actions`);
527 }
528 }
529
530 // Render pagination
531 function renderPagination(currentPage, totalPages, searchTerm, filter) {
532 const $container = $('#mxch-actions-pagination');
533
534 if (totalPages <= 1) {
535 $container.html('');
536 return;
537 }
538
539 let html = '<div class="mxch-pagination-btns">';
540
541 if (currentPage > 1) {
542 html += `<button class="mxch-page-btn" data-page="${currentPage - 1}">&laquo;</button>`;
543 }
544
545 html += `<span class="mxch-page-info">${currentPage} / ${totalPages}</span>`;
546
547 if (currentPage < totalPages) {
548 html += `<button class="mxch-page-btn" data-page="${currentPage + 1}">&raquo;</button>`;
549 }
550
551 html += '</div>';
552 $container.html(html);
553
554 // Pagination click handlers
555 $('.mxch-page-btn').on('click', function() {
556 const pageNum = $(this).data('page');
557 loadActionList(pageNum, searchTerm, filter);
558 });
559 }
560
561 // ==========================================================================
562 // Action Editor Panel
563 // ==========================================================================
564
565 // Select and view an action
566 function selectAction(actionId, $item) {
567 currentActionId = actionId;
568
569 // Get data from item attributes
570 const data = {
571 id: actionId,
572 label: $item.data('label'),
573 phrases: $item.data('phrases'),
574 threshold: $item.data('threshold'),
575 callback_function: $item.data('callback'),
576 enabled: $item.data('enabled') == '1',
577 enabled_bots: $item.data('enabled-bots') || ['default'],
578 has_legacy: $item.data('has-legacy') == '1',
579 individualPhrases: []
580 };
581
582 // Fetch individual phrases then show view
583 fetchIndividualPhrases(actionId, function(phrases) {
584 data.individualPhrases = phrases;
585 showActionView(data);
586 });
587 }
588
589 // Show action view (details)
590 function showActionView(action) {
591 $('#mxch-action-empty').hide();
592 $('#mxch-action-editor').show();
593
594 // Hide editor steps, show view
595 $('.mxch-editor-step').removeClass('active');
596 $('#mxch-action-view').addClass('active');
597
598 // Show detail panel on mobile
599 showMobileDetailPanel();
600
601 // Populate view
602 $('#mxch-view-title').text(action.label);
603 $('#mxch-view-label').text(action.label);
604
605 // Get callback label from filter dropdown
606 const callbackLabel = $('#mxch-filter-actions option[value="' + action.callback_function + '"]').text() || action.callback_function;
607 $('#mxch-view-type').text(callbackLabel);
608
609 // Get icon from type grid
610 const $typeCard = $('.mxch-type-card[data-value="' + action.callback_function + '"]');
611 const iconClass = $typeCard.find('.dashicons').attr('class') || 'dashicons dashicons-admin-generic';
612 $('#mxch-view-icon').html('<span class="' + iconClass + '"></span>');
613 $('#mxch-detail-icon').html('<span class="' + iconClass + '"></span>');
614
615 // Phrases - show legacy grouped phrases and individual phrases
616 let phrasesHtml = '';
617 const legacyPhrases = action.phrases ? String(action.phrases).trim() : '';
618 if (legacyPhrases) {
619 phrasesHtml += '<span class="mxch-phrase-tag mxch-phrase-legacy">' + escapeHtml(legacyPhrases) + ' <span class="mxch-legacy-badge">legacy</span></span>';
620 }
621 if (action.individualPhrases && action.individualPhrases.length) {
622 action.individualPhrases.forEach(function(p) {
623 phrasesHtml += '<span class="mxch-phrase-tag">' + escapeHtml(p.phrase) + '</span>';
624 });
625 }
626 if (!phrasesHtml) {
627 phrasesHtml = '<span class="mxch-no-phrases">No trigger phrases configured</span>';
628 }
629 $('#mxch-view-phrases').html(phrasesHtml);
630
631 // Settings
632 $('#mxch-view-threshold').text(action.threshold + '%');
633
634 const statusText = action.enabled ? 'Enabled' : 'Disabled';
635 const statusClass = action.enabled ? 'mxch-status-enabled' : 'mxch-status-disabled';
636 $('#mxch-view-status').text(statusText).removeClass('mxch-status-enabled mxch-status-disabled').addClass(statusClass);
637
638 // Toggle switch
639 $('#mxch-action-enabled-toggle').prop('checked', action.enabled);
640
641 // Bots
642 let botsHtml = '';
643 const enabledBots = Array.isArray(action.enabled_bots) ? action.enabled_bots : ['default'];
644 enabledBots.forEach(function(bot) {
645 const botName = bot === 'default' ? 'Default Bot' : bot;
646 botsHtml += '<span class="mxch-bot-tag">' + escapeHtml(botName) + '</span>';
647 });
648 $('#mxch-view-bots').html(botsHtml);
649
650 // Store current action data for editing
651 $('#mxch-action-view').data('current-action', action);
652 }
653
654 // Open action editor (for creating new)
655 function openActionEditor() {
656 currentActionId = null;
657
658 $('#mxch-action-empty').hide();
659 $('#mxch-action-editor').show();
660
661 // Show step 1
662 $('.mxch-editor-step').removeClass('active');
663 $('#mxch-editor-step-1').addClass('active');
664
665 // Reset form
666 resetForm();
667
668 // Show detail panel on mobile
669 showMobileDetailPanel();
670 }
671
672 // Open action editor for editing
673 function openActionEditorForEdit(action) {
674 currentActionId = action.id;
675
676 $('#mxch-action-empty').hide();
677 $('#mxch-action-editor').show();
678
679 // Go directly to step 2
680 $('.mxch-editor-step').removeClass('active');
681 $('#mxch-editor-step-2').addClass('active');
682
683 // Populate form
684 $('#mxch-action-id').val(action.id);
685 $('#mxch-callback-function').val(action.callback_function);
686 $('#mxch-form-mode').val('edit');
687 $('#mxch-action-label').val(action.label);
688 $('#mxch-action-threshold').val(action.threshold);
689 $('#mxch-threshold-value').text(action.threshold + '%');
690
691 // Populate phrase tags
692 currentPhrases = [];
693 const legacyPhrases = action.phrases ? String(action.phrases).trim() : '';
694 if (legacyPhrases) {
695 currentPhrases.push({id: 'legacy', text: legacyPhrases, isLegacy: true});
696 }
697 if (action.individualPhrases && action.individualPhrases.length) {
698 action.individualPhrases.forEach(function(p) {
699 currentPhrases.push({id: p.id, text: p.phrase, isLegacy: false});
700 });
701 }
702 renderPhraseTags();
703
704 // Set selected type display
705 const $typeCard = $('.mxch-type-card[data-value="' + action.callback_function + '"]');
706 const iconHtml = $typeCard.find('.mxch-type-icon').html();
707 const typeLabel = $typeCard.data('label');
708 const typeDesc = $typeCard.find('p').text();
709
710 $('#mxch-selected-type-icon').html(iconHtml);
711 $('#mxch-selected-type-label').text(typeLabel);
712 $('#mxch-selected-type-desc').text(typeDesc);
713 $('#mxch-config-title').text('Edit Action');
714
715 // Set enabled bots
716 const enabledBots = Array.isArray(action.enabled_bots) ? action.enabled_bots : ['default'];
717 $('#mxch-bot-selector input[type="checkbox"]').each(function() {
718 $(this).prop('checked', enabledBots.includes($(this).val()));
719 });
720
721 // Update save button text
722 $('#mxch-save-action').text('Update Action');
723 }
724
725 // Reset form
726 function resetForm() {
727 $('#mxch-action-id').val('');
728 $('#mxch-callback-function').val('');
729 $('#mxch-form-mode').val('add');
730 $('#mxch-action-label').val('');
731 $('#mxch-action-threshold').val(85);
732
733 // Clear phrase tags
734 currentPhrases = [];
735 renderPhraseTags();
736 $('#mxch-threshold-value').text('85%');
737 $('#mxch-config-title').text('Configure Action');
738 $('#mxch-save-action').text('Save Action');
739
740 // Reset bot selection
741 $('#mxch-bot-selector input[type="checkbox"]').prop('checked', false);
742 $('#mxch-bot-selector input[value="default"]').prop('checked', true);
743
744 // Reset type selection
745 $('.mxch-type-card').removeClass('selected');
746 }
747
748 // Reset editor panel to empty state
749 function resetEditorPanel() {
750 $('#mxch-action-editor').hide();
751 $('#mxch-action-empty').show();
752 $('.mxch-editor-step').removeClass('active');
753
754 // Hide mobile panel
755 hideMobileDetailPanel();
756 }
757
758 // ==========================================================================
759 // Editor Event Handlers
760 // ==========================================================================
761
762 // Add new action buttons
763 $('#mxch-add-action-btn, #mxch-create-first-action-btn').on('click', function() {
764 openActionEditor();
765 });
766
767 // Close editor buttons
768 $('#mxch-close-editor, #mxch-close-editor-2').on('click', function() {
769 resetEditorPanel();
770 $('.mxch-action-item').removeClass('active');
771 currentActionId = null;
772 });
773
774 // Cancel button
775 $('#mxch-cancel-action').on('click', function() {
776 if (currentActionId && $('#mxch-form-mode').val() === 'edit') {
777 // Go back to view mode
778 const action = $('#mxch-action-view').data('current-action');
779 if (action) {
780 showActionView(action);
781 }
782 } else {
783 resetEditorPanel();
784 }
785 });
786
787 // Back to step 1
788 $('#mxch-back-to-step-1').on('click', function() {
789 if ($('#mxch-form-mode').val() === 'edit') {
790 // If editing, go back to view
791 const action = $('#mxch-action-view').data('current-action');
792 if (action) {
793 showActionView(action);
794 }
795 } else {
796 // If creating, go back to step 1
797 $('.mxch-editor-step').removeClass('active');
798 $('#mxch-editor-step-1').addClass('active');
799 }
800 });
801
802 // Edit action button
803 $('#mxch-edit-action-btn').on('click', function() {
804 const action = $('#mxch-action-view').data('current-action');
805 if (action) {
806 openActionEditorForEdit(action);
807 }
808 });
809
810 // Toggle enabled status
811 $('#mxch-action-enabled-toggle').on('change', function() {
812 if (!currentActionId) return;
813
814 const isEnabled = $(this).is(':checked');
815 toggleActionStatus(currentActionId, isEnabled);
816 });
817
818 // Delete action button
819 $('#mxch-delete-action-btn').on('click', function() {
820 if (!currentActionId) return;
821 showDeleteModal(currentActionId);
822 });
823
824 // ==========================================================================
825 // Action Type Selection (Step 1)
826 // ==========================================================================
827
828 // Type search
829 $('#mxch-type-search-input').on('input', function() {
830 const searchTerm = $(this).val().toLowerCase();
831 filterTypeCards(searchTerm, 'all');
832 });
833
834 // Category buttons
835 $('.mxch-category-btn').on('click', function() {
836 $('.mxch-category-btn').removeClass('active');
837 $(this).addClass('active');
838
839 const category = $(this).data('category');
840 const searchTerm = $('#mxch-type-search-input').val().toLowerCase();
841 filterTypeCards(searchTerm, category);
842 });
843
844 // Filter type cards
845 function filterTypeCards(searchTerm, category) {
846 $('.mxch-type-card').each(function() {
847 const $card = $(this);
848 const cardCategory = $card.data('category');
849 const cardLabel = $card.data('label').toLowerCase();
850 const cardDesc = $card.find('p').text().toLowerCase();
851
852 const matchesCategory = category === 'all' || cardCategory === category;
853 const matchesSearch = !searchTerm || cardLabel.includes(searchTerm) || cardDesc.includes(searchTerm);
854
855 if (matchesCategory && matchesSearch) {
856 $card.show();
857 } else {
858 $card.hide();
859 }
860 });
861 }
862
863 // Type card click
864 $('.mxch-type-card').on('click', function() {
865 const $card = $(this);
866
867 // Check if pro required
868 if ($card.hasClass('mxch-type-pro') && !mxchActionsData.isActivated) {
869 alert(mxchActionsData.i18n.proRequired);
870 return;
871 }
872
873 // Check if addon required
874 if ($card.hasClass('mxch-type-addon') && $card.data('installed') !== true) {
875 alert(mxchActionsData.i18n.addonRequired);
876 return;
877 }
878
879 // Select this card
880 $('.mxch-type-card').removeClass('selected');
881 $card.addClass('selected');
882
883 // Get card data
884 const callback = $card.data('value');
885 const label = $card.data('label');
886 const iconHtml = $card.find('.mxch-type-icon').html();
887 const desc = $card.find('p').text();
888
889 // Populate step 2
890 $('#mxch-callback-function').val(callback);
891 $('#mxch-selected-type-icon').html(iconHtml);
892 $('#mxch-selected-type-label').text(label);
893 $('#mxch-selected-type-desc').text(desc);
894
895 // Go to step 2
896 $('.mxch-editor-step').removeClass('active');
897 $('#mxch-editor-step-2').addClass('active');
898 });
899
900 // ==========================================================================
901 // Form Handling
902 // ==========================================================================
903
904 // Threshold slider
905 $('#mxch-action-threshold').on('input', function() {
906 $('#mxch-threshold-value').text($(this).val() + '%');
907 });
908
909 // Form submission
910 $('#mxch-action-form').on('submit', function(e) {
911 e.preventDefault();
912
913 const formMode = $('#mxch-form-mode').val();
914 const actionId = $('#mxch-action-id').val();
915 const callbackFunction = $('#mxch-callback-function').val();
916 const label = $('#mxch-action-label').val().trim();
917 const threshold = $('#mxch-action-threshold').val();
918
919 // Validation
920 if (!callbackFunction) {
921 alert('Please select an action type.');
922 return;
923 }
924
925 if (!label) {
926 alert('Please enter an action label.');
927 $('#mxch-action-label').focus();
928 return;
929 }
930
931 // Check phrases exist (from tag input)
932 if (currentPhrases.length === 0) {
933 alert('Please add at least one trigger phrase.');
934 $('#mxch-phrase-input').focus();
935 return;
936 }
937
938 // Get enabled bots
939 const enabledBots = [];
940 $('#mxch-bot-selector input[type="checkbox"]:checked').each(function() {
941 enabledBots.push($(this).val());
942 });
943
944 if (enabledBots.length === 0) {
945 enabledBots.push('default');
946 }
947
948 showLoading();
949
950 const data = {
951 action: formMode === 'edit' ? 'mxchat_edit_intent_ajax' : 'mxchat_add_intent_ajax',
952 intent_label: label,
953 similarity_threshold: threshold,
954 callback_function: callbackFunction,
955 enabled_bots: enabledBots,
956 security: formMode === 'edit' ? mxchActionsData.editNonce : mxchActionsData.addNonce
957 };
958
959 if (formMode === 'edit') {
960 data.intent_id = actionId;
961 // In edit mode, phrases are managed via individual AJAX calls already
962 // Send empty phrases to skip legacy re-embedding
963 data.phrases = '';
964 } else {
965 // Add mode: send individual phrases for per-phrase embedding
966 const nonLegacyPhrases = currentPhrases.filter(p => !p.isLegacy).map(p => p.text);
967 if (nonLegacyPhrases.length > 0) {
968 data.individual_phrases = nonLegacyPhrases;
969 data.phrases = '';
970 } else {
971 data.phrases = '';
972 }
973 }
974
975 $.ajax({
976 url: mxchActionsData.ajaxUrl,
977 type: 'POST',
978 data: data,
979 success: function(response) {
980 hideLoading();
981
982 if (response.success) {
983 // Reload list
984 loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter);
985
986 // Reset and close editor
987 resetEditorPanel();
988 currentActionId = null;
989
990 // Show success message (could use a toast notification)
991 // For now, just reload
992 } else {
993 alert(response.data || mxchActionsData.i18n.error);
994 }
995 },
996 error: function() {
997 hideLoading();
998 alert(mxchActionsData.i18n.error);
999 }
1000 });
1001 });
1002
1003 // ==========================================================================
1004 // Toggle Action Status
1005 // ==========================================================================
1006
1007 function toggleActionStatus(actionId, isEnabled) {
1008 // Optimistic UI update - update immediately before AJAX completes
1009 const $item = $('.mxch-action-item[data-action-id="' + actionId + '"]');
1010 const $status = $item.find('.mxch-action-status');
1011 const $toggle = $('#mxch-action-enabled-toggle');
1012
1013 // Immediately update UI
1014 $item.data('enabled', isEnabled ? '1' : '0');
1015 if (isEnabled) {
1016 $status.removeClass('disabled').addClass('enabled').text('Enabled');
1017 $item.removeClass('disabled');
1018 } else {
1019 $status.removeClass('enabled').addClass('disabled').text('Disabled');
1020 $item.addClass('disabled');
1021 }
1022
1023 // Update view status immediately
1024 const statusText = isEnabled ? 'Enabled' : 'Disabled';
1025 const statusClass = isEnabled ? 'mxch-status-enabled' : 'mxch-status-disabled';
1026 $('#mxch-view-status').text(statusText).removeClass('mxch-status-enabled mxch-status-disabled').addClass(statusClass);
1027
1028 // Update stored data immediately
1029 const currentData = $('#mxch-action-view').data('current-action');
1030 if (currentData) {
1031 currentData.enabled = isEnabled;
1032 $('#mxch-action-view').data('current-action', currentData);
1033 }
1034
1035 $.ajax({
1036 url: mxchActionsData.ajaxUrl,
1037 type: 'POST',
1038 data: {
1039 action: 'mxchat_toggle_action_status',
1040 action_id: actionId,
1041 enabled: isEnabled ? 1 : 0,
1042 security: mxchActionsData.toggleNonce
1043 },
1044 success: function(response) {
1045 if (!response.success) {
1046 // Revert optimistic update on failure
1047 revertToggleUI(!isEnabled, $item, $status, $toggle);
1048 alert(response.data || mxchActionsData.i18n.error);
1049 }
1050 },
1051 error: function() {
1052 // Revert optimistic update on error
1053 revertToggleUI(!isEnabled, $item, $status, $toggle);
1054 alert(mxchActionsData.i18n.error);
1055 }
1056 });
1057 }
1058
1059 // Helper to revert toggle UI on error
1060 function revertToggleUI(revertEnabled, $item, $status, $toggle) {
1061 $toggle.prop('checked', revertEnabled);
1062 $item.data('enabled', revertEnabled ? '1' : '0');
1063 if (revertEnabled) {
1064 $status.removeClass('disabled').addClass('enabled').text('Enabled');
1065 $item.removeClass('disabled');
1066 } else {
1067 $status.removeClass('enabled').addClass('disabled').text('Disabled');
1068 $item.addClass('disabled');
1069 }
1070 const statusText = revertEnabled ? 'Enabled' : 'Disabled';
1071 const statusClass = revertEnabled ? 'mxch-status-enabled' : 'mxch-status-disabled';
1072 $('#mxch-view-status').text(statusText).removeClass('mxch-status-enabled mxch-status-disabled').addClass(statusClass);
1073 }
1074
1075 // ==========================================================================
1076 // Delete Modal
1077 // ==========================================================================
1078
1079 function showDeleteModal(actionId) {
1080 $('#mxch-delete-modal').show();
1081 $('#mxch-confirm-delete').data('action-id', actionId);
1082 }
1083
1084 $('#mxch-delete-modal .mxch-modal-close, #mxch-cancel-delete').on('click', function() {
1085 $('#mxch-delete-modal').hide();
1086 });
1087
1088 $('#mxch-confirm-delete').on('click', function() {
1089 const actionId = $(this).data('action-id');
1090 $('#mxch-delete-modal').hide();
1091 deleteAction(actionId);
1092 });
1093
1094 function deleteAction(actionId) {
1095 showLoading();
1096
1097 $.ajax({
1098 url: mxchActionsData.ajaxUrl,
1099 type: 'POST',
1100 data: {
1101 action: 'mxchat_delete_intent_ajax',
1102 intent_id: actionId,
1103 security: mxchActionsData.deleteNonce
1104 },
1105 success: function(response) {
1106 hideLoading();
1107
1108 if (response.success) {
1109 currentActionId = null;
1110 resetEditorPanel();
1111 loadActionList(currentPage, $('#mxch-search-actions').val(), currentFilter);
1112 } else {
1113 alert(response.data || mxchActionsData.i18n.error);
1114 }
1115 },
1116 error: function() {
1117 hideLoading();
1118 alert(mxchActionsData.i18n.error);
1119 }
1120 });
1121 }
1122
1123 // ==========================================================================
1124 // Utility Functions
1125 // ==========================================================================
1126
1127 function showLoading() {
1128 $('#mxch-action-loading').show();
1129 }
1130
1131 function hideLoading() {
1132 $('#mxch-action-loading').hide();
1133 }
1134
1135 function escapeHtml(text) {
1136 if (!text) return '';
1137 const div = document.createElement('div');
1138 div.appendChild(document.createTextNode(text));
1139 return div.innerHTML;
1140 }
1141 });
1142