PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.0.7
MxChat – AI Chatbot & Content Generation for WordPress v3.0.7
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
← All changes | js/mxchat_actions.js +23 -624 3.2.123.0.7 View file →
@@ -59,409 +59,13 @@
59 59 $('.mxch-mobile-menu').removeClass('open');
60 60 $('.mxch-mobile-overlay').removeClass('open');
61 61 });
62 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 63 // Quick action buttons
453 64 $('.mxch-quick-action-btn[data-action="view-actions"]').on('click', function() {
454 65 $('.mxch-nav-link[data-target="all-actions"]').trigger('click');
455 66 });
456 67
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 68 $('#mxch-add-action-dashboard-btn').on('click', function() {
465 69 $('.mxch-nav-link[data-target="all-actions"]').trigger('click');
466 70 setTimeout(function() {
467 71 openActionEditor();
@@ -509,171 +113,8 @@
509 113 let selectedActions = new Set();
510 114 let currentSortOrder = 'desc';
511 115 let currentFilter = '';
512 116
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 117 // Load action list on page load
677 118 loadActionList(1, '', '');
678 119
679 120 // Search functionality with debounce
@@ -828,15 +269,15 @@
828 269 totalPages = response.data.total_pages;
829 270 updateActionCount(response.data.showing_start, response.data.showing_end, response.data.total_actions);
830 271 renderPagination(response.data.page, response.data.total_pages, searchTerm, filter);
831 272 } else {
832 - $container.html('<div class="mxch-list-empty"><p>No trigger phrases found</p></div>');
273 + $container.html('<div class="mxch-list-empty"><p>No actions found</p></div>');
833 274 updateActionCount(0, 0, 0);
834 275 $('#mxch-actions-pagination').html('');
835 276 }
836 277 },
837 278 error: function() {
838 - $container.html('<div class="mxch-list-empty"><p>Error loading trigger phrases</p></div>');
279 + $container.html('<div class="mxch-list-empty"><p>Error loading actions</p></div>');
839 280 }
840 281 });
841 282 }
842 283
@@ -860,11 +301,9 @@
860 301 data-phrases="${escapeHtml(action.phrases)}"
861 302 data-threshold="${action.threshold}"
862 303 data-callback="${escapeHtml(action.callback_function)}"
863 304 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}">
305 + data-enabled-bots='${escapeHtml(JSON.stringify(action.enabled_bots))}'>
867 306 <input type="checkbox" class="mxch-action-checkbox"${isChecked}>
868 307 <div class="mxch-action-icon">
869 308 <span class="dashicons dashicons-${escapeHtml(action.icon || 'admin-generic')}"></span>
870 309 </div>
@@ -916,11 +355,11 @@
916 355
917 356 // Update action count display
918 357 function updateActionCount(start, end, total) {
919 358 if (total === 0) {
920 - $('#mxch-action-count').text('0 trigger phrases');
359 + $('#mxch-action-count').text('0 actions');
921 360 } else {
922 - $('#mxch-action-count').text(`${start}-${end} / ${total} trigger phrases`);
361 + $('#mxch-action-count').text(`${start}-${end} / ${total} actions`);
923 362 }
924 363 }
925 364
926 365 // Render pagination
@@ -969,18 +408,12 @@
969 408 phrases: $item.data('phrases'),
970 409 threshold: $item.data('threshold'),
971 410 callback_function: $item.data('callback'),
972 411 enabled: $item.data('enabled') == '1',
973 - enabled_bots: $item.data('enabled-bots') || ['default'],
974 - has_legacy: $item.data('has-legacy') == '1',
975 - individualPhrases: []
412 + enabled_bots: $item.data('enabled-bots') || ['default']
976 413 };
977 414
978 - // Fetch individual phrases then show view
979 - fetchIndividualPhrases(actionId, function(phrases) {
980 - data.individualPhrases = phrases;
981 - showActionView(data);
982 - });
415 + showActionView(data);
983 416 }
984 417
985 418 // Show action view (details)
986 419 function showActionView(action) {
@@ -1007,22 +440,14 @@
1007 440 const iconClass = $typeCard.find('.dashicons').attr('class') || 'dashicons dashicons-admin-generic';
1008 441 $('#mxch-view-icon').html('<span class="' + iconClass + '"></span>');
1009 442 $('#mxch-detail-icon').html('<span class="' + iconClass + '"></span>');
1010 443
1011 - // Phrases - show legacy grouped phrases and individual phrases
444 + // Phrases
445 + const phrases = action.phrases.split(',').map(p => p.trim()).filter(p => p);
1012 446 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 - }
447 + phrases.forEach(function(phrase) {
448 + phrasesHtml += '<span class="mxch-phrase-tag">' + escapeHtml(phrase) + '</span>';
449 + });
1025 450 $('#mxch-view-phrases').html(phrasesHtml);
1026 451
1027 452 // Settings
1028 453 $('#mxch-view-threshold').text(action.threshold + '%');
@@ -1080,24 +505,12 @@
1080 505 $('#mxch-action-id').val(action.id);
1081 506 $('#mxch-callback-function').val(action.callback_function);
1082 507 $('#mxch-form-mode').val('edit');
1083 508 $('#mxch-action-label').val(action.label);
509 + $('#mxch-action-phrases').val(action.phrases);
1084 510 $('#mxch-action-threshold').val(action.threshold);
1085 511 $('#mxch-threshold-value').text(action.threshold + '%');
1086 512
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 513 // Set selected type display
1101 514 const $typeCard = $('.mxch-type-card[data-value="' + action.callback_function + '"]');
1102 515 const iconHtml = $typeCard.find('.mxch-type-icon').html();
1103 516 const typeLabel = $typeCard.data('label');
@@ -1105,9 +518,9 @@
1105 518
1106 519 $('#mxch-selected-type-icon').html(iconHtml);
1107 520 $('#mxch-selected-type-label').text(typeLabel);
1108 521 $('#mxch-selected-type-desc').text(typeDesc);
1109 - $('#mxch-config-title').text('Edit Trigger Phrase');
522 + $('#mxch-config-title').text('Edit Action');
1110 523
1111 524 // Set enabled bots
1112 525 const enabledBots = Array.isArray(action.enabled_bots) ? action.enabled_bots : ['default'];
1113 526 $('#mxch-bot-selector input[type="checkbox"]').each(function() {
@@ -1114,9 +527,9 @@
1114 527 $(this).prop('checked', enabledBots.includes($(this).val()));
1115 528 });
1116 529
1117 530 // Update save button text
1118 - $('#mxch-save-action').text('Update Trigger Phrase');
531 + $('#mxch-save-action').text('Update Action');
1119 532 }
1120 533
1121 534 // Reset form
1122 535 function resetForm() {
@@ -1123,16 +536,13 @@
1123 536 $('#mxch-action-id').val('');
1124 537 $('#mxch-callback-function').val('');
1125 538 $('#mxch-form-mode').val('add');
1126 539 $('#mxch-action-label').val('');
540 + $('#mxch-action-phrases').val('');
1127 541 $('#mxch-action-threshold').val(85);
1128 -
1129 - // Clear phrase tags
1130 - currentPhrases = [];
1131 - renderPhraseTags();
1132 542 $('#mxch-threshold-value').text('85%');
1133 - $('#mxch-config-title').text('Configure Trigger Phrase');
1134 - $('#mxch-save-action').text('Save Trigger Phrase');
543 + $('#mxch-config-title').text('Configure Action');
544 + $('#mxch-save-action').text('Save Action');
1135 545
1136 546 // Reset bot selection
1137 547 $('#mxch-bot-selector input[type="checkbox"]').prop('checked', false);
1138 548 $('#mxch-bot-selector input[value="default"]').prop('checked', true);
@@ -1309,8 +719,9 @@
1309 719 const formMode = $('#mxch-form-mode').val();
1310 720 const actionId = $('#mxch-action-id').val();
1311 721 const callbackFunction = $('#mxch-callback-function').val();
1312 722 const label = $('#mxch-action-label').val().trim();
723 + const phrases = $('#mxch-action-phrases').val().trim();
1313 724 const threshold = $('#mxch-action-threshold').val();
1314 725
1315 726 // Validation
1316 727 if (!callbackFunction) {
@@ -1323,12 +734,11 @@
1323 734 $('#mxch-action-label').focus();
1324 735 return;
1325 736 }
1326 737
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();
738 + if (!phrases) {
739 + alert('Please enter at least one trigger phrase.');
740 + $('#mxch-action-phrases').focus();
1331 741 return;
1332 742 }
1333 743
1334 744 // Get enabled bots
@@ -1345,8 +755,9 @@
1345 755
1346 756 const data = {
1347 757 action: formMode === 'edit' ? 'mxchat_edit_intent_ajax' : 'mxchat_add_intent_ajax',
1348 758 intent_label: label,
759 + phrases: phrases,
1349 760 similarity_threshold: threshold,
1350 761 callback_function: callbackFunction,
1351 762 enabled_bots: enabledBots,
1352 763 security: formMode === 'edit' ? mxchActionsData.editNonce : mxchActionsData.addNonce
@@ -1353,20 +764,8 @@
1353 764 };
1354 765
1355 766 if (formMode === 'edit') {
1356 767 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 768 }
1370 769
1371 770 $.ajax({
1372 771 url: mxchActionsData.ajaxUrl,