PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.2.3
MxChat – AI Chatbot & Content Generation for WordPress v2.2.3
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-admin.js +1659 -155 2.0.42.2.3 View file →
@@ -10,9 +10,9 @@
10 10 timeout = setTimeout(later, wait);
11 11 };
12 12 }
13 13
14 -// Helper function to open edit modal
14 +// Helper function to open edit modal for intents/actions
15 15 function mxchatOpenEditModal(intentId, phrases) {
16 16 const modal = document.getElementById('mxchat-edit-modal');
17 17 if (!modal) return;
18 18
@@ -59,12 +59,145 @@
59 59
60 60 // Focus the textarea
61 61 phrasesField.focus();
62 62 }
63 +// Live Agent Notice Dismissal Function
64 +function dismissLiveAgentNotice() {
65 + if (typeof jQuery !== 'undefined' && typeof mxchatLiveAgent !== 'undefined') {
66 + jQuery.post(mxchatLiveAgent.ajaxurl, {
67 + action: 'dismiss_live_agent_notice',
68 + nonce: mxchatLiveAgent.nonce
69 + }, function(response) {
70 + if (response.success) {
71 + jQuery('#mxchat-disabled-notice').fadeOut(300);
72 + }
73 + }).fail(function() {
74 + // Fallback: just hide the notice if AJAX fails
75 + jQuery('#mxchat-disabled-notice').fadeOut(300);
76 + });
77 + } else {
78 + // Fallback for cases where jQuery or localized data isn't available
79 + var notice = document.getElementById('mxchat-disabled-notice');
80 + if (notice) {
81 + notice.style.display = 'none';
82 + }
83 + }
84 +}
63 85
86 +// Updated mxchatOpenActionModal function to integrate with the new selector
87 +function mxchatOpenActionModal(isEdit = false, actionId = '', label = '', phrases = '', threshold = 85, callbackFunction = '') {
88 + const modal = document.getElementById('mxchat-action-modal');
89 + if (!modal) return;
90 +
91 + // Get form fields
92 + const actionIdField = document.getElementById('edit_action_id');
93 + const labelField = document.getElementById('intent_label');
94 + const phrasesField = document.getElementById('action_phrases');
95 + const formActionType = document.getElementById('form_action_type');
96 + const callbackGroup = document.getElementById('callback_selection_group');
97 + const callbackSelect = document.getElementById('callback_function');
98 + const saveButton = document.getElementById('mxchat-save-action-btn');
99 + const nonceContainer = document.getElementById('action-nonce-container');
100 + const thresholdSlider = document.getElementById('similarity_threshold');
101 + const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
102 +
103 + // Set up modal for edit or create
104 + if (isEdit) {
105 + saveButton.textContent = 'Update Action';
106 + formActionType.value = 'mxchat_edit_intent';
107 + actionIdField.value = actionId;
108 + labelField.value = label;
109 + phrasesField.value = phrases;
110 + callbackGroup.style.display = 'none'; // Hide callback selection when editing
111 + thresholdSlider.value = threshold; // Set the current threshold value
112 + thresholdDisplay.textContent = threshold + '%'; // Update display
113 +
114 + // Remove the required attribute when editing
115 + callbackSelect.removeAttribute('required');
116 +
117 + // Update the nonce field for editing
118 + nonceContainer.innerHTML = ''; // Clear existing nonce
119 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.edit_intent_nonce) {
120 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.edit_intent_nonce}">`;
121 + }
122 + } else {
123 + saveButton.textContent = 'Save Action';
124 + formActionType.value = 'mxchat_add_intent';
125 + actionIdField.value = '';
126 + labelField.value = '';
127 + phrasesField.value = '';
128 + callbackGroup.style.display = 'block'; // Show callback selection when creating
129 + thresholdSlider.value = 85; // Default value for new actions
130 + thresholdDisplay.textContent = '85%'; // Default display
131 +
132 + // Ensure the required attribute is present when adding
133 + callbackSelect.setAttribute('required', 'required');
134 +
135 + // Update the nonce field for adding
136 + nonceContainer.innerHTML = ''; // Clear existing nonce
137 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.add_intent_nonce) {
138 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.add_intent_nonce}">`;
139 + }
140 + }
141 +
142 + // Show modal with animation
143 + modal.style.display = 'flex';
144 + requestAnimationFrame(() => {
145 + modal.classList.add('active');
146 + });
147 +
148 + // Set up close handlers
149 + const closeModal = () => {
150 + modal.classList.remove('active');
151 + setTimeout(() => {
152 + modal.style.display = 'none';
153 + }, 300); // Match the CSS transition time
154 + };
155 +
156 + // Close button handler
157 + const closeBtn = modal.querySelector('.mxchat-modal-close');
158 + if (closeBtn) {
159 + closeBtn.onclick = closeModal;
160 + }
161 +
162 + // Cancel button handler
163 + const cancelBtn = modal.querySelector('.mxchat-modal-cancel');
164 + if (cancelBtn) {
165 + cancelBtn.onclick = closeModal;
166 + }
167 +
168 + // Click outside modal to close
169 + modal.onclick = (e) => {
170 + if (e.target === modal) {
171 + closeModal();
172 + }
173 + };
174 +
175 + // Escape key to close modal
176 + document.addEventListener('keydown', function(e) {
177 + if (e.key === 'Escape' && modal.classList.contains('active')) {
178 + closeModal();
179 + }
180 + }, { once: true });
181 +
182 + // Focus the first field
183 + labelField.focus();
184 +
185 + // Dispatch an event for the action type selector to catch
186 + const event = new CustomEvent('mxchatModalOpened', {
187 + detail: {
188 + isEdit: isEdit,
189 + callbackFunction: callbackFunction || (isEdit ? callbackSelect.value : '')
190 + }
191 + });
192 + document.dispatchEvent(event);
193 +
194 + return closeModal; // Return close function for external use
195 +}
196 +
64 197 // Initialize event listeners
65 198 document.addEventListener('DOMContentLoaded', () => {
66 - // Set up edit button handlers
199 + // Set up edit button handlers for intents
67 200 document.querySelectorAll('.mxchat-edit-button').forEach(button => {
68 201 button.onclick = () => {
69 202 const intentId = button.dataset.intentId;
70 203 const phrases = button.dataset.phrases;
@@ -70,8 +203,158 @@
70 203 const phrases = button.dataset.phrases;
71 204 mxchatOpenEditModal(intentId, phrases);
72 205 };
73 206 });
207 +
208 + // Set up edit button handlers for actions (new functionality)
209 + document.querySelectorAll('.mxchat-action-card .mxchat-edit-button').forEach(button => {
210 + button.onclick = () => {
211 + const actionId = button.dataset.actionId;
212 + const phrases = button.dataset.phrases;
213 + const label = button.dataset.label;
214 + const threshold = button.dataset.threshold || 85;
215 + const callbackFunction = button.dataset.callbackFunction; // Add this data attribute
216 + mxchatOpenActionModal(true, actionId, label, phrases, threshold, callbackFunction);
217 + };
218 + });
219 +
220 + // Set up add new action buttons (new functionality)
221 + const addActionBtn = document.getElementById('mxchat-add-action-btn');
222 + if (addActionBtn) {
223 + addActionBtn.onclick = () => mxchatOpenActionModal();
224 + }
225 +
226 + const createFirstAction = document.getElementById('mxchat-create-first-action');
227 + if (createFirstAction) {
228 + createFirstAction.onclick = () => mxchatOpenActionModal();
229 + }
230 +
231 + // Setup category-specific new action buttons (new functionality)
232 + document.querySelectorAll('.mxchat-new-action-button').forEach(button => {
233 + button.onclick = () => {
234 + const category = button.closest('.mxchat-new-action-card').dataset.category;
235 + const closeModal = mxchatOpenActionModal();
236 +
237 + // Pre-select the appropriate callback based on category
238 + if (category) {
239 + const callbackSelect = document.getElementById('callback_function');
240 + if (callbackSelect) {
241 + setTimeout(() => {
242 + // Map categories to default callbacks
243 + const categoryToCallback = {
244 + 'data_collection': 'mxchat_handle_form_collection',
245 + 'integrations': 'mxchat_handle_slack_message',
246 + 'custom_actions': 'mxchat_handle_custom_action',
247 + 'recommendations': 'mxchat_handle_product_recommendations'
248 + // Add more mappings as needed
249 + };
250 +
251 + if (categoryToCallback[category]) {
252 + callbackSelect.value = categoryToCallback[category];
253 + }
254 + }, 100);
255 + }
256 + }
257 + };
258 + });
259 +
260 + // Handle action toggle switches (new functionality)
261 + document.querySelectorAll('.mxchat-action-toggle').forEach(toggle => {
262 + toggle.onchange = function() {
263 + const actionId = this.dataset.actionId;
264 + const isEnabled = this.checked;
265 +
266 + // Show loading indicator
267 + const loadingEl = document.getElementById('mxchat-action-loading');
268 + if (loadingEl) loadingEl.style.display = 'flex';
269 +
270 + // Send AJAX request to update status
271 + fetch(ajaxurl, {
272 + method: 'POST',
273 + headers: {
274 + 'Content-Type': 'application/x-www-form-urlencoded',
275 + },
276 + body: new URLSearchParams({
277 + action: 'mxchat_toggle_action',
278 + intent_id: actionId,
279 + enabled: isEnabled ? 1 : 0,
280 + nonce: mxchatAdmin.toggle_action_nonce // Use the correct nonce
281 + })
282 + })
283 + .then(response => response.json())
284 + .then(data => {
285 + if (!data.success) {
286 + alert('Failed to update action status: ' + (data.data?.message || 'Unknown error'));
287 + this.checked = !isEnabled; // Revert the toggle
288 + }
289 + })
290 + .catch(error => {
291 + //console.error('Error:', error);
292 + alert('Server error. Please try again.');
293 + this.checked = !isEnabled; // Revert the toggle
294 + })
295 + .finally(() => {
296 + if (loadingEl) loadingEl.style.display = 'none';
297 + });
298 + };
299 + });
300 +
301 + // Handle threshold sliders in action cards (new functionality)
302 + document.querySelectorAll('.mxchat-threshold-slider').forEach(slider => {
303 + slider.oninput = function() {
304 + const actionId = this.id.replace('intent_threshold_', '');
305 + document.getElementById('threshold_output_' + actionId).textContent = this.value + '%';
306 + };
307 + });
308 +
309 + // Handle threshold save buttons in action cards (new functionality)
310 + document.querySelectorAll('.mxchat-threshold-save').forEach(button => {
311 + button.onclick = function(e) {
312 + e.preventDefault();
313 + const form = this.closest('form');
314 + const intentId = form.querySelector('input[name="intent_id"]').value;
315 + const threshold = form.querySelector('input[name="intent_threshold"]').value;
316 + const nonce = form.querySelector('input[name="_wpnonce"]').value;
317 +
318 + // Show loading indicator
319 + const loadingEl = document.getElementById('mxchat-action-loading');
320 + if (loadingEl) loadingEl.style.display = 'flex';
321 +
322 + // Send AJAX request
323 + fetch(ajaxurl, {
324 + method: 'POST',
325 + headers: {
326 + 'Content-Type': 'application/x-www-form-urlencoded',
327 + },
328 + body: new URLSearchParams({
329 + action: 'mxchat_update_intent_threshold',
330 + intent_id: intentId,
331 + intent_threshold: threshold,
332 + _wpnonce: nonce
333 + })
334 + })
335 + .then(response => response.json())
336 + .then(data => {
337 + if (data.success) {
338 + // Visual feedback of success
339 + const card = this.closest('.mxchat-action-card');
340 + card.style.background = 'rgba(120, 115, 245, 0.1)';
341 + setTimeout(() => {
342 + card.style.background = 'white';
343 + }, 300);
344 + } else {
345 + alert('Failed to update threshold: ' + (data.data?.message || 'Unknown error'));
346 + }
347 + })
348 + .catch(error => {
349 + //console.error('Error:', error);
350 + alert('Server error. Please try again.');
351 + })
352 + .finally(() => {
353 + if (loadingEl) loadingEl.style.display = 'none';
354 + });
355 + };
356 + });
74 357 });
75 358
76 359 jQuery(document).ready(function($) {
77 360 // Ensure we have a debounce function (use lodash if available, otherwise use our implementation)
@@ -77,11 +360,29 @@
77 360 // Ensure we have a debounce function (use lodash if available, otherwise use our implementation)
78 361 const useDebounce = (window._ && window._.debounce) ? window._.debounce : debounce;
79 362
80 363 // --- AJAX Auto-Save ---
81 - const $autosaveSections = $('.mxchat-autosave-section');
364 + let $autosaveSections = $('.mxchat-autosave-section');
365 +
366 + // *** ADD THIS: Extend auto-save sections to include Pinecone settings ***
367 + const $pineconeAutosaveSection = $('#mxchat-kb-tab-pinecone');
368 + if ($pineconeAutosaveSection.length) {
369 + $autosaveSections = $autosaveSections.add($pineconeAutosaveSection);
370 + //console.log('Added Pinecone section to auto-save monitoring');
371 + }
372 +
373 + // Track whether fields have been modified by user
374 + const userModifiedFields = new Set();
82 375
83 376 if ($autosaveSections.length) {
377 + // Track user interactions with input fields to determine if changes are user-initiated
378 + $autosaveSections.find('input, textarea, select').on('focus keydown paste', function() {
379 + const fieldName = $(this).attr('name');
380 + if (fieldName) {
381 + userModifiedFields.add(fieldName);
382 + }
383 + });
384 +
84 385 // Handle real-time range slider value updates
85 386 $autosaveSections.find('input[type="range"]').on('input', function() {
86 387 const value = $(this).val();
87 388 $('#threshold_value').text(value);
@@ -87,16 +388,43 @@
87 388 $('#threshold_value').text(value);
88 389 });
89 390
90 391 // Handle all input changes (including range slider)
91 - $autosaveSections.find('input, textarea, select').on('change', function() {
392 + $autosaveSections.find('input, textarea, select').on('change', function() {
92 393 const $field = $(this);
93 394 const name = $field.attr('name');
395 +
396 + // Skip saving for API key fields that haven't been interacted with and are empty
397 + const isApiKeyField = name && (
398 + name === 'loops_api_key' ||
399 + name === 'api_key' ||
400 + name === 'xai_api_key' ||
401 + name === 'claude_api_key' ||
402 + name === 'voyage_api_key' ||
403 + name === 'gemini_api_key' ||
404 + name === 'deepseek_api_key' ||
405 + name.indexOf('_api_key') !== -1
406 + );
407 +
408 + // Skip processing if:
409 + // 1. It's an API key field
410 + // 2. The user hasn't interacted with it
411 + // 3. The field is empty
412 + if (isApiKeyField && !userModifiedFields.has(name) && (!$field.val() || $field.val().trim() === '')) {
413 + //console.log('Skipping auto-save for untouched API key field:', name);
414 + return;
415 + }
416 +
94 417 let value;
95 418
96 419 // Handle different input types
97 420 if ($field.attr('type') === 'checkbox') {
98 - value = $field.is(':checked') ? 'on' : 'off';
421 + // *** UPDATED: Handle Pinecone checkboxes differently ***
422 + if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) {
423 + value = $field.is(':checked') ? '1' : '0';
424 + } else {
425 + value = $field.is(':checked') ? 'on' : 'off';
426 + }
99 427 } else {
100 428 value = $field.val();
101 429 }
102 430
@@ -118,12 +446,14 @@
118 446 feedbackContainer.append(spinner);
119 447
120 448 // Determine which AJAX action and nonce to use:
121 449 var ajaxAction, nonce;
122 - // Use the new AJAX action for submenu fields:
123 - if ( name.indexOf('mxchat_prompts_options') !== -1 ||
124 - name === 'mxchat_auto_sync_posts' ||
125 - name === 'mxchat_auto_sync_pages' ) {
450 + // *** UPDATED: Add Pinecone fields to prompts action ***
451 + if (name.indexOf('mxchat_prompts_options') !== -1 ||
452 + name === 'mxchat_auto_sync_posts' ||
453 + name === 'mxchat_auto_sync_pages' ||
454 + name.indexOf('mxchat_auto_sync_') === 0 ||
455 + name.indexOf('mxchat_pinecone_addon_options') !== -1) { // *** ADD THIS LINE ***
126 456 ajaxAction = 'mxchat_save_prompts_setting';
127 457 nonce = mxchatPromptsAdmin.prompts_setting_nonce;
128 458 } else {
129 459 // Otherwise, use the existing AJAX action.
@@ -130,8 +460,13 @@
130 460 ajaxAction = 'mxchat_save_setting';
131 461 nonce = mxchatAdmin.setting_nonce;
132 462 }
133 463
464 + // *** ADD THIS: Debug logging for Pinecone fields ***
465 + if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) {
466 + //console.log('Saving Pinecone field:', name, '=', value);
467 + }
468 +
134 469 // AJAX save request
135 470 $.ajax({
136 471 url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url,
137 472 type: 'POST',
@@ -141,28 +476,105 @@
141 476 value: value,
142 477 _ajax_nonce: nonce
143 478 },
144 479 success: function(response) {
145 - if (response.success) {
146 - spinner.fadeOut(200, function() {
147 - feedbackContainer.append(successIcon);
148 - successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
149 - feedbackContainer.remove();
150 - });
151 - });
152 - } else {
153 - alert('Error saving: ' + (response.data?.message || 'Unknown error'));
154 - if ($field.attr('type') === 'checkbox') {
155 - $field.prop('checked', !$field.is(':checked'));
156 - }
157 - feedbackContainer.remove();
158 - }
159 - },
160 - error: function() {
161 - alert('An error occurred while saving.');
480 + if (response.success) {
481 + spinner.fadeOut(200, function() {
482 + feedbackContainer.append(successIcon);
483 + successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
484 + feedbackContainer.remove();
485 + });
486 + });
487 +
488 + // *** ADD THIS: Update Pinecone checkbox state after successful save ***
489 + if (name && name.indexOf('mxchat_pinecone_addon_options[mxchat_use_pinecone]') !== -1) {
490 + //console.log('Pinecone toggle saved successfully, value:', value);
491 +
492 + // The checkbox state is already updated by the user interaction
493 + // But let's make sure the UI state matches the saved value
494 + var $checkbox = $('input[name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"]');
495 + var settingsDiv = $('.mxchat-pinecone-settings');
496 +
497 + // Double-check the UI state matches what was saved
498 + if (value === '1' && !$checkbox.is(':checked')) {
499 + $checkbox.prop('checked', true);
500 + settingsDiv.slideDown(300);
501 + } else if (value === '0' && $checkbox.is(':checked')) {
502 + $checkbox.prop('checked', false);
503 + settingsDiv.slideUp(300);
504 + }
505 +
506 + //console.log('Pinecone UI state synchronized');
507 +
508 + // Check if Knowledge Import tab is currently active
509 + if ($('.mxchat-kb-tab-button[data-tab="import"]').hasClass('active')) {
510 + // Show a notice that we need to refresh
511 + var $knowledgeCard = $('#mxchat-kb-tab-import .mxchat-card').eq(1);
512 + if ($knowledgeCard.length > 0) {
513 + // Add a refresh notice at the top of the knowledge base card
514 + var refreshNotice = $('<div class="notice notice-warning" style="margin: 15px 0; padding: 10px 15px;">' +
515 + '<p style="margin: 0;">' +
516 + '<span class="dashicons dashicons-info" style="color: #f0ad4e; margin-right: 5px;"></span>' +
517 + 'Database settings have changed. ' +
518 + '<a href="#" onclick="location.reload(); return false;" style="font-weight: bold;">Click here to refresh</a> to see the updated knowledge base.' +
519 + '</p></div>');
520 +
521 + $knowledgeCard.prepend(refreshNotice);
522 + }
523 + } else {
524 + // If not on import tab, set a flag to refresh when they go there
525 + sessionStorage.setItem('mxchat_pinecone_changed', 'true');
526 + }
527 + }
528 +
529 + // *** ADD THIS: Debug logging for successful saves ***
530 + if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) {
531 + //console.log('Pinecone field saved successfully:', name, '=', value);
532 + }
533 +
534 + // Check if the response contains a "no changes" message and log it
535 + if (response.data && response.data.message === 'No changes detected') {
536 + //console.log('No changes detected for field:', name);
537 + }
538 + } else {
539 + // Only show alert for actual errors, not for "no changes"
540 + let errorMessage = response.data?.message || 'Unknown error';
541 +
542 + // Don't display an alert for "no changes" message
543 + if (errorMessage !== 'No changes detected' && errorMessage !== 'Update failed or no changes') {
544 + alert('Error saving: ' + errorMessage);
545 + } else {
546 + // Still provide visual feedback that no changes were needed
547 + spinner.fadeOut(200, function() {
548 + feedbackContainer.append(successIcon);
549 + successIcon.fadeIn(200).delay(1000).fadeOut(200, function() {
550 + feedbackContainer.remove();
551 + });
552 + });
553 + //console.log('No changes detected for field:', name);
554 + return;
555 + }
556 +
557 + // Only revert checkbox state if it was an actual error
558 + if (errorMessage !== 'No changes detected' && errorMessage !== 'Update failed or no changes') {
559 + if ($field.attr('type') === 'checkbox') {
560 + $field.prop('checked', !$field.is(':checked'));
561 + }
562 + }
563 +
564 + // Always clean up the feedback container
565 + feedbackContainer.remove();
566 + }
567 +},
568 + error: function(xhr, textStatus, error) {
569 + //console.error('AJAX Error:', textStatus, error);
570 + alert('An error occurred while saving. Please try again.');
571 +
572 + // Revert checkbox state on error
162 573 if ($field.attr('type') === 'checkbox') {
163 574 $field.prop('checked', !$field.is(':checked'));
164 575 }
576 +
165 577 feedbackContainer.remove();
166 578 }
167 579 });
168 580 });
@@ -174,9 +586,9 @@
174 586 $(this).wpColorPicker({
175 587 change: useDebounce(function(event, ui) {
176 588 // Safety check - ensure we have a valid field and value
177 589 if (!$colorField || !$colorField.val()) {
178 - console.warn('Color picker not ready');
590 + //console.warn('Color picker not ready');
179 591 return;
180 592 }
181 593
182 594 const name = $colorField.attr('name');
@@ -182,9 +594,9 @@
182 594 const name = $colorField.attr('name');
183 595 const value = $colorField.val();
184 596
185 597 if (!name || !value) {
186 - console.warn('Missing required color picker values');
598 + //console.warn('Missing required color picker values');
187 599 return;
188 600 }
189 601
190 602 // Create feedback container
@@ -195,20 +607,22 @@
195 607 // Position feedback container
196 608 $colorField.closest('.wp-picker-container').after(feedbackContainer);
197 609 feedbackContainer.append(spinner);
198 610
199 - // Determine AJAX action and nonce for color fields:
611 + // Determine which AJAX action and nonce to use:
200 612 var ajaxAction, nonce;
201 - if ( name.indexOf('mxchat_prompts_options') !== -1 ||
202 - name === 'mxchat_auto_sync_posts' ||
203 - name === 'mxchat_auto_sync_pages' ) {
613 + // Use the new AJAX action for submenu fields:
614 + if (name.indexOf('mxchat_prompts_options') !== -1 ||
615 + name === 'mxchat_auto_sync_posts' ||
616 + name === 'mxchat_auto_sync_pages' ||
617 + name.indexOf('mxchat_auto_sync_') === 0) { // Modified to catch all auto-sync fields
204 618 ajaxAction = 'mxchat_save_prompts_setting';
205 619 nonce = mxchatPromptsAdmin.prompts_setting_nonce;
206 620 } else {
621 + // Otherwise, use the existing AJAX action.
207 622 ajaxAction = 'mxchat_save_setting';
208 623 nonce = mxchatAdmin.setting_nonce;
209 624 }
210 -
211 625 // AJAX save request
212 626 $.ajax({
213 627 url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url,
214 628 type: 'POST',
@@ -240,9 +654,9 @@
240 654 });
241 655 });
242 656
243 657 // Reinitialize color pickers when switching tabs
244 - $('.mxchat-nav-tab').on('click.mxchat', function() {
658 + $('.mxchat-tab-button').on('click.mxchat', function() {
245 659 setTimeout(function() {
246 660 $('.my-color-field:visible').wpColorPicker('close');
247 661 }, 100);
248 662 });
@@ -247,107 +661,606 @@
247 661 }, 100);
248 662 });
249 663 }
250 664
251 - // Initialize tabs system
252 - function initTabs() {
253 - // Remove any existing handlers first
254 - $('.mxchat-nav-tab').off('click.mxchat');
665 +// Initialize tabs system
666 +function initTabs() {
667 + // Remove any existing handlers first
668 + $('.mxchat-tab-button').off('click.mxchat');
669 +
670 + // Add new click handlers
671 + $('.mxchat-tab-button').on('click.mxchat', function(e) {
672 + e.preventDefault();
673 + e.stopPropagation();
255 674
256 - // Add new click handlers
257 - $('.mxchat-nav-tab').on('click.mxchat', function(e) {
258 - e.preventDefault();
259 - e.stopPropagation();
260 -
261 - var $this = $(this);
262 -
263 - // Get tab ID - try href first, fallback to data-tab, then to default
264 - var tabId = $this.attr('href');
265 - if (tabId) {
266 - tabId = tabId.replace('#', '');
267 - } else {
268 - tabId = $this.data('tab') || 'chatbot';
675 + var $this = $(this);
676 +
677 + // Get tab ID from data-tab attribute
678 + var tabId = $this.data('tab') || 'chatbot';
679 +
680 + // Safety check for empty tabId
681 + if (!tabId) {
682 + //console.warn('No tab identifier found');
683 + return;
684 + }
685 +
686 + // Update tab buttons
687 + $('.mxchat-tab-button').removeClass('active');
688 + $this.addClass('active');
689 +
690 + // Update content areas - with safety check
691 + $('.mxchat-tab-content').removeClass('active');
692 + var $targetTab = $('#' + tabId);
693 + if ($targetTab.length) {
694 + $targetTab.addClass('active');
695 + // Removed localStorage saving functionality
696 + } else {
697 + //console.warn('Tab content #' + tabId + ' not found');
698 + }
699 + });
700 +}
701 +
702 +// Initialize tabs and handle events
703 +initTabs();
704 +$(document).on('widget-added widget-updated postbox-toggled', initTabs);
705 +
706 +// Always activate the first tab (Chatbot)
707 +$('.mxchat-tab-button').first().trigger('click.mxchat');
708 +
709 + // Attach edit modal event handler
710 + $(document).on('click', '.mxchat-edit-button', function() {
711 + const intentId = $(this).data('intent-id');
712 + const phrases = $(this).data('phrases');
713 + mxchatOpenEditModal(intentId, phrases);
714 + });
715 +
716 +// Toggle visibility handlers
717 +function toggleVisibility(selector) {
718 + $(selector).on('click', function() {
719 + var inputField = $(this).prev('input');
720 + if (inputField.attr('type') === 'password') {
721 + inputField.attr('type', 'text');
722 + $(this).text('Hide');
723 + } else {
724 + inputField.attr('type', 'password');
725 + $(this).text('Show');
726 + }
727 + });
728 +}
729 +
730 +// Initialize all toggle visibility buttons
731 +[
732 + '#toggleApiKeyVisibility',
733 + '#toggleWooCommerceSecretVisibility',
734 + '#toggleVoyageAPIKeyVisibility',
735 + '#toggleLoopsApiKeyVisibility',
736 + '#toggleXaiApiKeyVisibility',
737 + '#toggleClaudeApiKeyVisibility',
738 + '#toggleBraveApiKeyVisibility',
739 + '#toggleWebhookUrlVisibility',
740 + '#toggleSecretKeyVisibility',
741 + '#toggleBotTokenVisibility',
742 + '#toggleDeepSeekApiKeyVisibility',
743 + '#toggleGeminiApiKeyVisibility' // Added Gemini toggle
744 +].forEach(toggleVisibility);
745 +
746 +// Handle API key visibility based on model selection
747 +function setupAPIKeyVisibility() {
748 + // Cache the selectors
749 + const $chatModelSelect = $('#model');
750 + const $embeddingModelSelect = $('#embedding_model');
751 +
752 + // First, locate and mark the API key rows
753 + setupAPIKeyRows();
754 +
755 + // Initial setup based on current selections
756 + updateApiKeyVisibility();
757 +
758 + // Listen for changes to the model selectors
759 + $chatModelSelect.on('change', updateApiKeyVisibility);
760 + $embeddingModelSelect.on('change', updateApiKeyVisibility);
761 +
762 + /**
763 + * Locate and mark rows that contain API key fields
764 + */
765 + function setupAPIKeyRows() {
766 + // Find key rows by their field IDs
767 + const providerMap = {
768 + 'api_key': 'openai',
769 + 'xai_api_key': 'xai',
770 + 'claude_api_key': 'claude',
771 + 'deepseek_api_key': 'deepseek',
772 + 'voyage_api_key': 'voyage',
773 + 'gemini_api_key': 'gemini' // Added Gemini API key mapping
774 + };
775 +
776 + $.each(providerMap, function(fieldId, provider) {
777 + const $field = $('#' + fieldId);
778 + if ($field.length) {
779 + const $row = $field.closest('tr');
780 + $row.addClass('mxchat-setting-row');
781 + $row.attr('data-provider', provider);
269 782 }
270 -
271 - // Safety check for empty tabId
272 - if (!tabId) {
273 - console.warn('No tab identifier found');
274 - return;
783 + });
784 + }
785 +
786 +/**
787 + * Updates the visibility of API key fields based on current model selections
788 + */
789 +function updateApiKeyVisibility() {
790 + const chatModel = $chatModelSelect.val();
791 + const embeddingModel = $embeddingModelSelect.val();
792 +
793 + // Determine which providers are needed
794 + const isOpenAIChat = chatModel && chatModel.startsWith('gpt-');
795 + const isXAI = chatModel && chatModel.startsWith('grok-');
796 + const isClaude = chatModel && chatModel.startsWith('claude-');
797 + const isDeepSeek = chatModel && chatModel.startsWith('deepseek-');
798 + const isGemini = chatModel && chatModel.startsWith('gemini-'); // Added Gemini detection
799 +
800 + const isOpenAIEmbedding = embeddingModel && embeddingModel.startsWith('text-embedding-');
801 + const isVoyage = embeddingModel && embeddingModel.startsWith('voyage-');
802 + const isGeminiEmbedding = embeddingModel && embeddingModel.startsWith('gemini-embedding-');
803 +
804 + // Update API key visibility for each provider
805 + updateWrapperVisibility('openai', isOpenAIChat || isOpenAIEmbedding);
806 + updateWrapperVisibility('xai', isXAI);
807 + updateWrapperVisibility('claude', isClaude);
808 + updateWrapperVisibility('deepseek', isDeepSeek);
809 + updateWrapperVisibility('voyage', isVoyage);
810 + updateWrapperVisibility('gemini', isGemini || isGeminiEmbedding); // Updated Gemini visibility for both chat and embedding
811 +
812 + // Update provider-specific notices for OpenAI
813 + if (isOpenAIChat && isOpenAIEmbedding) {
814 + $('div[data-provider="openai"] .api-key-notice').text(
815 + 'Required for your selected chat model and embedding model. Important: You must add credits before use.'
816 + );
817 + } else if (isOpenAIChat) {
818 + $('div[data-provider="openai"] .api-key-notice').text(
819 + 'Required for your selected chat model. Important: You must add credits before use.'
820 + );
821 + } else if (isOpenAIEmbedding) {
822 + $('div[data-provider="openai"] .api-key-notice').text(
823 + 'Required for your selected embedding model. Important: You must add credits before use.'
824 + );
825 + }
826 +
827 + // Update provider-specific notices for Gemini
828 + if (isGemini && isGeminiEmbedding) {
829 + $('div[data-provider="gemini"] .api-key-notice').text(
830 + 'Required for your selected chat model and embedding model.'
831 + );
832 + } else if (isGemini) {
833 + $('div[data-provider="gemini"] .api-key-notice').text(
834 + 'Required for your selected chat model.'
835 + );
836 + } else if (isGeminiEmbedding) {
837 + $('div[data-provider="gemini"] .api-key-notice').text(
838 + 'Required for your selected embedding model.'
839 + );
840 + }
841 +}
842 +
843 + /**
844 + * Updates visibility of a specific provider's API key wrapper
845 + */
846 + function updateWrapperVisibility(provider, isVisible) {
847 + const $row = $('tr.mxchat-setting-row[data-provider="' + provider + '"]');
848 +
849 + if (!$row.length) {
850 + //console.warn('API key row not found for provider: ' + provider);
851 + return;
852 + }
853 +
854 + if (isVisible) {
855 + $row.show();
856 + if (!$row.hasClass('highlighted')) {
857 + $row.addClass('highlighted');
858 + setTimeout(() => {
859 + $row.removeClass('highlighted');
860 + }, 1500);
275 861 }
276 -
277 - // Update tabs
278 - $('.mxchat-nav-tab').removeClass('mxchat-nav-tab-active');
279 - $this.addClass('mxchat-nav-tab-active');
280 -
281 - // Update content areas - with safety check
282 - $('.mxchat-tab-content').removeClass('active').hide();
283 - var $targetTab = $('#' + tabId);
284 - if ($targetTab.length) {
285 - $targetTab.addClass('active').show();
286 -
287 - // Store active tab
288 - try {
289 - localStorage.setItem('mxchat_active_tab', tabId);
290 - } catch (e) {
291 - console.warn('LocalStorage not available:', e);
292 - }
293 - } else {
294 - console.warn('Tab content #' + tabId + ' not found');
862 + } else {
863 + $row.hide();
864 + }
865 + }
866 +}
867 +
868 +// Add this to your JavaScript file
869 +function setupMxChatModelSelector() {
870 + const $modelSelect = $('#model');
871 + const $modelSelectorButton = $('<button>', {
872 + type: 'button',
873 + id: 'mxchat_model_selector_btn',
874 + class: 'button-primary mxchat-model-selector-btn',
875 + text: 'Select AI Model'
876 + });
877 +
878 + // Replace the select dropdown with a button
879 + $modelSelect.hide().after($modelSelectorButton);
880 +
881 + // Update button text to show currently selected model
882 + function updateButtonText() {
883 + const selectedModel = $modelSelect.val();
884 + const selectedModelText = $modelSelect.find('option:selected').text();
885 + $modelSelectorButton.text(selectedModelText);
886 + }
887 +
888 + // Initialize button text
889 + updateButtonText();
890 +
891 + // Create and append modal HTML
892 + const modelSelectorModal = `
893 + <div id="mxchat_model_selector_modal" class="mxchat-model-selector-modal">
894 + <div class="mxchat-model-selector-modal-content">
895 + <div class="mxchat-model-selector-modal-header">
896 + <h3>Select AI Model</h3>
897 + <span class="mxchat-model-selector-modal-close">&times;</span>
898 + </div>
899 + <div class="mxchat-model-selector-modal-body">
900 + <div class="mxchat-model-selector-search-container">
901 + <input type="text" id="mxchat_model_search_input" class="mxchat-model-search-input" placeholder="Search models...">
902 + </div>
903 + <div class="mxchat-model-selector-categories">
904 + <button class="mxchat-model-category-btn active" data-category="all">All</button>
905 + <button class="mxchat-model-category-btn" data-category="gemini">Google Gemini</button>
906 + <button class="mxchat-model-category-btn" data-category="openai">OpenAI</button>
907 + <button class="mxchat-model-category-btn" data-category="claude">Claude</button>
908 + <button class="mxchat-model-category-btn" data-category="xai">X.AI</button>
909 + <button class="mxchat-model-category-btn" data-category="deepseek">DeepSeek</button>
910 + </div>
911 + <div class="mxchat-model-selector-grid" id="mxchat_models_grid"></div>
912 + </div>
913 + <div class="mxchat-model-selector-modal-footer">
914 + <button id="mxchat_cancel_model_selection" class="button mxchat-model-cancel-btn">Cancel</button>
915 + </div>
916 + </div>
917 + </div>
918 + `;
919 +
920 + $('body').append(modelSelectorModal);
921 +
922 + // Populate models grid
923 + function populateModelsGrid(filter = '', category = 'all') {
924 + const $grid = $('#mxchat_models_grid');
925 + $grid.empty();
926 +
927 + const models = {
928 + gemini: [
929 + { value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash', description: 'Next-Gen features, speed & multimodal generation' },
930 + { value: 'gemini-2.0-flash-lite', label: 'Gemini 2.0 Flash-Lite', description: 'Cost-efficient with low latency' },
931 + { value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro', description: 'Complex reasoning tasks requiring more intelligence' },
932 + { value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash', description: 'Fast and versatile performance' },
933 + ],
934 + openai: [
935 + { value: 'gpt-4.1-2025-04-14', label: 'GPT-4.1', description: 'Flagship model for complex tasks' },
936 + { value: 'gpt-4o', label: 'GPT-4o', description: 'Recommended for most use cases' },
937 + { value: 'gpt-4o-mini', label: 'GPT-4o Mini', description: 'Fast and lightweight' },
938 + { value: 'gpt-4-turbo', label: 'GPT-4 Turbo', description: 'High-performance model' },
939 + { value: 'gpt-4', label: 'GPT-4', description: 'High intelligence model' },
940 + { value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', description: 'Affordable and fast' },
941 + ],
942 + claude: [
943 + { value: 'claude-opus-4-20250514', label: 'Claude 4 Opus', description: 'Most capable Claude model' },
944 + { value: 'claude-sonnet-4-20250514', label: 'Claude 4 Sonnet', description: 'High performance' },
945 + { value: 'claude-3-7-sonnet-20250219', label: 'Claude 3.7 Sonnet', description: 'High intelligence' },
946 + { value: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet', description: 'Intelligent and balanced' },
947 + { value: 'claude-3-opus-20240229', label: 'Claude 3 Opus', description: 'Highly complex tasks' },
948 + { value: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet', description: 'Balanced performance' },
949 + { value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', description: 'Fastest Claude model' },
950 + ],
951 + xai: [
952 + { value: 'grok-3-beta', label: 'Grok-3', description: 'Powerful model with 131K context' },
953 + { value: 'grok-3-fast-beta', label: 'Grok-3 Fast', description: 'High performance with faster responses' },
954 + { value: 'grok-3-mini-beta', label: 'Grok-3 Mini', description: 'Affordable model with good performance' },
955 + { value: 'grok-3-mini-fast-beta', label: 'Grok-3 Mini Fast', description: 'Quick and cost-effective' },
956 + { value: 'grok-2', label: 'Grok 2', description: 'Latest X.AI model' },
957 + ],
958 + deepseek: [
959 + { value: 'deepseek-chat', label: 'DeepSeek-V3', description: 'Advanced AI assistant' },
960 + ],
961 + };
962 +
963 + let allModels = [];
964 + Object.keys(models).forEach(key => {
965 + if (category === 'all' || category === key) {
966 + allModels = allModels.concat(models[key]);
295 967 }
296 968 });
969 +
970 + // Filter by search term if present
971 + if (filter) {
972 + const lowerFilter = filter.toLowerCase();
973 + allModels = allModels.filter(model =>
974 + model.label.toLowerCase().includes(lowerFilter) ||
975 + model.description.toLowerCase().includes(lowerFilter)
976 + );
977 + }
978 +
979 + // Create model cards
980 + allModels.forEach(model => {
981 + const isSelected = $modelSelect.val() === model.value;
982 + const $modelCard = $(`
983 + <div class="mxchat-model-selector-card ${isSelected ? 'mxchat-model-selected' : ''}" data-value="${model.value}">
984 + <div class="mxchat-model-selector-icon">${getModelIcon(model.value)}</div>
985 + <div class="mxchat-model-selector-info">
986 + <h4 class="mxchat-model-selector-title">${model.label}</h4>
987 + <p class="mxchat-model-selector-description">${model.description}</p>
988 + </div>
989 + ${isSelected ? '<div class="mxchat-model-selector-checkmark">✓</div>' : ''}
990 + </div>
991 + `);
992 + $grid.append($modelCard);
993 + });
297 994 }
298 995
299 - // Initialize tabs and handle events
300 - initTabs();
301 - $(document).on('widget-added widget-updated postbox-toggled', initTabs);
996 +// Helper function to get icon for each model
997 +function getModelIcon(modelValue) {
998 + if (modelValue.startsWith('gemini-')) return '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" class="mxchat-model-icon-gemini"><defs><path id="a" d="M44.5 20H24v8.5h11.8C34.7 33.9 30.1 37 24 37c-7.2 0-13-5.8-13-13s5.8-13 13-13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 11.8 2 2 11.8 2 24s9.8 22 22 22c11 0 21-8 21-22 0-1.3-.2-2.7-.5-4z"></path></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"></use></clipPath><path clip-path="url(#b)" fill="#FBBC05" d="M0 37V11l17 13z"></path><path clip-path="url(#b)" fill="#EA4335" d="M0 11l17 13 7-6.1L48 14V0H0z"></path><path clip-path="url(#b)" fill="#34A853" d="M0 37l30-23 7.9 1L48 0v48H0z"></path><path clip-path="url(#b)" fill="#4285F4" d="M48 48L17 24l-4-3 35-10z"></path></svg>';
999 + if (modelValue.startsWith('gpt-')) return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" class="mxchat-model-icon-openai"><path fill="currentColor" d="M297 131a80.6 80.6 0 0 0-93.7-104.2 80.6 80.6 0 0 0-137 29A80.6 80.6 0 0 0 23 189a80.6 80.6 0 0 0 93.7 104.2 80.6 80.6 0 0 0 137-29A80.7 80.7 0 0 0 297.1 131zM176.9 299c-14 .1-27.6-4.8-38.4-13.8l1.9-1 63.7-36.9c3.3-1.8 5.3-5.3 5.2-9v-89.9l27 15.6c.3.1.4.4.5.7v74.4a60 60 0 0 1-60 60zM47.9 244a59.7 59.7 0 0 1-7.1-40.1l1.9 1.1 63.7 36.8c3.2 1.9 7.2 1.9 10.5 0l77.8-45V228c0 .3-.2.6-.4.8L129.9 266a60 60 0 0 1-82-22zM31.2 105c7-12.2 18-21.5 31.2-26.3v75.8c0 3.7 2 7.2 5.2 9l77.8 45-27 15.5a1 1 0 0 1-.9 0L53.1 187a60 60 0 0 1-22-82zm221.2 51.5-77.8-45 27-15.5a1 1 0 0 1 .9 0l64.4 37.1a60 60 0 0 1-9.3 108.2v-75.8c0-3.7-2-7.2-5.2-9zm26.8-40.4-1.9-1.1-63.7-36.8a10.4 10.4 0 0 0-10.5 0L125.4 123V92c0-.3 0-.6.3-.8L190.1 54a60 60 0 0 1 89.1 62.1zm-168.5 55.4-27-15.5a1 1 0 0 1-.4-.7V80.9a60 60 0 0 1 98.3-46.1l-1.9 1L116 72.8a10.3 10.3 0 0 0-5.2 9v89.8zm14.6-31.5 34.7-20 34.6 20v40L160 200l-34.7-20z"></path></svg>';
1000 + if (modelValue.startsWith('claude-')) return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 176" fill="none" class="mxchat-model-icon-claude"><path fill="currentColor" d="m147.487 0l70.081 175.78H256L185.919 0zM66.183 106.221l23.98-61.774l23.98 61.774zM70.07 0L0 175.78h39.18l14.33-36.914h73.308l14.328 36.914h39.179L110.255 0z"></path></svg>';
1001 + if (modelValue.startsWith('grok-')) return '<svg fill="currentColor" fill-rule="evenodd" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="mxchat-model-icon-xai"><path d="M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z"></path></svg>';
1002 + if (modelValue.startsWith('deepseek-')) return '<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg" class="mxchat-model-icon-deepseek"><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="currentColor"></path></svg>';
1003 + return '<span class="dashicons dashicons-admin-generic mxchat-model-icon-generic"></span>';
1004 +}
302 1005
303 - // Activate initial tab
304 - try {
305 - var savedTab = localStorage.getItem('mxchat_active_tab');
306 - if (savedTab && $('#' + savedTab).length > 0) {
307 - $('.mxchat-nav-tab[href="#' + savedTab + '"]').trigger('click.mxchat');
308 - } else {
309 - $('.mxchat-nav-tab').first().trigger('click.mxchat');
1006 + // Event handlers
1007 + $modelSelectorButton.on('click', function() {
1008 + $('#mxchat_model_selector_modal').show();
1009 + populateModelsGrid('', 'all');
1010 + });
1011 +
1012 + $('.mxchat-model-selector-modal-close, #mxchat_cancel_model_selection').on('click', function() {
1013 + $('#mxchat_model_selector_modal').hide();
1014 + });
1015 +
1016 + $('.mxchat-model-category-btn').on('click', function() {
1017 + $('.mxchat-model-category-btn').removeClass('active');
1018 + $(this).addClass('active');
1019 + const category = $(this).data('category');
1020 + const searchTerm = $('#mxchat_model_search_input').val();
1021 + populateModelsGrid(searchTerm, category);
1022 + });
1023 +
1024 + $('#mxchat_model_search_input').on('input', function() {
1025 + const searchTerm = $(this).val();
1026 + const activeCategory = $('.mxchat-model-category-btn.active').data('category');
1027 + populateModelsGrid(searchTerm, activeCategory);
1028 + });
1029 +
1030 + $(document).on('click', '.mxchat-model-selector-card', function() {
1031 + const modelValue = $(this).data('value');
1032 + $modelSelect.val(modelValue).trigger('change');
1033 + updateButtonText();
1034 + $('#mxchat_model_selector_modal').hide();
1035 + });
1036 +
1037 + // Close modal when clicking outside
1038 + $(window).on('click', function(event) {
1039 + if ($(event.target).is('#mxchat_model_selector_modal')) {
1040 + $('#mxchat_model_selector_modal').hide();
310 1041 }
311 - } catch (e) {
312 - $('.mxchat-nav-tab').first().trigger('click.mxchat');
1042 + });
1043 +}
1044 +
1045 +// Embedding model selector - completely separate from chat model selector
1046 +function setupMxChatEmbeddingModelSelector() {
1047 + const $embeddingModelSelect = $('#embedding_model');
1048 +
1049 + // Skip if the element doesn't exist on the page
1050 + if ($embeddingModelSelect.length === 0) {
1051 + return;
313 1052 }
314 1053
315 - // Attach edit modal event handler
316 - $(document).on('click', '.mxchat-edit-button', function() {
317 - const intentId = $(this).data('intent-id');
318 - const phrases = $(this).data('phrases');
319 - mxchatOpenEditModal(intentId, phrases);
1054 + const $embeddingModelSelectorButton = $('<button>', {
1055 + type: 'button',
1056 + id: 'mxchat_embedding_model_selector_btn',
1057 + class: 'button-primary mxchat-embedding-model-selector-btn', // Changed class name to be more specific
1058 + text: 'Select Embedding Model'
320 1059 });
321 1060
322 - // Toggle visibility handlers
323 - function toggleVisibility(selector) {
324 - $(selector).on('click', function() {
325 - var inputField = $(this).prev('input');
326 - if (inputField.attr('type') === 'password') {
327 - inputField.attr('type', 'text');
328 - $(this).text('Hide');
1061 + // Replace the select dropdown with a button
1062 + $embeddingModelSelect.hide().after($embeddingModelSelectorButton);
1063 +
1064 + // Update button text to show currently selected model
1065 + function updateButtonText() {
1066 + const selectedModel = $embeddingModelSelect.val();
1067 + const selectedModelText = $embeddingModelSelect.find('option:selected').text();
1068 + $embeddingModelSelectorButton.text(selectedModelText);
1069 + }
1070 +
1071 + // Initialize button text
1072 + updateButtonText();
1073 +
1074 + // Create a unique ID for the modal to avoid conflicts
1075 + const embeddingModalId = 'mxchat_embedding_model_selector_modal';
1076 +
1077 + // Create and append modal HTML with unique IDs
1078 + const embeddingModelSelectorModal = `
1079 + <div id="${embeddingModalId}" class="mxchat-embedding-model-selector-modal">
1080 + <div class="mxchat-embedding-model-selector-modal-content">
1081 + <div class="mxchat-embedding-model-selector-modal-header">
1082 + <h3>Select Embedding Model</h3>
1083 + <span class="mxchat-embedding-model-selector-modal-close">&times;</span>
1084 + </div>
1085 + <div class="mxchat-embedding-model-selector-modal-body">
1086 + <div class="mxchat-embedding-model-selector-search-container">
1087 + <input type="text" id="mxchat_embedding_model_search_input" class="mxchat-embedding-model-search-input" placeholder="Search models...">
1088 + </div>
1089 + <div class="mxchat-embedding-model-selector-categories">
1090 + <button class="mxchat-embedding-model-category-btn active" data-category="all">All</button>
1091 + <button class="mxchat-embedding-model-category-btn" data-category="openai">OpenAI</button>
1092 + <button class="mxchat-embedding-model-category-btn" data-category="voyage">Voyage AI</button>
1093 + <button class="mxchat-embedding-model-category-btn" data-category="gemini">Google Gemini</button>
1094 + </div>
1095 + <div class="mxchat-embedding-model-selector-grid" id="mxchat_embedding_models_grid"></div>
1096 + </div>
1097 + <div class="mxchat-embedding-model-selector-modal-footer">
1098 + <button id="mxchat_cancel_embedding_model_selection" class="button mxchat-embedding-model-cancel-btn">Cancel</button>
1099 + </div>
1100 + </div>
1101 + </div>
1102 + `;
1103 +
1104 + // Use jQuery's append to ensure it doesn't clash with existing modals
1105 + $('body').append(embeddingModelSelectorModal);
1106 +
1107 + // Populate models grid
1108 + function populateEmbeddingModelsGrid(filter = '', category = 'all') {
1109 + const $grid = $('#mxchat_embedding_models_grid');
1110 + $grid.empty();
1111 +
1112 + // Define embedding models with descriptions and context lengths
1113 + const models = {
1114 + openai: [
1115 + {
1116 + value: 'text-embedding-3-small',
1117 + label: 'TE3 Small',
1118 + description: 'Fast and cost-effective embeddings (1536 dimensions, 8K context)'
1119 + },
1120 + {
1121 + value: 'text-embedding-ada-002',
1122 + label: 'Ada 2',
1123 + description: 'Balanced performance embeddings (1536 dimensions, 8K context)'
1124 + },
1125 + {
1126 + value: 'text-embedding-3-large',
1127 + label: 'TE3 Large',
1128 + description: 'High-performance embeddings (3072 dimensions, 8K context)'
1129 + }
1130 + ],
1131 + voyage: [
1132 + {
1133 + value: 'voyage-3-large',
1134 + label: 'Voyage-3 Large',
1135 + description: 'Advanced semantic search embeddings (2048 dimensions, 32K context)'
1136 + }
1137 + ],
1138 + gemini: [
1139 + {
1140 + value: 'gemini-embedding-exp-03-07',
1141 + label: 'Gemini Embedding',
1142 + description: 'Experimental SOTA embeddings (1536 dimensions, 8K context)'
1143 + }
1144 + ]
1145 + };
1146 +
1147 + let allModels = [];
1148 + Object.keys(models).forEach(key => {
1149 + if (category === 'all' || category === key) {
1150 + allModels = allModels.concat(models[key]);
1151 + }
1152 + });
1153 +
1154 + // Filter by search term if present
1155 + if (filter) {
1156 + const lowerFilter = filter.toLowerCase();
1157 + allModels = allModels.filter(model =>
1158 + model.label.toLowerCase().includes(lowerFilter) ||
1159 + model.description.toLowerCase().includes(lowerFilter)
1160 + );
1161 + }
1162 +
1163 + // Create model cards
1164 + allModels.forEach(model => {
1165 + const isSelected = $embeddingModelSelect.val() === model.value;
1166 + let providerClass = 'mxchat-embedding-model-provider-openai';
1167 +
1168 + if (model.value.startsWith('voyage-')) {
1169 + providerClass = 'mxchat-embedding-model-provider-voyage';
1170 + } else if (model.value.startsWith('gemini-embedding-')) {
1171 + providerClass = 'mxchat-embedding-model-provider-gemini';
1172 + }
1173 +
1174 + let iconHTML = '';
1175 + if (model.value.startsWith('voyage-')) {
1176 + iconHTML = '<span class="dashicons dashicons-chart-line mxchat-embedding-model-icon-voyage"></span>';
1177 + } else if (model.value.startsWith('gemini-embedding-')) {
1178 + iconHTML = '<span class="dashicons dashicons-google mxchat-embedding-model-icon-gemini"></span>';
329 1179 } else {
330 - inputField.attr('type', 'password');
331 - $(this).text('Show');
1180 + iconHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" class="mxchat-embedding-model-icon-openai"><path fill="currentColor" d="M297 131a80.6 80.6 0 0 0-93.7-104.2 80.6 80.6 0 0 0-137 29A80.6 80.6 0 0 0 23 189a80.6 80.6 0 0 0 93.7 104.2 80.6 80.6 0 0 0 137-29A80.7 80.7 0 0 0 297.1 131zM176.9 299c-14 .1-27.6-4.8-38.4-13.8l1.9-1 63.7-36.9c3.3-1.8 5.3-5.3 5.2-9v-89.9l27 15.6c.3.1.4.4.5.7v74.4a60 60 0 0 1-60 60zM47.9 244a59.7 59.7 0 0 1-7.1-40.1l1.9 1.1 63.7 36.8c3.2 1.9 7.2 1.9 10.5 0l77.8-45V228c0 .3-.2.6-.4.8L129.9 266a60 60 0 0 1-82-22zM31.2 105c7-12.2 18-21.5 31.2-26.3v75.8c0 3.7 2 7.2 5.2 9l77.8 45-27 15.5a1 1 0 0 1-.9 0L53.1 187a60 60 0 0 1-22-82zm221.2 51.5-77.8-45 27-15.5a1 1 0 0 1 .9 0l64.4 37.1a60 60 0 0 1-9.3 108.2v-75.8c0-3.7-2-7.2-5.2-9zm26.8-40.4-1.9-1.1-63.7-36.8a10.4 10.4 0 0 0-10.5 0L125.4 123V92c0-.3 0-.6.3-.8L190.1 54a60 60 0 0 1 89.1 62.1zm-168.5 55.4-27-15.5a1 1 0 0 1-.4-.7V80.9a60 60 0 0 1 98.3-46.1l-1.9 1L116 72.8a10.3 10.3 0 0 0-5.2 9v89.8zm14.6-31.5 34.7-20 34.6 20v40L160 200l-34.7-20z"></path></svg>';
332 1181 }
1182 +
1183 + const $modelCard = $(`
1184 + <div class="mxchat-embedding-model-selector-card ${isSelected ? 'mxchat-embedding-model-selected' : ''} ${providerClass}" data-value="${model.value}">
1185 + <div class="mxchat-embedding-model-selector-icon">
1186 + ${iconHTML}
1187 + </div>
1188 + <div class="mxchat-embedding-model-selector-info">
1189 + <h4 class="mxchat-embedding-model-selector-title">${model.label}</h4>
1190 + <p class="mxchat-embedding-model-selector-description">${model.description}</p>
1191 + </div>
1192 + ${isSelected ? '<div class="mxchat-embedding-model-selector-checkmark">✓</div>' : ''}
1193 + </div>
1194 + `);
1195 +
1196 + $grid.append($modelCard);
333 1197 });
334 1198 }
335 1199
336 - // Initialize all toggle visibility buttons
337 - [
338 - '#toggleApiKeyVisibility',
339 - '#toggleWooCommerceSecretVisibility',
340 - '#toggleLoopsApiKeyVisibility',
341 - '#toggleXaiApiKeyVisibility',
342 - '#toggleClaudeApiKeyVisibility',
343 - '#toggleBraveApiKeyVisibility',
344 - '#toggleWebhookUrlVisibility',
345 - '#toggleSecretKeyVisibility',
346 - '#toggleBotTokenVisibility',
347 - '#toggleDeepSeekApiKeyVisibility'
348 - ].forEach(toggleVisibility);
1200 + // Event handlers - use namespaced events to avoid conflicts
1201 + $embeddingModelSelectorButton.on('click.embeddingModelSelector', function(e) {
1202 + e.stopPropagation(); // Prevent event bubbling
1203 + $('#' + embeddingModalId).show();
1204 + populateEmbeddingModelsGrid('', 'all');
1205 + });
349 1206
1207 + $('.mxchat-embedding-model-selector-modal-close, #mxchat_cancel_embedding_model_selection').on('click.embeddingModelSelector', function(e) {
1208 + e.stopPropagation(); // Prevent event bubbling
1209 + $('#' + embeddingModalId).hide();
1210 + });
1211 +
1212 + $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn').on('click.embeddingModelSelector', function(e) {
1213 + e.stopPropagation(); // Prevent event bubbling
1214 + $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn').removeClass('active');
1215 + $(this).addClass('active');
1216 + const category = $(this).data('category');
1217 + const searchTerm = $('#mxchat_embedding_model_search_input').val();
1218 + populateEmbeddingModelsGrid(searchTerm, category);
1219 + });
1220 +
1221 + $('#mxchat_embedding_model_search_input').on('input.embeddingModelSelector', function() {
1222 + const searchTerm = $(this).val();
1223 + const activeCategory = $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn.active').data('category');
1224 + populateEmbeddingModelsGrid(searchTerm, activeCategory);
1225 + });
1226 +
1227 + // Use a direct selector to avoid conflicts with other card elements
1228 + $(document).on('click.embeddingModelSelector', '.mxchat-embedding-model-selector-grid .mxchat-embedding-model-selector-card', function(e) {
1229 + e.stopPropagation(); // Prevent event bubbling
1230 + const modelValue = $(this).data('value');
1231 +
1232 + // Important: Only update this specific select element
1233 + $embeddingModelSelect.val(modelValue);
1234 +
1235 + // Manually trigger change only on this element
1236 + const changeEvent = new Event('change', { bubbles: true });
1237 + $embeddingModelSelect[0].dispatchEvent(changeEvent);
1238 +
1239 + // Update button text
1240 + updateButtonText();
1241 +
1242 + // Hide modal
1243 + $('#' + embeddingModalId).hide();
1244 + });
1245 +
1246 + // Close modal when clicking outside - use namespaced events
1247 + $(window).on('click.embeddingModelSelector', function(event) {
1248 + if ($(event.target).is('#' + embeddingModalId)) {
1249 + $('#' + embeddingModalId).hide();
1250 + }
1251 + });
1252 +}
1253 +
1254 +// Call this function after the DOM is fully loaded
1255 +$(document).ready(function() {
1256 + setupMxChatModelSelector();
1257 + setupMxChatEmbeddingModelSelector();
1258 +});
1259 +
1260 + // Initialize API key visibility
1261 + setupAPIKeyVisibility();
1262 +
350 1263 // Add Intent Form Submission
351 1264 $('#mxchat-add-intent-form').on('submit', function(event) {
352 1265 $('#mxchat-intent-loading').show();
353 1266 $('#mxchat-intent-loading-text').show();
@@ -411,50 +1324,9 @@
411 1324 }
412 1325 });
413 1326 });
414 1327
415 - // Activation handling
416 - const form = $('#mxchat-activation-form');
417 - const spinner = $('#mxchat-activation-spinner');
418 - const submitButton = $('#activate_license_button');
419 - const licenseStatus = $('#mxchat-license-status');
420 1328
421 - if (form.length && licenseStatus.length && submitButton.length) {
422 - function handleActivationResponse(response) {
423 - spinner.hide();
424 - if (response.success) {
425 - licenseStatus.text('Active');
426 - licenseStatus.removeClass('inactive').addClass('active');
427 - form.hide();
428 - } else {
429 - licenseStatus.text('Inactive');
430 - alert(response.data || 'Activation failed. Please check your input.');
431 - submitButton.prop('disabled', false);
432 - }
433 - }
434 -
435 - form.on('submit', function(event) {
436 - event.preventDefault();
437 - spinner.show();
438 - submitButton.prop('disabled', true);
439 -
440 - var formData = {
441 - action: 'mxchat_activate_license',
442 - mxchat_pro_email: $('#mxchat_pro_email').val(),
443 - mxchat_activation_key: $('#mxchat_activation_key').val(),
444 - security: mxchatAdmin.license_nonce
445 - };
446 -
447 - $.post(mxchatAdmin.ajax_url, formData, function(response) {
448 - handleActivationResponse(response);
449 - }).fail(function() {
450 - alert('Server error. Please try again.');
451 - spinner.hide();
452 - submitButton.prop('disabled', false);
453 - });
454 - });
455 - }
456 -
457 1329 // Questions handling
458 1330 $('.mxchat-add-question').on('click', function () {
459 1331 const container = $('#mxchat-additional-questions-container');
460 1332 const questionCount = container.find('.mxchat-question-row').length + 4;
@@ -544,9 +1416,641 @@
544 1416 }
545 1417 });
546 1418 }
547 1419
1420 + // Function to adjust the textarea height to content
1421 + function adjustTextareaHeight() {
1422 + this.style.height = 'auto'; // Reset to auto to calculate scrollHeight
1423 + this.style.height = this.scrollHeight + 'px'; // Expand to content height
1424 + }
1425 +
1426 + // Function to reset the textarea height to initial
1427 + function resetTextareaHeight() {
1428 + this.style.height = ''; // Remove inline height, reverting to CSS default
1429 + }
1430 +
1431 + // Target the specific textarea by ID
1432 + var $textarea = $('#system_prompt_instructions');
1433 +
1434 + // Bind events
1435 + $textarea.on('focus input', adjustTextareaHeight) // Expand on focus and input
1436 + .on('blur', resetTextareaHeight); // Reset on blur
1437 +});
1438 +
1439 +
1440 +
1441 +document.addEventListener('DOMContentLoaded', function() {
1442 + // Check if we're on the correct page before initializing
1443 + const modal = document.getElementById('mxchat-action-modal');
548 1444
1445 + // Only initialize if the modal exists on this page
1446 + if (modal) {
1447 + //console.log('MXChat Action Modal JS Loaded');
1448 +
1449 + // Initialize the action modal functionality
1450 + initStepBasedActionModal();
1451 + }
1452 +
1453 + // Function to initialize the step-based action modal
1454 + function initStepBasedActionModal() {
1455 + // We already checked for modal existence above, so no need to check again
1456 +
1457 + const actionStep1 = document.getElementById('mxchat-action-step-1');
1458 + const actionStep2 = document.getElementById('mxchat-action-step-2');
1459 + const backToStep1Btn = document.getElementById('mxchat-back-to-step-1');
1460 + const searchInput = document.getElementById('action-type-search');
1461 + const categoryButtons = modal.querySelectorAll('.mxchat-category-button');
1462 + const actionCards = modal.querySelectorAll('.mxchat-action-type-card');
1463 + const actionForm = document.getElementById('mxchat-action-form');
1464 + const callbackInput = document.getElementById('callback_function');
1465 + const actionIdField = document.getElementById('edit_action_id');
1466 + const labelField = document.getElementById('intent_label');
1467 + const phrasesField = document.getElementById('action_phrases');
1468 + const formActionType = document.getElementById('form_action_type');
1469 + const nonceContainer = document.getElementById('action-nonce-container');
1470 + const thresholdSlider = document.getElementById('similarity_threshold');
1471 + const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
1472 +
1473 + // Rest of your initialization code remains the same...
1474 +
1475 + // Log the structure of one action card for debugging
1476 + if (actionCards.length > 0) {
1477 + //console.log('First action card data attributes:', actionCards[0].dataset);
1478 + //console.log('First action card HTML:', actionCards[0].outerHTML);
1479 + }
1480 +
1481 + // Add click event listeners to category buttons
1482 + categoryButtons.forEach(button => {
1483 + button.addEventListener('click', function() {
1484 + //console.log('Category button clicked:', this.dataset.category);
1485 +
1486 + // Remove active class from all buttons
1487 + categoryButtons.forEach(btn => btn.classList.remove('active'));
1488 +
1489 + // Add active class to clicked button
1490 + this.classList.add('active');
1491 +
1492 + // Get selected category
1493 + const category = this.dataset.category;
1494 +
1495 + // Filter action cards
1496 + filterActionCards(category, searchInput.value);
1497 + });
1498 + });
1499 +
1500 + // Add search functionality
1501 + if (searchInput) {
1502 + searchInput.addEventListener('input', function() {
1503 + // Get active category
1504 + const activeCategory = modal.querySelector('.mxchat-category-button.active')?.dataset.category || 'all';
1505 + //console.log('Search input changed, active category:', activeCategory);
1506 +
1507 + // Filter action cards
1508 + filterActionCards(activeCategory, this.value);
1509 + });
1510 + }
1511 +
1512 + // Add click event listeners to action cards
1513 + actionCards.forEach(card => {
1514 + card.addEventListener('click', function() {
1515 + // Get the action data
1516 + const isPro = this.dataset.pro === 'true';
1517 + const isInstalled = this.dataset.installed === 'true';
1518 + const addonName = this.dataset.addon || '';
1519 + const actionValue = this.dataset.value;
1520 + const actionLabel = this.dataset.label;
1521 + const actionIcon = this.querySelector('.dashicons').getAttribute('class').replace('dashicons dashicons-', '');
1522 + const actionDescription = this.querySelector('p').textContent;
1523 +
1524 + // Pro check using the proper detection method
1525 + const proIsActivated = typeof mxchatAdmin !== 'undefined' &&
1526 + (mxchatAdmin.is_activated === '1' ||
1527 + mxchatAdmin.is_activated === 'true' ||
1528 + mxchatAdmin.is_activated === true);
1529 +
1530 + // Handle different states
1531 + if (isPro && !proIsActivated) {
1532 + // Pro feature but no Pro license
1533 + showProFeatureNotice();
1534 + return;
1535 + }
1536 +
1537 + if (addonName && !isInstalled) {
1538 + // Add-on required but not installed
1539 + const addonDisplayName = this.querySelector('.mxchat-addon-info')?.textContent?.replace('Requires ', '') || addonName + ' Add-on';
1540 + showAddonRequiredNotice(addonDisplayName);
1541 + return;
1542 + }
1543 +
1544 + // If we get here, the action is available - proceed as normal
1545 + callbackInput.value = actionValue;
1546 +
1547 + // Update the selected action display in step 2
1548 + document.getElementById('selected-action-title').textContent = actionLabel;
1549 + document.getElementById('selected-action-description').textContent = actionDescription;
1550 + document.getElementById('selected-action-icon').innerHTML =
1551 + `<span class="dashicons dashicons-${actionIcon}"></span>`;
1552 +
1553 + // Set a default label based on the action type (user can change it)
1554 + if (!labelField.value) {
1555 + labelField.value = actionLabel;
1556 + }
1557 +
1558 + // Move to step 2
1559 + actionStep1.classList.remove('active');
1560 + actionStep2.classList.add('active');
1561 +
1562 + // Update modal title
1563 + });
1564 + });
1565 +
1566 + // Back button functionality
1567 + if (backToStep1Btn) {
1568 + backToStep1Btn.addEventListener('click', function() {
1569 + //console.log('Back button clicked');
1570 + actionStep2.classList.remove('active');
1571 + actionStep1.classList.add('active');
1572 + });
1573 + }
1574 +
1575 + // Function to filter action cards by category and search term
1576 + function filterActionCards(category, searchTerm) {
1577 + searchTerm = searchTerm.toLowerCase().trim();
1578 + //console.log(`Filtering cards by category: "${category}", search: "${searchTerm}"`);
1579 +
1580 + let visibleCount = 0;
1581 +
1582 + // Show all cards initially with animation
1583 + actionCards.forEach((card, index) => {
1584 + // Reset animation
1585 + card.style.animation = 'none';
1586 + // Trigger reflow
1587 + void card.offsetWidth;
1588 +
1589 + // Determine if card should be visible based on category and search term
1590 + const cardCategory = card.dataset.category || '';
1591 + const matchesCategory = category === 'all' || cardCategory === category;
1592 +
1593 + const cardTitle = card.querySelector('h4')?.textContent?.toLowerCase() || '';
1594 + const cardDesc = card.querySelector('p')?.textContent?.toLowerCase() || '';
1595 + const matchesSearch = searchTerm === '' ||
1596 + cardTitle.includes(searchTerm) ||
1597 + cardDesc.includes(searchTerm);
1598 +
1599 + // Show/hide card with animation
1600 + if (matchesCategory && matchesSearch) {
1601 + card.style.display = 'flex';
1602 + // Staggered animation for cards
1603 + card.style.animation = `fadeIn 0.2s ease forwards ${index * 0.03}s`;
1604 + visibleCount++;
1605 + } else {
1606 + card.style.display = 'none';
1607 + }
1608 + });
1609 +
1610 + //console.log(`Filter results: ${visibleCount} cards visible out of ${actionCards.length}`);
1611 + }
1612 +
1613 + // Function to show notice for Pro features
1614 + function showProFeatureNotice() {
1615 + //console.log('Showing Pro feature notice');
1616 + // Check if we already have a notification container
1617 + let noticeContainer = document.querySelector('.mxchat-pro-notice');
1618 +
1619 + if (!noticeContainer) {
1620 + // Create the notice container
1621 + noticeContainer = document.createElement('div');
1622 + noticeContainer.className = 'mxchat-pro-notice';
1623 +
1624 + // Create content
1625 + noticeContainer.innerHTML = `
1626 + <div class="mxchat-pro-notice-content">
1627 + <h3>MxChat Pro Feature</h3>
1628 + <p>This action is available in the Pro version only.</p>
1629 + <div class="mxchat-pro-notice-buttons">
1630 + <button class="mxchat-button-secondary mxchat-pro-notice-close">Close</button>
1631 + <a href="https://mxchat.ai/" class="mxchat-button-primary">Upgrade to Pro</a>
1632 + </div>
1633 + </div>
1634 + `;
1635 +
1636 + // Append to body
1637 + document.body.appendChild(noticeContainer);
1638 +
1639 + // Add close functionality
1640 + const closeButton = noticeContainer.querySelector('.mxchat-pro-notice-close');
1641 + closeButton.addEventListener('click', function() {
1642 + noticeContainer.classList.remove('active');
1643 + setTimeout(() => {
1644 + noticeContainer.remove();
1645 + }, 300);
1646 + });
1647 +
1648 + // Click outside to close
1649 + noticeContainer.addEventListener('click', function(e) {
1650 + if (e.target === noticeContainer) {
1651 + closeButton.click();
1652 + }
1653 + });
1654 +
1655 + // Show with animation
1656 + setTimeout(() => {
1657 + noticeContainer.classList.add('active');
1658 + }, 10);
1659 + } else {
1660 + // If it already exists, just make it visible again
1661 + noticeContainer.classList.add('active');
1662 + }
1663 + }
1664 +
1665 + // Function to show notice for add-on requirements
1666 + function showAddonRequiredNotice(addonName) {
1667 + //console.log(`Showing add-on notice for: ${addonName}`);
1668 + // Check if we already have a notification container
1669 + let noticeContainer = document.querySelector('.mxchat-addon-notice');
1670 +
1671 + if (!noticeContainer) {
1672 + // Create the notice container
1673 + noticeContainer = document.createElement('div');
1674 + noticeContainer.className = 'mxchat-addon-notice';
1675 +
1676 + // Create content
1677 + noticeContainer.innerHTML = `
1678 + <div class="mxchat-addon-notice-content">
1679 + <span class="mxchat-addon-notice-icon">🧩</span>
1680 + <h3>Add-on Required</h3>
1681 + <p>This action requires the <strong>${addonName}</strong> add-on to be installed.</p>
1682 + <div class="mxchat-addon-notice-buttons">
1683 + <button class="mxchat-button-secondary mxchat-addon-notice-close">Close</button>
1684 + <a href="admin.php?page=mxchat-addons" class="mxchat-button-primary">Get Add-ons</a>
1685 + </div>
1686 + </div>
1687 + `;
1688 +
1689 + // Append to body
1690 + document.body.appendChild(noticeContainer);
1691 +
1692 + // Add close functionality
1693 + const closeButton = noticeContainer.querySelector('.mxchat-addon-notice-close');
1694 + closeButton.addEventListener('click', function() {
1695 + noticeContainer.classList.remove('active');
1696 + setTimeout(() => {
1697 + noticeContainer.remove();
1698 + }, 300);
1699 + });
1700 +
1701 + // Click outside to close
1702 + noticeContainer.addEventListener('click', function(e) {
1703 + if (e.target === noticeContainer) {
1704 + closeButton.click();
1705 + }
1706 + });
1707 +
1708 + // Show with animation
1709 + setTimeout(() => {
1710 + noticeContainer.classList.add('active');
1711 + }, 10);
1712 + } else {
1713 + // If it already exists, update the content
1714 + const addonNameElement = noticeContainer.querySelector('p strong');
1715 + if (addonNameElement) {
1716 + addonNameElement.textContent = addonName;
1717 + }
1718 +
1719 + // Make it visible again
1720 + noticeContainer.classList.add('active');
1721 + }
1722 + }
1723 +
1724 + // Form submission handling
1725 + if (actionForm) {
1726 + actionForm.addEventListener('submit', function() {
1727 + //console.log('Form submitted');
1728 + document.getElementById('mxchat-action-loading').style.display = 'flex';
1729 + this.querySelector('button[type="submit"]').disabled = true;
1730 + });
1731 + }
1732 + }
1733 +
1734 + // Setup add action buttons (only if we're on the correct page)
1735 + if (modal) {
1736 + // Update the modal open function to support the step-based flow
1737 + window.mxchatOpenActionModal = function(isEdit = false, actionId = '', label = '', phrases = '', threshold = 85, callbackFunction = '') {
1738 + //console.log('Modal opening, edit mode:', isEdit);
1739 +
1740 + // No need to check again, we already verified modal exists
1741 +
1742 + // Get form fields
1743 + const actionIdField = document.getElementById('edit_action_id');
1744 + const labelField = document.getElementById('intent_label');
1745 + const phrasesField = document.getElementById('action_phrases');
1746 + const formActionType = document.getElementById('form_action_type');
1747 + const callbackInput = document.getElementById('callback_function');
1748 + const saveButton = document.getElementById('mxchat-save-action-btn');
1749 + const nonceContainer = document.getElementById('action-nonce-container');
1750 + const thresholdSlider = document.getElementById('similarity_threshold');
1751 + const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
1752 + const actionStep1 = document.getElementById('mxchat-action-step-1');
1753 + const actionStep2 = document.getElementById('mxchat-action-step-2');
1754 + const searchInput = document.getElementById('action-type-search');
1755 +
1756 + // Set up modal for edit or create
1757 + if (isEdit) {
1758 + saveButton.textContent = 'Update Action';
1759 + formActionType.value = 'mxchat_edit_intent';
1760 + actionIdField.value = actionId;
1761 + labelField.value = label;
1762 + phrasesField.value = phrases;
1763 + callbackInput.value = callbackFunction;
1764 + thresholdSlider.value = threshold; // Set the current threshold value
1765 + thresholdDisplay.textContent = threshold + '%'; // Update display
1766 +
1767 + // Update the nonce field for editing
1768 + nonceContainer.innerHTML = ''; // Clear existing nonce
1769 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.edit_intent_nonce) {
1770 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.edit_intent_nonce}">`;
1771 + }
1772 +
1773 + // For editing, go directly to step 2 and update the selected action display
1774 + actionStep1.classList.remove('active');
1775 + actionStep2.classList.add('active');
1776 +
1777 + // Find the matching action card to get its details
1778 + const actionCards = document.querySelectorAll('.mxchat-action-type-card');
1779 + let foundCard = null;
1780 +
1781 + actionCards.forEach(card => {
1782 + if (card.dataset.value === callbackFunction) {
1783 + foundCard = card;
1784 + }
1785 + });
1786 +
1787 + if (foundCard) {
1788 + //console.log('Found matching action card for:', callbackFunction);
1789 + const actionLabel = foundCard.dataset.label || foundCard.querySelector('h4')?.textContent || '';
1790 + const actionIconElement = foundCard.querySelector('.dashicons');
1791 + const actionIcon = actionIconElement
1792 + ? actionIconElement.getAttribute('class').replace('dashicons dashicons-', '')
1793 + : 'admin-generic';
1794 + const actionDescription = foundCard.querySelector('p')?.textContent || '';
1795 +
1796 + document.getElementById('selected-action-title').textContent = actionLabel;
1797 + document.getElementById('selected-action-description').textContent = actionDescription;
1798 + document.getElementById('selected-action-icon').innerHTML =
1799 + `<span class="dashicons dashicons-${actionIcon}"></span>`;
1800 + } else {
1801 + //console.log('No matching action card found for:', callbackFunction);
1802 + // Fallback if we can't find the card
1803 + document.getElementById('selected-action-title').textContent = label;
1804 + document.getElementById('selected-action-description').textContent = 'Configure this action for your chatbot';
1805 + document.getElementById('selected-action-icon').innerHTML =
1806 + `<span class="dashicons dashicons-admin-generic"></span>`;
1807 + }
1808 + } else {
1809 + //console.log('Setting up create mode');
1810 + saveButton.textContent = 'Save Action';
1811 + formActionType.value = 'mxchat_add_intent';
1812 + actionIdField.value = '';
1813 + labelField.value = '';
1814 + phrasesField.value = '';
1815 + callbackInput.value = '';
1816 + thresholdSlider.value = 85; // Default value for new actions
1817 + thresholdDisplay.textContent = '85%'; // Default display
1818 +
1819 + // Update the nonce field for adding
1820 + nonceContainer.innerHTML = ''; // Clear existing nonce
1821 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.add_intent_nonce) {
1822 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.add_intent_nonce}">`;
1823 + }
1824 +
1825 + // For creating new, start at step 1
1826 + actionStep1.classList.add('active');
1827 + actionStep2.classList.remove('active');
1828 + }
1829 +
1830 + // Show modal with animation
1831 + modal.style.display = 'flex';
1832 + requestAnimationFrame(() => {
1833 + modal.classList.add('active');
1834 + });
1835 +
1836 + // Set up close handlers
1837 + const closeModal = () => {
1838 + //console.log('Closing modal');
1839 + modal.classList.remove('active');
1840 + setTimeout(() => {
1841 + modal.style.display = 'none';
1842 + }, 300); // Match the CSS transition time
1843 + };
1844 +
1845 + // Close button handler
1846 + const closeBtn = modal.querySelector('.mxchat-modal-close');
1847 + if (closeBtn) {
1848 + closeBtn.onclick = closeModal;
1849 + }
1850 +
1851 + // Cancel button handler
1852 + const cancelBtns = modal.querySelectorAll('.mxchat-modal-cancel');
1853 + if (cancelBtns) {
1854 + cancelBtns.forEach(btn => {
1855 + btn.onclick = closeModal;
1856 + });
1857 + }
1858 +
1859 + // Click outside modal to close
1860 + modal.onclick = (e) => {
1861 + if (e.target === modal) {
1862 + closeModal();
1863 + }
1864 + };
1865 +
1866 + // Escape key to close modal
1867 + document.addEventListener('keydown', function(e) {
1868 + if (e.key === 'Escape' && modal.classList.contains('active')) {
1869 + closeModal();
1870 + }
1871 + }, { once: true });
1872 +
1873 + // Focus appropriate field based on current step
1874 + if (isEdit || actionStep2.classList.contains('active')) {
1875 + if (labelField) labelField.focus();
1876 + } else {
1877 + if (searchInput) searchInput.focus();
1878 + }
1879 +
1880 + return closeModal; // Return close function for external use
1881 + };
1882 +
1883 + // Setup add action buttons
1884 + const addActionBtn = document.getElementById('mxchat-add-action-btn');
1885 + if (addActionBtn) {
1886 + //console.log('Add action button found');
1887 + addActionBtn.onclick = () => window.mxchatOpenActionModal();
1888 + }
1889 +
1890 + const createFirstAction = document.getElementById('mxchat-create-first-action');
1891 + if (createFirstAction) {
1892 + //console.log('Create first action button found');
1893 + createFirstAction.onclick = () => window.mxchatOpenActionModal();
1894 + }
1895 +
1896 + // Setup edit buttons
1897 + const editButtons = document.querySelectorAll('.mxchat-action-card .mxchat-edit-button');
1898 + //console.log('Edit buttons found:', editButtons.length);
1899 + editButtons.forEach(button => {
1900 + button.onclick = () => {
1901 + const actionId = button.dataset.actionId;
1902 + const phrases = button.dataset.phrases;
1903 + const label = button.dataset.label;
1904 + const threshold = button.dataset.threshold || 85;
1905 + const callbackFunction = button.dataset.callbackFunction;
1906 +
1907 + window.mxchatOpenActionModal(true, actionId, label, phrases, threshold, callbackFunction);
1908 + };
1909 + });
1910 + }
549 1911 });
550 1912
1913 +jQuery(document).ready(function($) {
1914 + // Toggle custom post types container
1915 + $('#mxchat-custom-post-types-toggle').on('click', function(e) {
1916 + e.preventDefault();
1917 +
1918 + $('#mxchat-custom-post-types-container').slideToggle(300);
1919 +
1920 + // Rotate the toggle icon
1921 + const $icon = $(this).find('.mxchat-accordion-icon');
1922 + if ($('#mxchat-custom-post-types-container').is(':visible')) {
1923 + $icon.css('transform', 'rotate(180deg)');
1924 + $(this).closest('.mxchat-settings-accordion').addClass('active');
1925 + } else {
1926 + $icon.css('transform', 'rotate(0deg)');
1927 + $(this).closest('.mxchat-settings-accordion').removeClass('active');
1928 + }
1929 + });
1930 +
1931 + // If there are any selections made, auto-expand the container
1932 + function autoExpandIfNeeded() {
1933 + // Check if any checkbox in the container is checked
1934 + const hasCheckedItems = $('#mxchat-custom-post-types-container input[type="checkbox"]:checked').length > 0;
1935 +
1936 + if (hasCheckedItems) {
1937 + $('#mxchat-custom-post-types-container').show();
1938 + $('#mxchat-custom-post-types-toggle .mxchat-accordion-icon').css('transform', 'rotate(180deg)');
1939 + $('.mxchat-settings-accordion').addClass('active');
1940 + }
1941 + }
1942 +
1943 + // Run on page load
1944 + autoExpandIfNeeded();
1945 +});
551 1946
1947 +document.addEventListener('DOMContentLoaded', function() {
1948 + var viewSampleBtn = document.getElementById('mxchatViewSampleBtn');
1949 + var modal = document.getElementById('mxchatSampleModal');
1950 + var modalClose = document.getElementById('mxchatModalClose');
1951 + var closeBtn = document.getElementById('mxchatCloseBtn');
1952 + var copyBtn = document.getElementById('mxchatCopyBtn');
1953 + var instructionsContent = document.querySelector('.mxchat-instructions-content');
1954 + var modalContent = document.querySelector('.mxchat-instructions-modal-content');
552 1955
1956 + if (!viewSampleBtn || !modal) {
1957 + return;
1958 + }
1959 +
1960 + // Open modal
1961 + viewSampleBtn.addEventListener('click', function(e) {
1962 + e.preventDefault();
1963 + e.stopPropagation();
1964 + modal.classList.add('mxchat-instructions-show');
1965 + });
1966 +
1967 + // Close modal function
1968 + function closeModal(e) {
1969 + if (e) {
1970 + e.preventDefault();
1971 + e.stopPropagation();
1972 + }
1973 + modal.classList.remove('mxchat-instructions-show');
1974 + }
1975 +
1976 + // Close modal events
1977 + if (modalClose) {
1978 + modalClose.addEventListener('click', function(e) {
1979 + closeModal(e);
1980 + });
1981 + }
1982 +
1983 + if (closeBtn) {
1984 + closeBtn.addEventListener('click', function(e) {
1985 + closeModal(e);
1986 + });
1987 + }
1988 +
1989 + // Close on backdrop click ONLY (not on hover)
1990 + modal.addEventListener('click', function(e) {
1991 + // Only close if clicking directly on the overlay, not on child elements
1992 + if (e.target === modal) {
1993 + closeModal(e);
1994 + }
1995 + });
1996 +
1997 + // Prevent modal content clicks from closing the modal
1998 + if (modalContent) {
1999 + modalContent.addEventListener('click', function(e) {
2000 + e.stopPropagation();
2001 + });
2002 + }
2003 +
2004 + // Close on escape key
2005 + document.addEventListener('keydown', function(e) {
2006 + if (e.key === 'Escape' && modal.classList.contains('mxchat-instructions-show')) {
2007 + closeModal();
2008 + }
2009 + });
2010 +
2011 + // Copy functionality
2012 + if (copyBtn && instructionsContent) {
2013 + copyBtn.addEventListener('click', function(e) {
2014 + e.preventDefault();
2015 + e.stopPropagation();
2016 +
2017 + var text = instructionsContent.textContent;
2018 +
2019 + if (navigator.clipboard) {
2020 + navigator.clipboard.writeText(text).then(function() {
2021 + showCopySuccess();
2022 + }).catch(function() {
2023 + fallbackCopy(text);
2024 + });
2025 + } else {
2026 + fallbackCopy(text);
2027 + }
2028 + });
2029 + }
2030 +
2031 + function fallbackCopy(text) {
2032 + var textArea = document.createElement('textarea');
2033 + textArea.value = text;
2034 + textArea.style.position = 'fixed';
2035 + textArea.style.left = '-999999px';
2036 + textArea.style.top = '-999999px';
2037 + document.body.appendChild(textArea);
2038 + textArea.select();
2039 + try {
2040 + document.execCommand('copy');
2041 + showCopySuccess();
2042 + } catch (err) {
2043 + console.error('Copy failed');
2044 + }
2045 + document.body.removeChild(textArea);
2046 + }
2047 +
2048 + function showCopySuccess() {
2049 + var originalText = copyBtn.innerHTML;
2050 + copyBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20,6 9,17 4,12"/></svg>Copied!';
2051 +
2052 + setTimeout(function() {
2053 + copyBtn.innerHTML = originalText;
2054 + }, 2000);
2055 + }
2056 +});