PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.2.8
MxChat – AI Chatbot & Content Generation for WordPress v2.2.8
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | js/mxchat-admin.js +1753 -245 2.0.32.2.8 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,86 +388,197 @@
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() {
92 - const $field = $(this);
93 - const name = $field.attr('name');
94 - let value;
95 -
96 - // Handle different input types
97 - if ($field.attr('type') === 'checkbox') {
98 - value = $field.is(':checked') ? 'on' : 'off';
99 - } else {
100 - value = $field.val();
101 - }
102 -
103 - // Create feedback container
104 - const feedbackContainer = $('<div class="feedback-container"></div>');
105 - const spinner = $('<div class="saving-spinner"></div>');
106 - const successIcon = $('<div class="success-icon">✔</div>');
107 -
108 - // Position feedback container based on input type
109 - if ($field.closest('.toggle-switch').length) {
110 - $field.closest('td').append(feedbackContainer);
111 - } else if ($field.closest('.mxchat-toggle-switch').length) {
112 - $field.closest('.mxchat-toggle-container').append(feedbackContainer);
113 - } else if ($field.closest('.slider-container').length) {
114 - $field.closest('.slider-container').after(feedbackContainer);
115 - } else {
116 - $field.after(feedbackContainer);
117 - }
118 - feedbackContainer.append(spinner);
119 -
120 - // Determine which AJAX action and nonce to use:
121 - 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' ) {
126 - ajaxAction = 'mxchat_save_prompts_setting';
127 - nonce = mxchatPromptsAdmin.prompts_setting_nonce;
128 - } else {
129 - // Otherwise, use the existing AJAX action.
130 - ajaxAction = 'mxchat_save_setting';
131 - nonce = mxchatAdmin.setting_nonce;
132 - }
133 -
134 - // AJAX save request
135 - $.ajax({
136 - url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url,
137 - type: 'POST',
138 - data: {
139 - action: ajaxAction,
140 - name: name,
141 - value: value,
142 - _ajax_nonce: nonce
143 - },
144 - 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 - });
392 + $autosaveSections.find('input, textarea, select').on('change', function() {
393 + const $field = $(this);
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 +
417 + let value;
418 +
419 + // Handle different input types
420 + if ($field.attr('type') === 'checkbox') {
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 + }
152 427 } else {
153 - alert('Error saving: ' + (response.data?.message || 'Unknown error'));
154 - if ($field.attr('type') === 'checkbox') {
155 - $field.prop('checked', !$field.is(':checked'));
428 + value = $field.val();
429 + }
430 +
431 + // Create feedback container
432 + const feedbackContainer = $('<div class="feedback-container"></div>');
433 + const spinner = $('<div class="saving-spinner"></div>');
434 + const successIcon = $('<div class="success-icon">✔</div>');
435 +
436 + // Position feedback container based on input type
437 + if ($field.closest('.toggle-switch').length) {
438 + $field.closest('td').append(feedbackContainer);
439 + } else if ($field.closest('.mxchat-toggle-switch').length) {
440 + $field.closest('.mxchat-toggle-container').append(feedbackContainer);
441 + } else if ($field.closest('.slider-container').length) {
442 + $field.closest('.slider-container').after(feedbackContainer);
443 + } else {
444 + $field.after(feedbackContainer);
445 + }
446 + feedbackContainer.append(spinner);
447 +
448 + // Determine which AJAX action and nonce to use:
449 + var ajaxAction, nonce;
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 ***
456 + ajaxAction = 'mxchat_save_prompts_setting';
457 + nonce = mxchatPromptsAdmin.prompts_setting_nonce;
458 + } else {
459 + // Otherwise, use the existing AJAX action.
460 + ajaxAction = 'mxchat_save_setting';
461 + nonce = mxchatAdmin.setting_nonce;
462 + }
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 +
469 + // AJAX save request
470 + $.ajax({
471 + url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url,
472 + type: 'POST',
473 + data: {
474 + action: ajaxAction,
475 + name: name,
476 + value: value,
477 + _ajax_nonce: nonce
478 + },
479 + success: function(response) {
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);
156 522 }
157 - feedbackContainer.remove();
523 + } else {
524 + // If not on import tab, set a flag to refresh when they go there
525 + sessionStorage.setItem('mxchat_pinecone_changed', 'true');
158 526 }
159 - },
160 - error: function() {
161 - alert('An error occurred while saving.');
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') {
162 559 if ($field.attr('type') === 'checkbox') {
163 560 $field.prop('checked', !$field.is(':checked'));
164 561 }
165 - feedbackContainer.remove();
166 562 }
167 - });
168 - });
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
573 + if ($field.attr('type') === 'checkbox') {
574 + $field.prop('checked', !$field.is(':checked'));
575 + }
576 +
577 + feedbackContainer.remove();
578 + }
579 + });
580 + });
169 581
170 582 // Initialize color pickers with debouncing
171 583 $autosaveSections.find('.my-color-field').each(function() {
172 584 const $colorField = $(this);
@@ -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();
@@ -363,97 +1276,60 @@
363 1276 row.find('.save-button').show();
364 1277 });
365 1278
366 1279 // Save button handler
367 - $('.save-button').on('click', function() {
368 - var button = $(this);
369 - var row = button.closest('tr');
370 - var id = button.data('id');
371 - var newContent = row.find('.content-edit').val();
372 - var newUrl = row.find('.url-edit').val();
373 -
374 - button.prop('disabled', true);
375 - button.text('Saving...');
376 -
377 - $.ajax({
378 - url: mxchatAdmin.ajax_url,
379 - type: 'POST',
380 - data: {
381 - action: 'mxchat_save_inline_prompt',
382 - id: id,
383 - article_content: newContent,
384 - article_url: newUrl,
385 - _ajax_nonce: mxchatAdmin.inline_edit_nonce
386 - },
387 - success: function(response) {
388 - button.prop('disabled', false);
389 - button.text('Save');
390 -
391 - if (response.success) {
392 - row.find('.content-view').html(newContent.replace(/\n/g, "<br>"));
393 - if (newUrl) {
394 - row.find('.url-view').html('<a href="' + newUrl + '" target="_blank">' + newUrl + '</a>');
395 - } else {
396 - row.find('.url-view').html('N/A');
397 - }
398 -
399 - row.find('.content-edit, .url-edit').hide();
400 - row.find('.content-view, .url-view').show();
401 - row.find('.save-button').hide();
402 - row.find('.edit-button').show();
1280 +// Save button handler
1281 +$('.save-button').on('click', function() {
1282 + var button = $(this);
1283 + var row = button.closest('tr');
1284 + var id = button.data('id');
1285 + var nonce = button.data('nonce'); // Get nonce from button data attribute
1286 + var newContent = row.find('.content-edit').val();
1287 + var newUrl = row.find('.url-edit').val();
1288 +
1289 + //console.log('Nonce from button:', nonce); // Debug
1290 +
1291 + button.prop('disabled', true);
1292 + button.text('Saving...');
1293 +
1294 + $.ajax({
1295 + url: mxchatAdmin.ajax_url,
1296 + type: 'POST',
1297 + data: {
1298 + action: 'mxchat_save_inline_prompt',
1299 + id: id,
1300 + article_content: newContent,
1301 + article_url: newUrl,
1302 + _ajax_nonce: nonce // Use nonce from button
1303 + },
1304 + success: function(response) {
1305 + button.prop('disabled', false);
1306 + button.text('Save');
1307 +
1308 + if (response.success) {
1309 + row.find('.content-view').html(newContent.replace(/\n/g, "<br>"));
1310 + if (newUrl) {
1311 + row.find('.url-view').html('<a href="' + newUrl + '" target="_blank">' + newUrl + '</a>');
403 1312 } else {
404 - alert('Error saving content: ' + (response.data?.message || 'Unknown error'));
1313 + row.find('.url-view').html('N/A');
405 1314 }
406 - },
407 - error: function() {
408 - button.prop('disabled', false);
409 - button.text('Save');
410 - alert('An error occurred while saving.');
411 - }
412 - });
413 - });
414 -
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 -
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();
1315 +
1316 + row.find('.content-edit, .url-edit').hide();
1317 + row.find('.content-view, .url-view').show();
1318 + row.find('.save-button').hide();
1319 + row.find('.edit-button').show();
428 1320 } else {
429 - licenseStatus.text('Inactive');
430 - alert(response.data || 'Activation failed. Please check your input.');
431 - submitButton.prop('disabled', false);
1321 + alert('Error saving content: ' + (response.data?.message || 'Unknown error'));
432 1322 }
1323 + },
1324 + error: function() {
1325 + button.prop('disabled', false);
1326 + button.text('Save');
1327 + alert('An error occurred while saving.');
433 1328 }
1329 + });
1330 +});
434 1331
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 1332
457 1333 // Questions handling
458 1334 $('.mxchat-add-question').on('click', function () {
459 1335 const container = $('#mxchat-additional-questions-container');
@@ -544,9 +1420,641 @@
544 1420 }
545 1421 });
546 1422 }
547 1423
1424 + // Function to adjust the textarea height to content
1425 + function adjustTextareaHeight() {
1426 + this.style.height = 'auto'; // Reset to auto to calculate scrollHeight
1427 + this.style.height = this.scrollHeight + 'px'; // Expand to content height
1428 + }
1429 +
1430 + // Function to reset the textarea height to initial
1431 + function resetTextareaHeight() {
1432 + this.style.height = ''; // Remove inline height, reverting to CSS default
1433 + }
1434 +
1435 + // Target the specific textarea by ID
1436 + var $textarea = $('#system_prompt_instructions');
1437 +
1438 + // Bind events
1439 + $textarea.on('focus input', adjustTextareaHeight) // Expand on focus and input
1440 + .on('blur', resetTextareaHeight); // Reset on blur
1441 +});
1442 +
1443 +
1444 +
1445 +document.addEventListener('DOMContentLoaded', function() {
1446 + // Check if we're on the correct page before initializing
1447 + const modal = document.getElementById('mxchat-action-modal');
548 1448
1449 + // Only initialize if the modal exists on this page
1450 + if (modal) {
1451 + //console.log('MXChat Action Modal JS Loaded');
1452 +
1453 + // Initialize the action modal functionality
1454 + initStepBasedActionModal();
1455 + }
1456 +
1457 + // Function to initialize the step-based action modal
1458 + function initStepBasedActionModal() {
1459 + // We already checked for modal existence above, so no need to check again
1460 +
1461 + const actionStep1 = document.getElementById('mxchat-action-step-1');
1462 + const actionStep2 = document.getElementById('mxchat-action-step-2');
1463 + const backToStep1Btn = document.getElementById('mxchat-back-to-step-1');
1464 + const searchInput = document.getElementById('action-type-search');
1465 + const categoryButtons = modal.querySelectorAll('.mxchat-category-button');
1466 + const actionCards = modal.querySelectorAll('.mxchat-action-type-card');
1467 + const actionForm = document.getElementById('mxchat-action-form');
1468 + const callbackInput = document.getElementById('callback_function');
1469 + const actionIdField = document.getElementById('edit_action_id');
1470 + const labelField = document.getElementById('intent_label');
1471 + const phrasesField = document.getElementById('action_phrases');
1472 + const formActionType = document.getElementById('form_action_type');
1473 + const nonceContainer = document.getElementById('action-nonce-container');
1474 + const thresholdSlider = document.getElementById('similarity_threshold');
1475 + const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
1476 +
1477 + // Rest of your initialization code remains the same...
1478 +
1479 + // Log the structure of one action card for debugging
1480 + if (actionCards.length > 0) {
1481 + //console.log('First action card data attributes:', actionCards[0].dataset);
1482 + //console.log('First action card HTML:', actionCards[0].outerHTML);
1483 + }
1484 +
1485 + // Add click event listeners to category buttons
1486 + categoryButtons.forEach(button => {
1487 + button.addEventListener('click', function() {
1488 + //console.log('Category button clicked:', this.dataset.category);
1489 +
1490 + // Remove active class from all buttons
1491 + categoryButtons.forEach(btn => btn.classList.remove('active'));
1492 +
1493 + // Add active class to clicked button
1494 + this.classList.add('active');
1495 +
1496 + // Get selected category
1497 + const category = this.dataset.category;
1498 +
1499 + // Filter action cards
1500 + filterActionCards(category, searchInput.value);
1501 + });
1502 + });
1503 +
1504 + // Add search functionality
1505 + if (searchInput) {
1506 + searchInput.addEventListener('input', function() {
1507 + // Get active category
1508 + const activeCategory = modal.querySelector('.mxchat-category-button.active')?.dataset.category || 'all';
1509 + //console.log('Search input changed, active category:', activeCategory);
1510 +
1511 + // Filter action cards
1512 + filterActionCards(activeCategory, this.value);
1513 + });
1514 + }
1515 +
1516 + // Add click event listeners to action cards
1517 + actionCards.forEach(card => {
1518 + card.addEventListener('click', function() {
1519 + // Get the action data
1520 + const isPro = this.dataset.pro === 'true';
1521 + const isInstalled = this.dataset.installed === 'true';
1522 + const addonName = this.dataset.addon || '';
1523 + const actionValue = this.dataset.value;
1524 + const actionLabel = this.dataset.label;
1525 + const actionIcon = this.querySelector('.dashicons').getAttribute('class').replace('dashicons dashicons-', '');
1526 + const actionDescription = this.querySelector('p').textContent;
1527 +
1528 + // Pro check using the proper detection method
1529 + const proIsActivated = typeof mxchatAdmin !== 'undefined' &&
1530 + (mxchatAdmin.is_activated === '1' ||
1531 + mxchatAdmin.is_activated === 'true' ||
1532 + mxchatAdmin.is_activated === true);
1533 +
1534 + // Handle different states
1535 + if (isPro && !proIsActivated) {
1536 + // Pro feature but no Pro license
1537 + showProFeatureNotice();
1538 + return;
1539 + }
1540 +
1541 + if (addonName && !isInstalled) {
1542 + // Add-on required but not installed
1543 + const addonDisplayName = this.querySelector('.mxchat-addon-info')?.textContent?.replace('Requires ', '') || addonName + ' Add-on';
1544 + showAddonRequiredNotice(addonDisplayName);
1545 + return;
1546 + }
1547 +
1548 + // If we get here, the action is available - proceed as normal
1549 + callbackInput.value = actionValue;
1550 +
1551 + // Update the selected action display in step 2
1552 + document.getElementById('selected-action-title').textContent = actionLabel;
1553 + document.getElementById('selected-action-description').textContent = actionDescription;
1554 + document.getElementById('selected-action-icon').innerHTML =
1555 + `<span class="dashicons dashicons-${actionIcon}"></span>`;
1556 +
1557 + // Set a default label based on the action type (user can change it)
1558 + if (!labelField.value) {
1559 + labelField.value = actionLabel;
1560 + }
1561 +
1562 + // Move to step 2
1563 + actionStep1.classList.remove('active');
1564 + actionStep2.classList.add('active');
1565 +
1566 + // Update modal title
1567 + });
1568 + });
1569 +
1570 + // Back button functionality
1571 + if (backToStep1Btn) {
1572 + backToStep1Btn.addEventListener('click', function() {
1573 + //console.log('Back button clicked');
1574 + actionStep2.classList.remove('active');
1575 + actionStep1.classList.add('active');
1576 + });
1577 + }
1578 +
1579 + // Function to filter action cards by category and search term
1580 + function filterActionCards(category, searchTerm) {
1581 + searchTerm = searchTerm.toLowerCase().trim();
1582 + //console.log(`Filtering cards by category: "${category}", search: "${searchTerm}"`);
1583 +
1584 + let visibleCount = 0;
1585 +
1586 + // Show all cards initially with animation
1587 + actionCards.forEach((card, index) => {
1588 + // Reset animation
1589 + card.style.animation = 'none';
1590 + // Trigger reflow
1591 + void card.offsetWidth;
1592 +
1593 + // Determine if card should be visible based on category and search term
1594 + const cardCategory = card.dataset.category || '';
1595 + const matchesCategory = category === 'all' || cardCategory === category;
1596 +
1597 + const cardTitle = card.querySelector('h4')?.textContent?.toLowerCase() || '';
1598 + const cardDesc = card.querySelector('p')?.textContent?.toLowerCase() || '';
1599 + const matchesSearch = searchTerm === '' ||
1600 + cardTitle.includes(searchTerm) ||
1601 + cardDesc.includes(searchTerm);
1602 +
1603 + // Show/hide card with animation
1604 + if (matchesCategory && matchesSearch) {
1605 + card.style.display = 'flex';
1606 + // Staggered animation for cards
1607 + card.style.animation = `fadeIn 0.2s ease forwards ${index * 0.03}s`;
1608 + visibleCount++;
1609 + } else {
1610 + card.style.display = 'none';
1611 + }
1612 + });
1613 +
1614 + //console.log(`Filter results: ${visibleCount} cards visible out of ${actionCards.length}`);
1615 + }
1616 +
1617 + // Function to show notice for Pro features
1618 + function showProFeatureNotice() {
1619 + //console.log('Showing Pro feature notice');
1620 + // Check if we already have a notification container
1621 + let noticeContainer = document.querySelector('.mxchat-pro-notice');
1622 +
1623 + if (!noticeContainer) {
1624 + // Create the notice container
1625 + noticeContainer = document.createElement('div');
1626 + noticeContainer.className = 'mxchat-pro-notice';
1627 +
1628 + // Create content
1629 + noticeContainer.innerHTML = `
1630 + <div class="mxchat-pro-notice-content">
1631 + <h3>MxChat Pro Feature</h3>
1632 + <p>This action is available in the Pro version only.</p>
1633 + <div class="mxchat-pro-notice-buttons">
1634 + <button class="mxchat-button-secondary mxchat-pro-notice-close">Close</button>
1635 + <a href="https://mxchat.ai/" class="mxchat-button-primary">Upgrade to Pro</a>
1636 + </div>
1637 + </div>
1638 + `;
1639 +
1640 + // Append to body
1641 + document.body.appendChild(noticeContainer);
1642 +
1643 + // Add close functionality
1644 + const closeButton = noticeContainer.querySelector('.mxchat-pro-notice-close');
1645 + closeButton.addEventListener('click', function() {
1646 + noticeContainer.classList.remove('active');
1647 + setTimeout(() => {
1648 + noticeContainer.remove();
1649 + }, 300);
1650 + });
1651 +
1652 + // Click outside to close
1653 + noticeContainer.addEventListener('click', function(e) {
1654 + if (e.target === noticeContainer) {
1655 + closeButton.click();
1656 + }
1657 + });
1658 +
1659 + // Show with animation
1660 + setTimeout(() => {
1661 + noticeContainer.classList.add('active');
1662 + }, 10);
1663 + } else {
1664 + // If it already exists, just make it visible again
1665 + noticeContainer.classList.add('active');
1666 + }
1667 + }
1668 +
1669 + // Function to show notice for add-on requirements
1670 + function showAddonRequiredNotice(addonName) {
1671 + //console.log(`Showing add-on notice for: ${addonName}`);
1672 + // Check if we already have a notification container
1673 + let noticeContainer = document.querySelector('.mxchat-addon-notice');
1674 +
1675 + if (!noticeContainer) {
1676 + // Create the notice container
1677 + noticeContainer = document.createElement('div');
1678 + noticeContainer.className = 'mxchat-addon-notice';
1679 +
1680 + // Create content
1681 + noticeContainer.innerHTML = `
1682 + <div class="mxchat-addon-notice-content">
1683 + <span class="mxchat-addon-notice-icon">🧩</span>
1684 + <h3>Add-on Required</h3>
1685 + <p>This action requires the <strong>${addonName}</strong> add-on to be installed.</p>
1686 + <div class="mxchat-addon-notice-buttons">
1687 + <button class="mxchat-button-secondary mxchat-addon-notice-close">Close</button>
1688 + <a href="admin.php?page=mxchat-addons" class="mxchat-button-primary">Get Add-ons</a>
1689 + </div>
1690 + </div>
1691 + `;
1692 +
1693 + // Append to body
1694 + document.body.appendChild(noticeContainer);
1695 +
1696 + // Add close functionality
1697 + const closeButton = noticeContainer.querySelector('.mxchat-addon-notice-close');
1698 + closeButton.addEventListener('click', function() {
1699 + noticeContainer.classList.remove('active');
1700 + setTimeout(() => {
1701 + noticeContainer.remove();
1702 + }, 300);
1703 + });
1704 +
1705 + // Click outside to close
1706 + noticeContainer.addEventListener('click', function(e) {
1707 + if (e.target === noticeContainer) {
1708 + closeButton.click();
1709 + }
1710 + });
1711 +
1712 + // Show with animation
1713 + setTimeout(() => {
1714 + noticeContainer.classList.add('active');
1715 + }, 10);
1716 + } else {
1717 + // If it already exists, update the content
1718 + const addonNameElement = noticeContainer.querySelector('p strong');
1719 + if (addonNameElement) {
1720 + addonNameElement.textContent = addonName;
1721 + }
1722 +
1723 + // Make it visible again
1724 + noticeContainer.classList.add('active');
1725 + }
1726 + }
1727 +
1728 + // Form submission handling
1729 + if (actionForm) {
1730 + actionForm.addEventListener('submit', function() {
1731 + //console.log('Form submitted');
1732 + document.getElementById('mxchat-action-loading').style.display = 'flex';
1733 + this.querySelector('button[type="submit"]').disabled = true;
1734 + });
1735 + }
1736 + }
1737 +
1738 + // Setup add action buttons (only if we're on the correct page)
1739 + if (modal) {
1740 + // Update the modal open function to support the step-based flow
1741 + window.mxchatOpenActionModal = function(isEdit = false, actionId = '', label = '', phrases = '', threshold = 85, callbackFunction = '') {
1742 + //console.log('Modal opening, edit mode:', isEdit);
1743 +
1744 + // No need to check again, we already verified modal exists
1745 +
1746 + // Get form fields
1747 + const actionIdField = document.getElementById('edit_action_id');
1748 + const labelField = document.getElementById('intent_label');
1749 + const phrasesField = document.getElementById('action_phrases');
1750 + const formActionType = document.getElementById('form_action_type');
1751 + const callbackInput = document.getElementById('callback_function');
1752 + const saveButton = document.getElementById('mxchat-save-action-btn');
1753 + const nonceContainer = document.getElementById('action-nonce-container');
1754 + const thresholdSlider = document.getElementById('similarity_threshold');
1755 + const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
1756 + const actionStep1 = document.getElementById('mxchat-action-step-1');
1757 + const actionStep2 = document.getElementById('mxchat-action-step-2');
1758 + const searchInput = document.getElementById('action-type-search');
1759 +
1760 + // Set up modal for edit or create
1761 + if (isEdit) {
1762 + saveButton.textContent = 'Update Action';
1763 + formActionType.value = 'mxchat_edit_intent';
1764 + actionIdField.value = actionId;
1765 + labelField.value = label;
1766 + phrasesField.value = phrases;
1767 + callbackInput.value = callbackFunction;
1768 + thresholdSlider.value = threshold; // Set the current threshold value
1769 + thresholdDisplay.textContent = threshold + '%'; // Update display
1770 +
1771 + // Update the nonce field for editing
1772 + nonceContainer.innerHTML = ''; // Clear existing nonce
1773 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.edit_intent_nonce) {
1774 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.edit_intent_nonce}">`;
1775 + }
1776 +
1777 + // For editing, go directly to step 2 and update the selected action display
1778 + actionStep1.classList.remove('active');
1779 + actionStep2.classList.add('active');
1780 +
1781 + // Find the matching action card to get its details
1782 + const actionCards = document.querySelectorAll('.mxchat-action-type-card');
1783 + let foundCard = null;
1784 +
1785 + actionCards.forEach(card => {
1786 + if (card.dataset.value === callbackFunction) {
1787 + foundCard = card;
1788 + }
1789 + });
1790 +
1791 + if (foundCard) {
1792 + //console.log('Found matching action card for:', callbackFunction);
1793 + const actionLabel = foundCard.dataset.label || foundCard.querySelector('h4')?.textContent || '';
1794 + const actionIconElement = foundCard.querySelector('.dashicons');
1795 + const actionIcon = actionIconElement
1796 + ? actionIconElement.getAttribute('class').replace('dashicons dashicons-', '')
1797 + : 'admin-generic';
1798 + const actionDescription = foundCard.querySelector('p')?.textContent || '';
1799 +
1800 + document.getElementById('selected-action-title').textContent = actionLabel;
1801 + document.getElementById('selected-action-description').textContent = actionDescription;
1802 + document.getElementById('selected-action-icon').innerHTML =
1803 + `<span class="dashicons dashicons-${actionIcon}"></span>`;
1804 + } else {
1805 + //console.log('No matching action card found for:', callbackFunction);
1806 + // Fallback if we can't find the card
1807 + document.getElementById('selected-action-title').textContent = label;
1808 + document.getElementById('selected-action-description').textContent = 'Configure this action for your chatbot';
1809 + document.getElementById('selected-action-icon').innerHTML =
1810 + `<span class="dashicons dashicons-admin-generic"></span>`;
1811 + }
1812 + } else {
1813 + //console.log('Setting up create mode');
1814 + saveButton.textContent = 'Save Action';
1815 + formActionType.value = 'mxchat_add_intent';
1816 + actionIdField.value = '';
1817 + labelField.value = '';
1818 + phrasesField.value = '';
1819 + callbackInput.value = '';
1820 + thresholdSlider.value = 85; // Default value for new actions
1821 + thresholdDisplay.textContent = '85%'; // Default display
1822 +
1823 + // Update the nonce field for adding
1824 + nonceContainer.innerHTML = ''; // Clear existing nonce
1825 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.add_intent_nonce) {
1826 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.add_intent_nonce}">`;
1827 + }
1828 +
1829 + // For creating new, start at step 1
1830 + actionStep1.classList.add('active');
1831 + actionStep2.classList.remove('active');
1832 + }
1833 +
1834 + // Show modal with animation
1835 + modal.style.display = 'flex';
1836 + requestAnimationFrame(() => {
1837 + modal.classList.add('active');
1838 + });
1839 +
1840 + // Set up close handlers
1841 + const closeModal = () => {
1842 + //console.log('Closing modal');
1843 + modal.classList.remove('active');
1844 + setTimeout(() => {
1845 + modal.style.display = 'none';
1846 + }, 300); // Match the CSS transition time
1847 + };
1848 +
1849 + // Close button handler
1850 + const closeBtn = modal.querySelector('.mxchat-modal-close');
1851 + if (closeBtn) {
1852 + closeBtn.onclick = closeModal;
1853 + }
1854 +
1855 + // Cancel button handler
1856 + const cancelBtns = modal.querySelectorAll('.mxchat-modal-cancel');
1857 + if (cancelBtns) {
1858 + cancelBtns.forEach(btn => {
1859 + btn.onclick = closeModal;
1860 + });
1861 + }
1862 +
1863 + // Click outside modal to close
1864 + modal.onclick = (e) => {
1865 + if (e.target === modal) {
1866 + closeModal();
1867 + }
1868 + };
1869 +
1870 + // Escape key to close modal
1871 + document.addEventListener('keydown', function(e) {
1872 + if (e.key === 'Escape' && modal.classList.contains('active')) {
1873 + closeModal();
1874 + }
1875 + }, { once: true });
1876 +
1877 + // Focus appropriate field based on current step
1878 + if (isEdit || actionStep2.classList.contains('active')) {
1879 + if (labelField) labelField.focus();
1880 + } else {
1881 + if (searchInput) searchInput.focus();
1882 + }
1883 +
1884 + return closeModal; // Return close function for external use
1885 + };
1886 +
1887 + // Setup add action buttons
1888 + const addActionBtn = document.getElementById('mxchat-add-action-btn');
1889 + if (addActionBtn) {
1890 + //console.log('Add action button found');
1891 + addActionBtn.onclick = () => window.mxchatOpenActionModal();
1892 + }
1893 +
1894 + const createFirstAction = document.getElementById('mxchat-create-first-action');
1895 + if (createFirstAction) {
1896 + //console.log('Create first action button found');
1897 + createFirstAction.onclick = () => window.mxchatOpenActionModal();
1898 + }
1899 +
1900 + // Setup edit buttons
1901 + const editButtons = document.querySelectorAll('.mxchat-action-card .mxchat-edit-button');
1902 + //console.log('Edit buttons found:', editButtons.length);
1903 + editButtons.forEach(button => {
1904 + button.onclick = () => {
1905 + const actionId = button.dataset.actionId;
1906 + const phrases = button.dataset.phrases;
1907 + const label = button.dataset.label;
1908 + const threshold = button.dataset.threshold || 85;
1909 + const callbackFunction = button.dataset.callbackFunction;
1910 +
1911 + window.mxchatOpenActionModal(true, actionId, label, phrases, threshold, callbackFunction);
1912 + };
1913 + });
1914 + }
549 1915 });
550 1916
1917 +jQuery(document).ready(function($) {
1918 + // Toggle custom post types container
1919 + $('#mxchat-custom-post-types-toggle').on('click', function(e) {
1920 + e.preventDefault();
1921 +
1922 + $('#mxchat-custom-post-types-container').slideToggle(300);
1923 +
1924 + // Rotate the toggle icon
1925 + const $icon = $(this).find('.mxchat-accordion-icon');
1926 + if ($('#mxchat-custom-post-types-container').is(':visible')) {
1927 + $icon.css('transform', 'rotate(180deg)');
1928 + $(this).closest('.mxchat-settings-accordion').addClass('active');
1929 + } else {
1930 + $icon.css('transform', 'rotate(0deg)');
1931 + $(this).closest('.mxchat-settings-accordion').removeClass('active');
1932 + }
1933 + });
1934 +
1935 + // If there are any selections made, auto-expand the container
1936 + function autoExpandIfNeeded() {
1937 + // Check if any checkbox in the container is checked
1938 + const hasCheckedItems = $('#mxchat-custom-post-types-container input[type="checkbox"]:checked').length > 0;
1939 +
1940 + if (hasCheckedItems) {
1941 + $('#mxchat-custom-post-types-container').show();
1942 + $('#mxchat-custom-post-types-toggle .mxchat-accordion-icon').css('transform', 'rotate(180deg)');
1943 + $('.mxchat-settings-accordion').addClass('active');
1944 + }
1945 + }
1946 +
1947 + // Run on page load
1948 + autoExpandIfNeeded();
1949 +});
551 1950
1951 +document.addEventListener('DOMContentLoaded', function() {
1952 + var viewSampleBtn = document.getElementById('mxchatViewSampleBtn');
1953 + var modal = document.getElementById('mxchatSampleModal');
1954 + var modalClose = document.getElementById('mxchatModalClose');
1955 + var closeBtn = document.getElementById('mxchatCloseBtn');
1956 + var copyBtn = document.getElementById('mxchatCopyBtn');
1957 + var instructionsContent = document.querySelector('.mxchat-instructions-content');
1958 + var modalContent = document.querySelector('.mxchat-instructions-modal-content');
552 1959
1960 + if (!viewSampleBtn || !modal) {
1961 + return;
1962 + }
1963 +
1964 + // Open modal
1965 + viewSampleBtn.addEventListener('click', function(e) {
1966 + e.preventDefault();
1967 + e.stopPropagation();
1968 + modal.classList.add('mxchat-instructions-show');
1969 + });
1970 +
1971 + // Close modal function
1972 + function closeModal(e) {
1973 + if (e) {
1974 + e.preventDefault();
1975 + e.stopPropagation();
1976 + }
1977 + modal.classList.remove('mxchat-instructions-show');
1978 + }
1979 +
1980 + // Close modal events
1981 + if (modalClose) {
1982 + modalClose.addEventListener('click', function(e) {
1983 + closeModal(e);
1984 + });
1985 + }
1986 +
1987 + if (closeBtn) {
1988 + closeBtn.addEventListener('click', function(e) {
1989 + closeModal(e);
1990 + });
1991 + }
1992 +
1993 + // Close on backdrop click ONLY (not on hover)
1994 + modal.addEventListener('click', function(e) {
1995 + // Only close if clicking directly on the overlay, not on child elements
1996 + if (e.target === modal) {
1997 + closeModal(e);
1998 + }
1999 + });
2000 +
2001 + // Prevent modal content clicks from closing the modal
2002 + if (modalContent) {
2003 + modalContent.addEventListener('click', function(e) {
2004 + e.stopPropagation();
2005 + });
2006 + }
2007 +
2008 + // Close on escape key
2009 + document.addEventListener('keydown', function(e) {
2010 + if (e.key === 'Escape' && modal.classList.contains('mxchat-instructions-show')) {
2011 + closeModal();
2012 + }
2013 + });
2014 +
2015 + // Copy functionality
2016 + if (copyBtn && instructionsContent) {
2017 + copyBtn.addEventListener('click', function(e) {
2018 + e.preventDefault();
2019 + e.stopPropagation();
2020 +
2021 + var text = instructionsContent.textContent;
2022 +
2023 + if (navigator.clipboard) {
2024 + navigator.clipboard.writeText(text).then(function() {
2025 + showCopySuccess();
2026 + }).catch(function() {
2027 + fallbackCopy(text);
2028 + });
2029 + } else {
2030 + fallbackCopy(text);
2031 + }
2032 + });
2033 + }
2034 +
2035 + function fallbackCopy(text) {
2036 + var textArea = document.createElement('textarea');
2037 + textArea.value = text;
2038 + textArea.style.position = 'fixed';
2039 + textArea.style.left = '-999999px';
2040 + textArea.style.top = '-999999px';
2041 + document.body.appendChild(textArea);
2042 + textArea.select();
2043 + try {
2044 + document.execCommand('copy');
2045 + showCopySuccess();
2046 + } catch (err) {
2047 + console.error('Copy failed');
2048 + }
2049 + document.body.removeChild(textArea);
2050 + }
2051 +
2052 + function showCopySuccess() {
2053 + var originalText = copyBtn.innerHTML;
2054 + 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!';
2055 +
2056 + setTimeout(function() {
2057 + copyBtn.innerHTML = originalText;
2058 + }, 2000);
2059 + }
2060 +});