PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.1.4
MxChat – AI Chatbot & Content Generation for WordPress v2.1.4
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 +1488 -51 2.0.52.1.4 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
@@ -60,11 +60,122 @@
60 60 // Focus the textarea
61 61 phrasesField.focus();
62 62 }
63 63
64 +// Updated mxchatOpenActionModal function to integrate with the new selector
65 +function mxchatOpenActionModal(isEdit = false, actionId = '', label = '', phrases = '', threshold = 85, callbackFunction = '') {
66 + const modal = document.getElementById('mxchat-action-modal');
67 + if (!modal) return;
68 +
69 + // Get form fields
70 + const actionIdField = document.getElementById('edit_action_id');
71 + const labelField = document.getElementById('intent_label');
72 + const phrasesField = document.getElementById('action_phrases');
73 + const formActionType = document.getElementById('form_action_type');
74 + const callbackGroup = document.getElementById('callback_selection_group');
75 + const callbackSelect = document.getElementById('callback_function');
76 + const saveButton = document.getElementById('mxchat-save-action-btn');
77 + const nonceContainer = document.getElementById('action-nonce-container');
78 + const thresholdSlider = document.getElementById('similarity_threshold');
79 + const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
80 +
81 + // Set up modal for edit or create
82 + if (isEdit) {
83 + saveButton.textContent = 'Update Action';
84 + formActionType.value = 'mxchat_edit_intent';
85 + actionIdField.value = actionId;
86 + labelField.value = label;
87 + phrasesField.value = phrases;
88 + callbackGroup.style.display = 'none'; // Hide callback selection when editing
89 + thresholdSlider.value = threshold; // Set the current threshold value
90 + thresholdDisplay.textContent = threshold + '%'; // Update display
91 +
92 + // Remove the required attribute when editing
93 + callbackSelect.removeAttribute('required');
94 +
95 + // Update the nonce field for editing
96 + nonceContainer.innerHTML = ''; // Clear existing nonce
97 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.edit_intent_nonce) {
98 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.edit_intent_nonce}">`;
99 + }
100 + } else {
101 + saveButton.textContent = 'Save Action';
102 + formActionType.value = 'mxchat_add_intent';
103 + actionIdField.value = '';
104 + labelField.value = '';
105 + phrasesField.value = '';
106 + callbackGroup.style.display = 'block'; // Show callback selection when creating
107 + thresholdSlider.value = 85; // Default value for new actions
108 + thresholdDisplay.textContent = '85%'; // Default display
109 +
110 + // Ensure the required attribute is present when adding
111 + callbackSelect.setAttribute('required', 'required');
112 +
113 + // Update the nonce field for adding
114 + nonceContainer.innerHTML = ''; // Clear existing nonce
115 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.add_intent_nonce) {
116 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.add_intent_nonce}">`;
117 + }
118 + }
119 +
120 + // Show modal with animation
121 + modal.style.display = 'flex';
122 + requestAnimationFrame(() => {
123 + modal.classList.add('active');
124 + });
125 +
126 + // Set up close handlers
127 + const closeModal = () => {
128 + modal.classList.remove('active');
129 + setTimeout(() => {
130 + modal.style.display = 'none';
131 + }, 300); // Match the CSS transition time
132 + };
133 +
134 + // Close button handler
135 + const closeBtn = modal.querySelector('.mxchat-modal-close');
136 + if (closeBtn) {
137 + closeBtn.onclick = closeModal;
138 + }
139 +
140 + // Cancel button handler
141 + const cancelBtn = modal.querySelector('.mxchat-modal-cancel');
142 + if (cancelBtn) {
143 + cancelBtn.onclick = closeModal;
144 + }
145 +
146 + // Click outside modal to close
147 + modal.onclick = (e) => {
148 + if (e.target === modal) {
149 + closeModal();
150 + }
151 + };
152 +
153 + // Escape key to close modal
154 + document.addEventListener('keydown', function(e) {
155 + if (e.key === 'Escape' && modal.classList.contains('active')) {
156 + closeModal();
157 + }
158 + }, { once: true });
159 +
160 + // Focus the first field
161 + labelField.focus();
162 +
163 + // Dispatch an event for the action type selector to catch
164 + const event = new CustomEvent('mxchatModalOpened', {
165 + detail: {
166 + isEdit: isEdit,
167 + callbackFunction: callbackFunction || (isEdit ? callbackSelect.value : '')
168 + }
169 + });
170 + document.dispatchEvent(event);
171 +
172 + return closeModal; // Return close function for external use
173 +}
174 +
64 175 // Initialize event listeners
65 176 document.addEventListener('DOMContentLoaded', () => {
66 - // Set up edit button handlers
177 + // Set up edit button handlers for intents
67 178 document.querySelectorAll('.mxchat-edit-button').forEach(button => {
68 179 button.onclick = () => {
69 180 const intentId = button.dataset.intentId;
70 181 const phrases = button.dataset.phrases;
@@ -70,8 +181,158 @@
70 181 const phrases = button.dataset.phrases;
71 182 mxchatOpenEditModal(intentId, phrases);
72 183 };
73 184 });
185 +
186 + // Set up edit button handlers for actions (new functionality)
187 + document.querySelectorAll('.mxchat-action-card .mxchat-edit-button').forEach(button => {
188 + button.onclick = () => {
189 + const actionId = button.dataset.actionId;
190 + const phrases = button.dataset.phrases;
191 + const label = button.dataset.label;
192 + const threshold = button.dataset.threshold || 85;
193 + const callbackFunction = button.dataset.callbackFunction; // Add this data attribute
194 + mxchatOpenActionModal(true, actionId, label, phrases, threshold, callbackFunction);
195 + };
196 + });
197 +
198 + // Set up add new action buttons (new functionality)
199 + const addActionBtn = document.getElementById('mxchat-add-action-btn');
200 + if (addActionBtn) {
201 + addActionBtn.onclick = () => mxchatOpenActionModal();
202 + }
203 +
204 + const createFirstAction = document.getElementById('mxchat-create-first-action');
205 + if (createFirstAction) {
206 + createFirstAction.onclick = () => mxchatOpenActionModal();
207 + }
208 +
209 + // Setup category-specific new action buttons (new functionality)
210 + document.querySelectorAll('.mxchat-new-action-button').forEach(button => {
211 + button.onclick = () => {
212 + const category = button.closest('.mxchat-new-action-card').dataset.category;
213 + const closeModal = mxchatOpenActionModal();
214 +
215 + // Pre-select the appropriate callback based on category
216 + if (category) {
217 + const callbackSelect = document.getElementById('callback_function');
218 + if (callbackSelect) {
219 + setTimeout(() => {
220 + // Map categories to default callbacks
221 + const categoryToCallback = {
222 + 'data_collection': 'mxchat_handle_form_collection',
223 + 'integrations': 'mxchat_handle_slack_message',
224 + 'custom_actions': 'mxchat_handle_custom_action',
225 + 'recommendations': 'mxchat_handle_product_recommendations'
226 + // Add more mappings as needed
227 + };
228 +
229 + if (categoryToCallback[category]) {
230 + callbackSelect.value = categoryToCallback[category];
231 + }
232 + }, 100);
233 + }
234 + }
235 + };
236 + });
237 +
238 + // Handle action toggle switches (new functionality)
239 + document.querySelectorAll('.mxchat-action-toggle').forEach(toggle => {
240 + toggle.onchange = function() {
241 + const actionId = this.dataset.actionId;
242 + const isEnabled = this.checked;
243 +
244 + // Show loading indicator
245 + const loadingEl = document.getElementById('mxchat-action-loading');
246 + if (loadingEl) loadingEl.style.display = 'flex';
247 +
248 + // Send AJAX request to update status
249 + fetch(ajaxurl, {
250 + method: 'POST',
251 + headers: {
252 + 'Content-Type': 'application/x-www-form-urlencoded',
253 + },
254 + body: new URLSearchParams({
255 + action: 'mxchat_toggle_action',
256 + intent_id: actionId,
257 + enabled: isEnabled ? 1 : 0,
258 + nonce: mxchatAdmin.toggle_action_nonce // Use the correct nonce
259 + })
260 + })
261 + .then(response => response.json())
262 + .then(data => {
263 + if (!data.success) {
264 + alert('Failed to update action status: ' + (data.data?.message || 'Unknown error'));
265 + this.checked = !isEnabled; // Revert the toggle
266 + }
267 + })
268 + .catch(error => {
269 + console.error('Error:', error);
270 + alert('Server error. Please try again.');
271 + this.checked = !isEnabled; // Revert the toggle
272 + })
273 + .finally(() => {
274 + if (loadingEl) loadingEl.style.display = 'none';
275 + });
276 + };
277 + });
278 +
279 + // Handle threshold sliders in action cards (new functionality)
280 + document.querySelectorAll('.mxchat-threshold-slider').forEach(slider => {
281 + slider.oninput = function() {
282 + const actionId = this.id.replace('intent_threshold_', '');
283 + document.getElementById('threshold_output_' + actionId).textContent = this.value + '%';
284 + };
285 + });
286 +
287 + // Handle threshold save buttons in action cards (new functionality)
288 + document.querySelectorAll('.mxchat-threshold-save').forEach(button => {
289 + button.onclick = function(e) {
290 + e.preventDefault();
291 + const form = this.closest('form');
292 + const intentId = form.querySelector('input[name="intent_id"]').value;
293 + const threshold = form.querySelector('input[name="intent_threshold"]').value;
294 + const nonce = form.querySelector('input[name="_wpnonce"]').value;
295 +
296 + // Show loading indicator
297 + const loadingEl = document.getElementById('mxchat-action-loading');
298 + if (loadingEl) loadingEl.style.display = 'flex';
299 +
300 + // Send AJAX request
301 + fetch(ajaxurl, {
302 + method: 'POST',
303 + headers: {
304 + 'Content-Type': 'application/x-www-form-urlencoded',
305 + },
306 + body: new URLSearchParams({
307 + action: 'mxchat_update_intent_threshold',
308 + intent_id: intentId,
309 + intent_threshold: threshold,
310 + _wpnonce: nonce
311 + })
312 + })
313 + .then(response => response.json())
314 + .then(data => {
315 + if (data.success) {
316 + // Visual feedback of success
317 + const card = this.closest('.mxchat-action-card');
318 + card.style.background = 'rgba(120, 115, 245, 0.1)';
319 + setTimeout(() => {
320 + card.style.background = 'white';
321 + }, 300);
322 + } else {
323 + alert('Failed to update threshold: ' + (data.data?.message || 'Unknown error'));
324 + }
325 + })
326 + .catch(error => {
327 + console.error('Error:', error);
328 + alert('Server error. Please try again.');
329 + })
330 + .finally(() => {
331 + if (loadingEl) loadingEl.style.display = 'none';
332 + });
333 + };
334 + });
74 335 });
75 336
76 337 jQuery(document).ready(function($) {
77 338 // Ensure we have a debounce function (use lodash if available, otherwise use our implementation)
@@ -119,11 +380,12 @@
119 380
120 381 // Determine which AJAX action and nonce to use:
121 382 var ajaxAction, nonce;
122 383 // 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' ) {
384 + if (name.indexOf('mxchat_prompts_options') !== -1 ||
385 + name === 'mxchat_auto_sync_posts' ||
386 + name === 'mxchat_auto_sync_pages' ||
387 + name.indexOf('mxchat_auto_sync_') === 0) { // Modified to catch all auto-sync fields
126 388 ajaxAction = 'mxchat_save_prompts_setting';
127 389 nonce = mxchatPromptsAdmin.prompts_setting_nonce;
128 390 } else {
129 391 // Otherwise, use the existing AJAX action.
@@ -195,20 +457,22 @@
195 457 // Position feedback container
196 458 $colorField.closest('.wp-picker-container').after(feedbackContainer);
197 459 feedbackContainer.append(spinner);
198 460
199 - // Determine AJAX action and nonce for color fields:
461 + // Determine which AJAX action and nonce to use:
200 462 var ajaxAction, nonce;
201 - if ( name.indexOf('mxchat_prompts_options') !== -1 ||
202 - name === 'mxchat_auto_sync_posts' ||
203 - name === 'mxchat_auto_sync_pages' ) {
463 + // Use the new AJAX action for submenu fields:
464 + if (name.indexOf('mxchat_prompts_options') !== -1 ||
465 + name === 'mxchat_auto_sync_posts' ||
466 + name === 'mxchat_auto_sync_pages' ||
467 + name.indexOf('mxchat_auto_sync_') === 0) { // Modified to catch all auto-sync fields
204 468 ajaxAction = 'mxchat_save_prompts_setting';
205 469 nonce = mxchatPromptsAdmin.prompts_setting_nonce;
206 470 } else {
471 + // Otherwise, use the existing AJAX action.
207 472 ajaxAction = 'mxchat_save_setting';
208 473 nonce = mxchatAdmin.setting_nonce;
209 474 }
210 -
211 475 // AJAX save request
212 476 $.ajax({
213 477 url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url,
214 478 type: 'POST',
@@ -240,9 +504,9 @@
240 504 });
241 505 });
242 506
243 507 // Reinitialize color pickers when switching tabs
244 - $('.mxchat-nav-tab').on('click.mxchat', function() {
508 + $('.mxchat-tab-button').on('click.mxchat', function() {
245 509 setTimeout(function() {
246 510 $('.my-color-field:visible').wpColorPicker('close');
247 511 }, 100);
248 512 });
@@ -250,24 +514,19 @@
250 514
251 515 // Initialize tabs system
252 516 function initTabs() {
253 517 // Remove any existing handlers first
254 - $('.mxchat-nav-tab').off('click.mxchat');
518 + $('.mxchat-tab-button').off('click.mxchat');
255 519
256 520 // Add new click handlers
257 - $('.mxchat-nav-tab').on('click.mxchat', function(e) {
521 + $('.mxchat-tab-button').on('click.mxchat', function(e) {
258 522 e.preventDefault();
259 523 e.stopPropagation();
260 524
261 525 var $this = $(this);
262 526
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';
269 - }
527 + // Get tab ID from data-tab attribute
528 + var tabId = $this.data('tab') || 'chatbot';
270 529
271 530 // Safety check for empty tabId
272 531 if (!tabId) {
273 532 console.warn('No tab identifier found');
@@ -273,17 +532,17 @@
273 532 console.warn('No tab identifier found');
274 533 return;
275 534 }
276 535
277 - // Update tabs
278 - $('.mxchat-nav-tab').removeClass('mxchat-nav-tab-active');
279 - $this.addClass('mxchat-nav-tab-active');
536 + // Update tab buttons
537 + $('.mxchat-tab-button').removeClass('active');
538 + $this.addClass('active');
280 539
281 540 // Update content areas - with safety check
282 - $('.mxchat-tab-content').removeClass('active').hide();
541 + $('.mxchat-tab-content').removeClass('active');
283 542 var $targetTab = $('#' + tabId);
284 543 if ($targetTab.length) {
285 - $targetTab.addClass('active').show();
544 + $targetTab.addClass('active');
286 545
287 546 // Store active tab
288 547 try {
289 548 localStorage.setItem('mxchat_active_tab', tabId);
@@ -303,14 +562,14 @@
303 562 // Activate initial tab
304 563 try {
305 564 var savedTab = localStorage.getItem('mxchat_active_tab');
306 565 if (savedTab && $('#' + savedTab).length > 0) {
307 - $('.mxchat-nav-tab[href="#' + savedTab + '"]').trigger('click.mxchat');
566 + $('.mxchat-tab-button[data-tab="' + savedTab + '"]').trigger('click.mxchat');
308 567 } else {
309 - $('.mxchat-nav-tab').first().trigger('click.mxchat');
568 + $('.mxchat-tab-button').first().trigger('click.mxchat');
310 569 }
311 570 } catch (e) {
312 - $('.mxchat-nav-tab').first().trigger('click.mxchat');
571 + $('.mxchat-tab-button').first().trigger('click.mxchat');
313 572 }
314 573
315 574 // Attach edit modal event handler
316 575 $(document).on('click', '.mxchat-edit-button', function() {
@@ -318,36 +577,512 @@
318 577 const phrases = $(this).data('phrases');
319 578 mxchatOpenEditModal(intentId, phrases);
320 579 });
321 580
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');
329 - } else {
330 - inputField.attr('type', 'password');
331 - $(this).text('Show');
581 +// Toggle visibility handlers
582 +function toggleVisibility(selector) {
583 + $(selector).on('click', function() {
584 + var inputField = $(this).prev('input');
585 + if (inputField.attr('type') === 'password') {
586 + inputField.attr('type', 'text');
587 + $(this).text('Hide');
588 + } else {
589 + inputField.attr('type', 'password');
590 + $(this).text('Show');
591 + }
592 + });
593 +}
594 +
595 +// Initialize all toggle visibility buttons
596 +[
597 + '#toggleApiKeyVisibility',
598 + '#toggleWooCommerceSecretVisibility',
599 + '#toggleVoyageAPIKeyVisibility',
600 + '#toggleLoopsApiKeyVisibility',
601 + '#toggleXaiApiKeyVisibility',
602 + '#toggleClaudeApiKeyVisibility',
603 + '#toggleBraveApiKeyVisibility',
604 + '#toggleWebhookUrlVisibility',
605 + '#toggleSecretKeyVisibility',
606 + '#toggleBotTokenVisibility',
607 + '#toggleDeepSeekApiKeyVisibility',
608 + '#toggleGeminiApiKeyVisibility' // Added Gemini toggle
609 +].forEach(toggleVisibility);
610 +
611 +// Handle API key visibility based on model selection
612 +function setupAPIKeyVisibility() {
613 + // Cache the selectors
614 + const $chatModelSelect = $('#model');
615 + const $embeddingModelSelect = $('#embedding_model');
616 +
617 + // First, locate and mark the API key rows
618 + setupAPIKeyRows();
619 +
620 + // Initial setup based on current selections
621 + updateApiKeyVisibility();
622 +
623 + // Listen for changes to the model selectors
624 + $chatModelSelect.on('change', updateApiKeyVisibility);
625 + $embeddingModelSelect.on('change', updateApiKeyVisibility);
626 +
627 + /**
628 + * Locate and mark rows that contain API key fields
629 + */
630 + function setupAPIKeyRows() {
631 + // Find key rows by their field IDs
632 + const providerMap = {
633 + 'api_key': 'openai',
634 + 'xai_api_key': 'xai',
635 + 'claude_api_key': 'claude',
636 + 'deepseek_api_key': 'deepseek',
637 + 'voyage_api_key': 'voyage',
638 + 'gemini_api_key': 'gemini' // Added Gemini API key mapping
639 + };
640 +
641 + $.each(providerMap, function(fieldId, provider) {
642 + const $field = $('#' + fieldId);
643 + if ($field.length) {
644 + const $row = $field.closest('tr');
645 + $row.addClass('mxchat-setting-row');
646 + $row.attr('data-provider', provider);
332 647 }
333 648 });
334 649 }
335 650
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);
651 + /**
652 + * Updates the visibility of API key fields based on current model selections
653 + */
654 + function updateApiKeyVisibility() {
655 + const chatModel = $chatModelSelect.val();
656 + const embeddingModel = $embeddingModelSelect.val();
657 +
658 + // Determine which providers are needed
659 + const isOpenAIChat = chatModel && chatModel.startsWith('gpt-');
660 + const isXAI = chatModel && chatModel.startsWith('grok-');
661 + const isClaude = chatModel && chatModel.startsWith('claude-');
662 + const isDeepSeek = chatModel && chatModel.startsWith('deepseek-');
663 + const isGemini = chatModel && chatModel.startsWith('gemini-'); // Added Gemini detection
664 +
665 + const isOpenAIEmbedding = embeddingModel && embeddingModel.startsWith('text-embedding-');
666 + const isVoyage = embeddingModel && embeddingModel.startsWith('voyage-');
667 +
668 + // Update API key visibility for each provider
669 + updateWrapperVisibility('openai', isOpenAIChat || isOpenAIEmbedding);
670 + updateWrapperVisibility('xai', isXAI);
671 + updateWrapperVisibility('claude', isClaude);
672 + updateWrapperVisibility('deepseek', isDeepSeek);
673 + updateWrapperVisibility('voyage', isVoyage);
674 + updateWrapperVisibility('gemini', isGemini); // Added Gemini visibility update
675 +
676 + // Update provider-specific notices for OpenAI
677 + if (isOpenAIChat && isOpenAIEmbedding) {
678 + $('div[data-provider="openai"] .api-key-notice').text(
679 + 'Required for your selected chat model and embedding model. Important: You must add credits before use.'
680 + );
681 + } else if (isOpenAIChat) {
682 + $('div[data-provider="openai"] .api-key-notice').text(
683 + 'Required for your selected chat model. Important: You must add credits before use.'
684 + );
685 + } else if (isOpenAIEmbedding) {
686 + $('div[data-provider="openai"] .api-key-notice').text(
687 + 'Required for your selected embedding model. Important: You must add credits before use.'
688 + );
689 + }
690 + }
349 691
692 + /**
693 + * Updates visibility of a specific provider's API key wrapper
694 + */
695 + function updateWrapperVisibility(provider, isVisible) {
696 + const $row = $('tr.mxchat-setting-row[data-provider="' + provider + '"]');
697 +
698 + if (!$row.length) {
699 + console.warn('API key row not found for provider: ' + provider);
700 + return;
701 + }
702 +
703 + if (isVisible) {
704 + $row.show();
705 + if (!$row.hasClass('highlighted')) {
706 + $row.addClass('highlighted');
707 + setTimeout(() => {
708 + $row.removeClass('highlighted');
709 + }, 1500);
710 + }
711 + } else {
712 + $row.hide();
713 + }
714 + }
715 +}
716 +
717 +// Add this to your JavaScript file
718 +function setupMxChatModelSelector() {
719 + const $modelSelect = $('#model');
720 + const $modelSelectorButton = $('<button>', {
721 + type: 'button',
722 + id: 'mxchat_model_selector_btn',
723 + class: 'button-primary mxchat-model-selector-btn',
724 + text: 'Select AI Model'
725 + });
726 +
727 + // Replace the select dropdown with a button
728 + $modelSelect.hide().after($modelSelectorButton);
729 +
730 + // Update button text to show currently selected model
731 + function updateButtonText() {
732 + const selectedModel = $modelSelect.val();
733 + const selectedModelText = $modelSelect.find('option:selected').text();
734 + $modelSelectorButton.text(selectedModelText);
735 + }
736 +
737 + // Initialize button text
738 + updateButtonText();
739 +
740 + // Create and append modal HTML
741 + const modelSelectorModal = `
742 + <div id="mxchat_model_selector_modal" class="mxchat-model-selector-modal">
743 + <div class="mxchat-model-selector-modal-content">
744 + <div class="mxchat-model-selector-modal-header">
745 + <h3>Select AI Model</h3>
746 + <span class="mxchat-model-selector-modal-close">&times;</span>
747 + </div>
748 + <div class="mxchat-model-selector-modal-body">
749 + <div class="mxchat-model-selector-search-container">
750 + <input type="text" id="mxchat_model_search_input" class="mxchat-model-search-input" placeholder="Search models...">
751 + </div>
752 + <div class="mxchat-model-selector-categories">
753 + <button class="mxchat-model-category-btn active" data-category="all">All</button>
754 + <button class="mxchat-model-category-btn" data-category="gemini">Google Gemini</button>
755 + <button class="mxchat-model-category-btn" data-category="openai">OpenAI</button>
756 + <button class="mxchat-model-category-btn" data-category="claude">Claude</button>
757 + <button class="mxchat-model-category-btn" data-category="xai">X.AI</button>
758 + <button class="mxchat-model-category-btn" data-category="deepseek">DeepSeek</button>
759 + </div>
760 + <div class="mxchat-model-selector-grid" id="mxchat_models_grid"></div>
761 + </div>
762 + <div class="mxchat-model-selector-modal-footer">
763 + <button id="mxchat_cancel_model_selection" class="button mxchat-model-cancel-btn">Cancel</button>
764 + </div>
765 + </div>
766 + </div>
767 + `;
768 +
769 + $('body').append(modelSelectorModal);
770 +
771 + // Populate models grid
772 + function populateModelsGrid(filter = '', category = 'all') {
773 + const $grid = $('#mxchat_models_grid');
774 + $grid.empty();
775 +
776 + const models = {
777 + gemini: [
778 + { value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash', description: 'Next-Gen features, speed & multimodal generation' },
779 + { value: 'gemini-2.0-flash-lite', label: 'Gemini 2.0 Flash-Lite', description: 'Cost-efficient with low latency' },
780 + { value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro', description: 'Complex reasoning tasks requiring more intelligence' },
781 + { value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash', description: 'Fast and versatile performance' },
782 + ],
783 + openai: [
784 + { value: 'gpt-4o', label: 'GPT-4o', description: 'Recommended for most use cases' },
785 + { value: 'gpt-4o-mini', label: 'GPT-4o Mini', description: 'Fast and lightweight' },
786 + { value: 'gpt-4-turbo', label: 'GPT-4 Turbo', description: 'High-performance model' },
787 + { value: 'gpt-4', label: 'GPT-4', description: 'High intelligence model' },
788 + { value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', description: 'Affordable and fast' },
789 + ],
790 + claude: [
791 + { value: 'claude-3-7-sonnet-20250219', label: 'Claude 3.7 Sonnet', description: 'Most intelligent Claude model' },
792 + { value: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet', description: 'Intelligent and balanced' },
793 + { value: 'claude-3-opus-20240229', label: 'Claude 3 Opus', description: 'Highly complex tasks' },
794 + { value: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet', description: 'Balanced performance' },
795 + { value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', description: 'Fastest Claude model' },
796 + ],
797 + xai: [
798 + { value: 'grok-beta', label: 'grok-beta', description: 'Early beta version' },
799 + { value: 'grok-2', label: 'Grok 2', description: 'Latest X.AI model' },
800 + ],
801 + deepseek: [
802 + { value: 'deepseek-chat', label: 'DeepSeek-V3', description: 'Advanced AI assistant' },
803 + ],
804 + };
805 +
806 + let allModels = [];
807 + Object.keys(models).forEach(key => {
808 + if (category === 'all' || category === key) {
809 + allModels = allModels.concat(models[key]);
810 + }
811 + });
812 +
813 + // Filter by search term if present
814 + if (filter) {
815 + const lowerFilter = filter.toLowerCase();
816 + allModels = allModels.filter(model =>
817 + model.label.toLowerCase().includes(lowerFilter) ||
818 + model.description.toLowerCase().includes(lowerFilter)
819 + );
820 + }
821 +
822 + // Create model cards
823 + allModels.forEach(model => {
824 + const isSelected = $modelSelect.val() === model.value;
825 + const $modelCard = $(`
826 + <div class="mxchat-model-selector-card ${isSelected ? 'mxchat-model-selected' : ''}" data-value="${model.value}">
827 + <div class="mxchat-model-selector-icon">${getModelIcon(model.value)}</div>
828 + <div class="mxchat-model-selector-info">
829 + <h4 class="mxchat-model-selector-title">${model.label}</h4>
830 + <p class="mxchat-model-selector-description">${model.description}</p>
831 + </div>
832 + ${isSelected ? '<div class="mxchat-model-selector-checkmark">✓</div>' : ''}
833 + </div>
834 + `);
835 + $grid.append($modelCard);
836 + });
837 + }
838 +
839 + // Helper function to get icon for each model
840 + function getModelIcon(modelValue) {
841 + if (modelValue.startsWith('gemini-')) return '<span class="dashicons dashicons-google mxchat-model-icon-gemini"></span>';
842 + if (modelValue.startsWith('gpt-')) return '<span class="dashicons dashicons-superhero mxchat-model-icon-openai"></span>';
843 + if (modelValue.startsWith('claude-')) return '<span class="dashicons dashicons-buddicons-buddypress-logo mxchat-model-icon-claude"></span>';
844 + if (modelValue.startsWith('grok-')) return '<span class="dashicons dashicons-twitter mxchat-model-icon-xai"></span>';
845 + if (modelValue.startsWith('deepseek-')) return '<span class="dashicons dashicons-visibility mxchat-model-icon-deepseek"></span>';
846 + return '<span class="dashicons dashicons-admin-generic mxchat-model-icon-generic"></span>';
847 + }
848 +
849 + // Event handlers
850 + $modelSelectorButton.on('click', function() {
851 + $('#mxchat_model_selector_modal').show();
852 + populateModelsGrid('', 'all');
853 + });
854 +
855 + $('.mxchat-model-selector-modal-close, #mxchat_cancel_model_selection').on('click', function() {
856 + $('#mxchat_model_selector_modal').hide();
857 + });
858 +
859 + $('.mxchat-model-category-btn').on('click', function() {
860 + $('.mxchat-model-category-btn').removeClass('active');
861 + $(this).addClass('active');
862 + const category = $(this).data('category');
863 + const searchTerm = $('#mxchat_model_search_input').val();
864 + populateModelsGrid(searchTerm, category);
865 + });
866 +
867 + $('#mxchat_model_search_input').on('input', function() {
868 + const searchTerm = $(this).val();
869 + const activeCategory = $('.mxchat-model-category-btn.active').data('category');
870 + populateModelsGrid(searchTerm, activeCategory);
871 + });
872 +
873 + $(document).on('click', '.mxchat-model-selector-card', function() {
874 + const modelValue = $(this).data('value');
875 + $modelSelect.val(modelValue).trigger('change');
876 + updateButtonText();
877 + $('#mxchat_model_selector_modal').hide();
878 + });
879 +
880 + // Close modal when clicking outside
881 + $(window).on('click', function(event) {
882 + if ($(event.target).is('#mxchat_model_selector_modal')) {
883 + $('#mxchat_model_selector_modal').hide();
884 + }
885 + });
886 +}
887 +
888 +// Embedding model selector - completely separate from chat model selector
889 +function setupMxChatEmbeddingModelSelector() {
890 + const $embeddingModelSelect = $('#embedding_model');
891 +
892 + // Skip if the element doesn't exist on the page
893 + if ($embeddingModelSelect.length === 0) {
894 + return;
895 + }
896 +
897 + const $embeddingModelSelectorButton = $('<button>', {
898 + type: 'button',
899 + id: 'mxchat_embedding_model_selector_btn',
900 + class: 'button-primary mxchat-embedding-model-selector-btn', // Changed class name to be more specific
901 + text: 'Select Embedding Model'
902 + });
903 +
904 + // Replace the select dropdown with a button
905 + $embeddingModelSelect.hide().after($embeddingModelSelectorButton);
906 +
907 + // Update button text to show currently selected model
908 + function updateButtonText() {
909 + const selectedModel = $embeddingModelSelect.val();
910 + const selectedModelText = $embeddingModelSelect.find('option:selected').text();
911 + $embeddingModelSelectorButton.text(selectedModelText);
912 + }
913 +
914 + // Initialize button text
915 + updateButtonText();
916 +
917 + // Create a unique ID for the modal to avoid conflicts
918 + const embeddingModalId = 'mxchat_embedding_model_selector_modal';
919 +
920 + // Create and append modal HTML with unique IDs
921 + const embeddingModelSelectorModal = `
922 + <div id="${embeddingModalId}" class="mxchat-embedding-model-selector-modal">
923 + <div class="mxchat-embedding-model-selector-modal-content">
924 + <div class="mxchat-embedding-model-selector-modal-header">
925 + <h3>Select Embedding Model</h3>
926 + <span class="mxchat-embedding-model-selector-modal-close">&times;</span>
927 + </div>
928 + <div class="mxchat-embedding-model-selector-modal-body">
929 + <div class="mxchat-embedding-model-selector-search-container">
930 + <input type="text" id="mxchat_embedding_model_search_input" class="mxchat-embedding-model-search-input" placeholder="Search models...">
931 + </div>
932 + <div class="mxchat-embedding-model-selector-categories">
933 + <button class="mxchat-embedding-model-category-btn active" data-category="all">All</button>
934 + <button class="mxchat-embedding-model-category-btn" data-category="openai">OpenAI</button>
935 + <button class="mxchat-embedding-model-category-btn" data-category="voyage">Voyage AI</button>
936 + </div>
937 + <div class="mxchat-embedding-model-selector-grid" id="mxchat_embedding_models_grid"></div>
938 + </div>
939 + <div class="mxchat-embedding-model-selector-modal-footer">
940 + <button id="mxchat_cancel_embedding_model_selection" class="button mxchat-embedding-model-cancel-btn">Cancel</button>
941 + </div>
942 + </div>
943 + </div>
944 + `;
945 +
946 + // Use jQuery's append to ensure it doesn't clash with existing modals
947 + $('body').append(embeddingModelSelectorModal);
948 +
949 + // Populate models grid
950 + function populateEmbeddingModelsGrid(filter = '', category = 'all') {
951 + const $grid = $('#mxchat_embedding_models_grid');
952 + $grid.empty();
953 +
954 +// Define embedding models with descriptions and context lengths
955 +const models = {
956 + openai: [
957 + {
958 + value: 'text-embedding-3-small',
959 + label: 'TE3 Small',
960 + description: 'Fast and cost-effective embeddings (1536 dimensions, 8K context)'
961 + },
962 + {
963 + value: 'text-embedding-ada-002',
964 + label: 'Ada 2',
965 + description: 'Balanced performance embeddings (1536 dimensions, 8K context)'
966 + },
967 + {
968 + value: 'text-embedding-3-large',
969 + label: 'TE3 Large',
970 + description: 'High-performance embeddings (3072 dimensions, 8K context)'
971 + }
972 + ],
973 + voyage: [
974 + {
975 + value: 'voyage-3-large',
976 + label: 'Voyage-3 Large',
977 + description: 'Advanced semantic search embeddings (2048 dimensions, 32K context)'
978 + }
979 + ]
980 +};
981 +
982 + let allModels = [];
983 + Object.keys(models).forEach(key => {
984 + if (category === 'all' || category === key) {
985 + allModels = allModels.concat(models[key]);
986 + }
987 + });
988 +
989 + // Filter by search term if present
990 + if (filter) {
991 + const lowerFilter = filter.toLowerCase();
992 + allModels = allModels.filter(model =>
993 + model.label.toLowerCase().includes(lowerFilter) ||
994 + model.description.toLowerCase().includes(lowerFilter)
995 + );
996 + }
997 +
998 + // Create model cards
999 + allModels.forEach(model => {
1000 + const isSelected = $embeddingModelSelect.val() === model.value;
1001 + const providerClass = model.value.startsWith('voyage-') ? 'mxchat-embedding-model-provider-voyage' : 'mxchat-embedding-model-provider-openai';
1002 +
1003 + const $modelCard = $(`
1004 + <div class="mxchat-embedding-model-selector-card ${isSelected ? 'mxchat-embedding-model-selected' : ''} ${providerClass}" data-value="${model.value}">
1005 + <div class="mxchat-embedding-model-selector-icon">
1006 + ${model.value.startsWith('voyage-') ?
1007 + '<span class="dashicons dashicons-chart-line mxchat-embedding-model-icon-voyage"></span>' :
1008 + '<span class="dashicons dashicons-superhero mxchat-embedding-model-icon-openai"></span>'
1009 + }
1010 + </div>
1011 + <div class="mxchat-embedding-model-selector-info">
1012 + <h4 class="mxchat-embedding-model-selector-title">${model.label}</h4>
1013 + <p class="mxchat-embedding-model-selector-description">${model.description}</p>
1014 + </div>
1015 + ${isSelected ? '<div class="mxchat-embedding-model-selector-checkmark">✓</div>' : ''}
1016 + </div>
1017 + `);
1018 + $grid.append($modelCard);
1019 + });
1020 + }
1021 +
1022 + // Event handlers - use namespaced events to avoid conflicts
1023 + $embeddingModelSelectorButton.on('click.embeddingModelSelector', function(e) {
1024 + e.stopPropagation(); // Prevent event bubbling
1025 + $('#' + embeddingModalId).show();
1026 + populateEmbeddingModelsGrid('', 'all');
1027 + });
1028 +
1029 + $('.mxchat-embedding-model-selector-modal-close, #mxchat_cancel_embedding_model_selection').on('click.embeddingModelSelector', function(e) {
1030 + e.stopPropagation(); // Prevent event bubbling
1031 + $('#' + embeddingModalId).hide();
1032 + });
1033 +
1034 + $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn').on('click.embeddingModelSelector', function(e) {
1035 + e.stopPropagation(); // Prevent event bubbling
1036 + $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn').removeClass('active');
1037 + $(this).addClass('active');
1038 + const category = $(this).data('category');
1039 + const searchTerm = $('#mxchat_embedding_model_search_input').val();
1040 + populateEmbeddingModelsGrid(searchTerm, category);
1041 + });
1042 +
1043 + $('#mxchat_embedding_model_search_input').on('input.embeddingModelSelector', function() {
1044 + const searchTerm = $(this).val();
1045 + const activeCategory = $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn.active').data('category');
1046 + populateEmbeddingModelsGrid(searchTerm, activeCategory);
1047 + });
1048 +
1049 + // Use a direct selector to avoid conflicts with other card elements
1050 + $(document).on('click.embeddingModelSelector', '.mxchat-embedding-model-selector-grid .mxchat-embedding-model-selector-card', function(e) {
1051 + e.stopPropagation(); // Prevent event bubbling
1052 + const modelValue = $(this).data('value');
1053 +
1054 + // Important: Only update this specific select element
1055 + $embeddingModelSelect.val(modelValue);
1056 +
1057 + // Manually trigger change only on this element
1058 + const changeEvent = new Event('change', { bubbles: true });
1059 + $embeddingModelSelect[0].dispatchEvent(changeEvent);
1060 +
1061 + // Update button text
1062 + updateButtonText();
1063 +
1064 + // Hide modal
1065 + $('#' + embeddingModalId).hide();
1066 + });
1067 +
1068 + // Close modal when clicking outside - use namespaced events
1069 + $(window).on('click.embeddingModelSelector', function(event) {
1070 + if ($(event.target).is('#' + embeddingModalId)) {
1071 + $('#' + embeddingModalId).hide();
1072 + }
1073 + });
1074 +}
1075 +
1076 +// Call this function after the DOM is fully loaded
1077 +$(document).ready(function() {
1078 + setupMxChatModelSelector();
1079 + setupMxChatEmbeddingModelSelector();
1080 +});
1081 +
1082 + // Initialize API key visibility
1083 + setupAPIKeyVisibility();
1084 +
350 1085 // Add Intent Form Submission
351 1086 $('#mxchat-add-intent-form').on('submit', function(event) {
352 1087 $('#mxchat-intent-loading').show();
353 1088 $('#mxchat-intent-loading-text').show();
@@ -561,10 +1296,712 @@
561 1296
562 1297 // Bind events
563 1298 $textarea.on('focus input', adjustTextareaHeight) // Expand on focus and input
564 1299 .on('blur', resetTextareaHeight); // Reset on blur
1300 +});
1301 +
1302 +document.addEventListener('DOMContentLoaded', function() {
1303 + //console.log('MXChat Action Modal JS Loaded');
565 1304
1305 + // Initialize the action modal functionality
1306 + initStepBasedActionModal();
566 1307
1308 + // Function to initialize the step-based action modal
1309 + function initStepBasedActionModal() {
1310 + // Get references to elements
1311 + const modal = document.getElementById('mxchat-action-modal');
1312 + if (!modal) {
1313 + console.error('Modal element not found!');
1314 + return;
1315 + }
1316 +
1317 + const actionStep1 = document.getElementById('mxchat-action-step-1');
1318 + const actionStep2 = document.getElementById('mxchat-action-step-2');
1319 + const backToStep1Btn = document.getElementById('mxchat-back-to-step-1');
1320 + const searchInput = document.getElementById('action-type-search');
1321 + const categoryButtons = modal.querySelectorAll('.mxchat-category-button');
1322 + const actionCards = modal.querySelectorAll('.mxchat-action-type-card');
1323 + const actionForm = document.getElementById('mxchat-action-form');
1324 + const callbackInput = document.getElementById('callback_function');
1325 + const actionIdField = document.getElementById('edit_action_id');
1326 + const labelField = document.getElementById('intent_label');
1327 + const phrasesField = document.getElementById('action_phrases');
1328 + const formActionType = document.getElementById('form_action_type');
1329 + const nonceContainer = document.getElementById('action-nonce-container');
1330 + const thresholdSlider = document.getElementById('similarity_threshold');
1331 + const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
1332 +
1333 + // Log the structure of one action card for debugging
1334 + if (actionCards.length > 0) {
1335 + //console.log('First action card data attributes:', actionCards[0].dataset);
1336 + //console.log('First action card HTML:', actionCards[0].outerHTML);
1337 + }
1338 +
1339 + // Add click event listeners to category buttons
1340 + categoryButtons.forEach(button => {
1341 + button.addEventListener('click', function() {
1342 + //console.log('Category button clicked:', this.dataset.category);
1343 +
1344 + // Remove active class from all buttons
1345 + categoryButtons.forEach(btn => btn.classList.remove('active'));
1346 +
1347 + // Add active class to clicked button
1348 + this.classList.add('active');
1349 +
1350 + // Get selected category
1351 + const category = this.dataset.category;
1352 +
1353 + // Filter action cards
1354 + filterActionCards(category, searchInput.value);
1355 + });
1356 + });
1357 +
1358 + // Add search functionality
1359 + if (searchInput) {
1360 + searchInput.addEventListener('input', function() {
1361 + // Get active category
1362 + const activeCategory = modal.querySelector('.mxchat-category-button.active')?.dataset.category || 'all';
1363 + //console.log('Search input changed, active category:', activeCategory);
1364 +
1365 + // Filter action cards
1366 + filterActionCards(activeCategory, this.value);
1367 + });
1368 + }
1369 +
1370 + // Add click event listeners to action cards with fallback support
1371 +// Add click event listeners to action cards
1372 +actionCards.forEach(card => {
1373 + card.addEventListener('click', function() {
1374 + // Get the action data
1375 + const isPro = this.dataset.pro === 'true';
1376 + const isInstalled = this.dataset.installed === 'true';
1377 + const addonName = this.dataset.addon || '';
1378 + const actionValue = this.dataset.value;
1379 + const actionLabel = this.dataset.label;
1380 + const actionIcon = this.querySelector('.dashicons').getAttribute('class').replace('dashicons dashicons-', '');
1381 + const actionDescription = this.querySelector('p').textContent;
1382 +
1383 + // Pro check using the proper detection method
1384 + const proIsActivated = typeof mxchatAdmin !== 'undefined' &&
1385 + (mxchatAdmin.is_activated === '1' ||
1386 + mxchatAdmin.is_activated === 'true' ||
1387 + mxchatAdmin.is_activated === true);
1388 +
1389 + // Handle different states
1390 + if (isPro && !proIsActivated) {
1391 + // Pro feature but no Pro license
1392 + showProFeatureNotice();
1393 + return;
1394 + }
1395 +
1396 + if (addonName && !isInstalled) {
1397 + // Add-on required but not installed
1398 + const addonDisplayName = this.querySelector('.mxchat-addon-info')?.textContent?.replace('Requires ', '') || addonName + ' Add-on';
1399 + showAddonRequiredNotice(addonDisplayName);
1400 + return;
1401 + }
1402 +
1403 + // If we get here, the action is available - proceed as normal
1404 + callbackInput.value = actionValue;
1405 +
1406 + // Update the selected action display in step 2
1407 + document.getElementById('selected-action-title').textContent = actionLabel;
1408 + document.getElementById('selected-action-description').textContent = actionDescription;
1409 + document.getElementById('selected-action-icon').innerHTML =
1410 + `<span class="dashicons dashicons-${actionIcon}"></span>`;
1411 +
1412 + // Set a default label based on the action type (user can change it)
1413 + if (!labelField.value) {
1414 + labelField.value = actionLabel;
1415 + }
1416 +
1417 + // Move to step 2
1418 + actionStep1.classList.remove('active');
1419 + actionStep2.classList.add('active');
1420 +
1421 + // Update modal title
1422 + });
567 1423 });
1424 +
1425 + // Back button functionality
1426 + if (backToStep1Btn) {
1427 + backToStep1Btn.addEventListener('click', function() {
1428 + //console.log('Back button clicked');
1429 + actionStep2.classList.remove('active');
1430 + actionStep1.classList.add('active');
1431 + });
1432 + }
1433 +
1434 + // Function to filter action cards by category and search term
1435 + function filterActionCards(category, searchTerm) {
1436 + searchTerm = searchTerm.toLowerCase().trim();
1437 + //console.log(`Filtering cards by category: "${category}", search: "${searchTerm}"`);
1438 +
1439 + let visibleCount = 0;
1440 +
1441 + // Show all cards initially with animation
1442 + actionCards.forEach((card, index) => {
1443 + // Reset animation
1444 + card.style.animation = 'none';
1445 + // Trigger reflow
1446 + void card.offsetWidth;
1447 +
1448 + // Determine if card should be visible based on category and search term
1449 + const cardCategory = card.dataset.category || '';
1450 + const matchesCategory = category === 'all' || cardCategory === category;
1451 +
1452 + const cardTitle = card.querySelector('h4')?.textContent?.toLowerCase() || '';
1453 + const cardDesc = card.querySelector('p')?.textContent?.toLowerCase() || '';
1454 + const matchesSearch = searchTerm === '' ||
1455 + cardTitle.includes(searchTerm) ||
1456 + cardDesc.includes(searchTerm);
1457 +
1458 + // Show/hide card with animation
1459 + if (matchesCategory && matchesSearch) {
1460 + card.style.display = 'flex';
1461 + // Staggered animation for cards
1462 + card.style.animation = `fadeIn 0.2s ease forwards ${index * 0.03}s`;
1463 + visibleCount++;
1464 + } else {
1465 + card.style.display = 'none';
1466 + }
1467 + });
1468 +
1469 + //console.log(`Filter results: ${visibleCount} cards visible out of ${actionCards.length}`);
1470 + }
1471 +
1472 + // Function to show notice for Pro features
1473 + function showProFeatureNotice() {
1474 + //console.log('Showing Pro feature notice');
1475 + // Check if we already have a notification container
1476 + let noticeContainer = document.querySelector('.mxchat-pro-notice');
1477 +
1478 + if (!noticeContainer) {
1479 + // Create the notice container
1480 + noticeContainer = document.createElement('div');
1481 + noticeContainer.className = 'mxchat-pro-notice';
1482 +
1483 + // Create content
1484 + noticeContainer.innerHTML = `
1485 + <div class="mxchat-pro-notice-content">
1486 + <h3>MxChat Pro Feature</h3>
1487 + <p>This action is available in the Pro version only.</p>
1488 + <div class="mxchat-pro-notice-buttons">
1489 + <button class="mxchat-button-secondary mxchat-pro-notice-close">Close</button>
1490 + <a href="https://mxchat.ai/" class="mxchat-button-primary">Upgrade to Pro</a>
1491 + </div>
1492 + </div>
1493 + `;
1494 +
1495 + // Append to body
1496 + document.body.appendChild(noticeContainer);
1497 +
1498 + // Add close functionality
1499 + const closeButton = noticeContainer.querySelector('.mxchat-pro-notice-close');
1500 + closeButton.addEventListener('click', function() {
1501 + noticeContainer.classList.remove('active');
1502 + setTimeout(() => {
1503 + noticeContainer.remove();
1504 + }, 300);
1505 + });
1506 +
1507 + // Click outside to close
1508 + noticeContainer.addEventListener('click', function(e) {
1509 + if (e.target === noticeContainer) {
1510 + closeButton.click();
1511 + }
1512 + });
1513 +
1514 + // Show with animation
1515 + setTimeout(() => {
1516 + noticeContainer.classList.add('active');
1517 + }, 10);
1518 + } else {
1519 + // If it already exists, just make it visible again
1520 + noticeContainer.classList.add('active');
1521 + }
1522 + }
568 1523
1524 + // Function to show notice for add-on requirements
1525 + function showAddonRequiredNotice(addonName) {
1526 + //console.log(`Showing add-on notice for: ${addonName}`);
1527 + // Check if we already have a notification container
1528 + let noticeContainer = document.querySelector('.mxchat-addon-notice');
1529 +
1530 + if (!noticeContainer) {
1531 + // Create the notice container
1532 + noticeContainer = document.createElement('div');
1533 + noticeContainer.className = 'mxchat-addon-notice';
1534 +
1535 + // Create content
1536 + noticeContainer.innerHTML = `
1537 + <div class="mxchat-addon-notice-content">
1538 + <span class="mxchat-addon-notice-icon">🧩</span>
1539 + <h3>Add-on Required</h3>
1540 + <p>This action requires the <strong>${addonName}</strong> add-on to be installed.</p>
1541 + <div class="mxchat-addon-notice-buttons">
1542 + <button class="mxchat-button-secondary mxchat-addon-notice-close">Close</button>
1543 + <a href="admin.php?page=mxchat-addons" class="mxchat-button-primary">Get Add-ons</a>
1544 + </div>
1545 + </div>
1546 + `;
1547 +
1548 + // Append to body
1549 + document.body.appendChild(noticeContainer);
1550 +
1551 + // Add close functionality
1552 + const closeButton = noticeContainer.querySelector('.mxchat-addon-notice-close');
1553 + closeButton.addEventListener('click', function() {
1554 + noticeContainer.classList.remove('active');
1555 + setTimeout(() => {
1556 + noticeContainer.remove();
1557 + }, 300);
1558 + });
1559 +
1560 + // Click outside to close
1561 + noticeContainer.addEventListener('click', function(e) {
1562 + if (e.target === noticeContainer) {
1563 + closeButton.click();
1564 + }
1565 + });
1566 +
1567 + // Show with animation
1568 + setTimeout(() => {
1569 + noticeContainer.classList.add('active');
1570 + }, 10);
1571 + } else {
1572 + // If it already exists, update the content
1573 + const addonNameElement = noticeContainer.querySelector('p strong');
1574 + if (addonNameElement) {
1575 + addonNameElement.textContent = addonName;
1576 + }
1577 +
1578 + // Make it visible again
1579 + noticeContainer.classList.add('active');
1580 + }
1581 + }
1582 +
1583 + // Form submission handling
1584 + if (actionForm) {
1585 + actionForm.addEventListener('submit', function() {
1586 + //console.log('Form submitted');
1587 + document.getElementById('mxchat-action-loading').style.display = 'flex';
1588 + this.querySelector('button[type="submit"]').disabled = true;
1589 + });
1590 + }
1591 + }
1592 +
1593 + // Update the modal open function to support the step-based flow
1594 + window.mxchatOpenActionModal = function(isEdit = false, actionId = '', label = '', phrases = '', threshold = 85, callbackFunction = '') {
1595 + //console.log('Modal opening, edit mode:', isEdit);
1596 +
1597 + const modal = document.getElementById('mxchat-action-modal');
1598 + if (!modal) {
1599 + console.error('Modal element not found!');
1600 + return;
1601 + }
1602 +
1603 + // Get form fields
1604 + const actionIdField = document.getElementById('edit_action_id');
1605 + const labelField = document.getElementById('intent_label');
1606 + const phrasesField = document.getElementById('action_phrases');
1607 + const formActionType = document.getElementById('form_action_type');
1608 + const callbackInput = document.getElementById('callback_function');
1609 + const saveButton = document.getElementById('mxchat-save-action-btn');
1610 + const nonceContainer = document.getElementById('action-nonce-container');
1611 + const thresholdSlider = document.getElementById('similarity_threshold');
1612 + const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display');
1613 + const actionStep1 = document.getElementById('mxchat-action-step-1');
1614 + const actionStep2 = document.getElementById('mxchat-action-step-2');
1615 + const searchInput = document.getElementById('action-type-search');
1616 +
1617 + // Set up modal for edit or create
1618 + if (isEdit) {
569 1619
1620 + saveButton.textContent = 'Update Action';
1621 + formActionType.value = 'mxchat_edit_intent';
1622 + actionIdField.value = actionId;
1623 + labelField.value = label;
1624 + phrasesField.value = phrases;
1625 + callbackInput.value = callbackFunction;
1626 + thresholdSlider.value = threshold; // Set the current threshold value
1627 + thresholdDisplay.textContent = threshold + '%'; // Update display
1628 +
1629 + // Update the nonce field for editing
1630 + nonceContainer.innerHTML = ''; // Clear existing nonce
1631 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.edit_intent_nonce) {
1632 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.edit_intent_nonce}">`;
1633 + }
1634 +
1635 + // For editing, go directly to step 2 and update the selected action display
1636 + actionStep1.classList.remove('active');
1637 + actionStep2.classList.add('active');
1638 +
1639 + // Find the matching action card to get its details
1640 + const actionCards = document.querySelectorAll('.mxchat-action-type-card');
1641 + let foundCard = null;
1642 +
1643 + actionCards.forEach(card => {
1644 + if (card.dataset.value === callbackFunction) {
1645 + foundCard = card;
1646 + }
1647 + });
1648 +
1649 + if (foundCard) {
1650 + //console.log('Found matching action card for:', callbackFunction);
1651 + const actionLabel = foundCard.dataset.label || foundCard.querySelector('h4')?.textContent || '';
1652 + const actionIconElement = foundCard.querySelector('.dashicons');
1653 + const actionIcon = actionIconElement
1654 + ? actionIconElement.getAttribute('class').replace('dashicons dashicons-', '')
1655 + : 'admin-generic';
1656 + const actionDescription = foundCard.querySelector('p')?.textContent || '';
1657 +
1658 + document.getElementById('selected-action-title').textContent = actionLabel;
1659 + document.getElementById('selected-action-description').textContent = actionDescription;
1660 + document.getElementById('selected-action-icon').innerHTML =
1661 + `<span class="dashicons dashicons-${actionIcon}"></span>`;
1662 + } else {
1663 + //console.log('No matching action card found for:', callbackFunction);
1664 + // Fallback if we can't find the card
1665 + document.getElementById('selected-action-title').textContent = label;
1666 + document.getElementById('selected-action-description').textContent = 'Configure this action for your chatbot';
1667 + document.getElementById('selected-action-icon').innerHTML =
1668 + `<span class="dashicons dashicons-admin-generic"></span>`;
1669 + }
1670 + } else {
1671 + //console.log('Setting up create mode');
1672 + saveButton.textContent = 'Save Action';
1673 + formActionType.value = 'mxchat_add_intent';
1674 + actionIdField.value = '';
1675 + labelField.value = '';
1676 + phrasesField.value = '';
1677 + callbackInput.value = '';
1678 + thresholdSlider.value = 85; // Default value for new actions
1679 + thresholdDisplay.textContent = '85%'; // Default display
1680 +
1681 + // Update the nonce field for adding
1682 + nonceContainer.innerHTML = ''; // Clear existing nonce
1683 + if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.add_intent_nonce) {
1684 + nonceContainer.innerHTML = `<input type="hidden" name="_wpnonce" value="${mxchatAdmin.add_intent_nonce}">`;
1685 + }
1686 +
1687 + // For creating new, start at step 1
1688 + actionStep1.classList.add('active');
1689 + actionStep2.classList.remove('active');
1690 + }
1691 +
1692 + // Show modal with animation
1693 + modal.style.display = 'flex';
1694 + requestAnimationFrame(() => {
1695 + modal.classList.add('active');
1696 + });
1697 +
1698 + // Set up close handlers
1699 + const closeModal = () => {
1700 + //console.log('Closing modal');
1701 + modal.classList.remove('active');
1702 + setTimeout(() => {
1703 + modal.style.display = 'none';
1704 + }, 300); // Match the CSS transition time
1705 + };
1706 +
1707 + // Close button handler
1708 + const closeBtn = modal.querySelector('.mxchat-modal-close');
1709 + if (closeBtn) {
1710 + closeBtn.onclick = closeModal;
1711 + }
1712 +
1713 + // Cancel button handler
1714 + const cancelBtns = modal.querySelectorAll('.mxchat-modal-cancel');
1715 + if (cancelBtns) {
1716 + cancelBtns.forEach(btn => {
1717 + btn.onclick = closeModal;
1718 + });
1719 + }
1720 +
1721 + // Click outside modal to close
1722 + modal.onclick = (e) => {
1723 + if (e.target === modal) {
1724 + closeModal();
1725 + }
1726 + };
1727 +
1728 + // Escape key to close modal
1729 + document.addEventListener('keydown', function(e) {
1730 + if (e.key === 'Escape' && modal.classList.contains('active')) {
1731 + closeModal();
1732 + }
1733 + }, { once: true });
1734 +
1735 + // Focus appropriate field based on current step
1736 + if (isEdit || actionStep2.classList.contains('active')) {
1737 + if (labelField) labelField.focus();
1738 + } else {
1739 + if (searchInput) searchInput.focus();
1740 + }
1741 +
1742 + return closeModal; // Return close function for external use
1743 + };
1744 +
1745 + // Setup add action buttons
1746 + const addActionBtn = document.getElementById('mxchat-add-action-btn');
1747 + if (addActionBtn) {
1748 + //console.log('Add action button found');
1749 + addActionBtn.onclick = () => window.mxchatOpenActionModal();
1750 + } else {
1751 + console.warn('Add action button not found');
1752 + }
1753 +
1754 + const createFirstAction = document.getElementById('mxchat-create-first-action');
1755 + if (createFirstAction) {
1756 + //console.log('Create first action button found');
1757 + createFirstAction.onclick = () => window.mxchatOpenActionModal();
1758 + }
1759 +
1760 + // Setup edit buttons
1761 + const editButtons = document.querySelectorAll('.mxchat-action-card .mxchat-edit-button');
1762 + //console.log('Edit buttons found:', editButtons.length);
1763 + editButtons.forEach(button => {
1764 + button.onclick = () => {
1765 + const actionId = button.dataset.actionId;
1766 + const phrases = button.dataset.phrases;
1767 + const label = button.dataset.label;
1768 + const threshold = button.dataset.threshold || 85;
1769 + const callbackFunction = button.dataset.callbackFunction;
570 1770
1771 + window.mxchatOpenActionModal(true, actionId, label, phrases, threshold, callbackFunction);
1772 + };
1773 + });
1774 +});
1775 +
1776 +jQuery(document).ready(function($) {
1777 + // Toggle custom post types container
1778 + $('#mxchat-custom-post-types-toggle').on('click', function(e) {
1779 + e.preventDefault();
1780 +
1781 + $('#mxchat-custom-post-types-container').slideToggle(300);
1782 +
1783 + // Rotate the toggle icon
1784 + const $icon = $(this).find('.mxchat-accordion-icon');
1785 + if ($('#mxchat-custom-post-types-container').is(':visible')) {
1786 + $icon.css('transform', 'rotate(180deg)');
1787 + $(this).closest('.mxchat-settings-accordion').addClass('active');
1788 + } else {
1789 + $icon.css('transform', 'rotate(0deg)');
1790 + $(this).closest('.mxchat-settings-accordion').removeClass('active');
1791 + }
1792 + });
1793 +
1794 + // If there are any selections made, auto-expand the container
1795 + function autoExpandIfNeeded() {
1796 + // Check if any checkbox in the container is checked
1797 + const hasCheckedItems = $('#mxchat-custom-post-types-container input[type="checkbox"]:checked').length > 0;
1798 +
1799 + if (hasCheckedItems) {
1800 + $('#mxchat-custom-post-types-container').show();
1801 + $('#mxchat-custom-post-types-toggle .mxchat-accordion-icon').css('transform', 'rotate(180deg)');
1802 + $('.mxchat-settings-accordion').addClass('active');
1803 + }
1804 + }
1805 +
1806 + // Run on page load
1807 + autoExpandIfNeeded();
1808 +});
1809 +
1810 +// Add this code to your admin-script.js file
1811 +
1812 +jQuery(document).ready(function($) {
1813 + // Check if we're on the right admin page with status updating
1814 + if ($('.mxchat-status-card').length > 0) {
1815 + // Initialize AJAX status updates
1816 + initStatusUpdates();
1817 + }
1818 +
1819 + function initStatusUpdates() {
1820 + // Get the refresh interval (default to 5 seconds if not set)
1821 + const refreshInterval = parseInt(mxchatAdmin.status_refresh_interval || 5000);
1822 +
1823 + // Check if the status cards exist
1824 + const hasSitemapStatus = $('.mxchat-status-card').length > 0;
1825 +
1826 + if (hasSitemapStatus) {
1827 + // Start the interval for automatic updates
1828 + const updateIntervalId = setInterval(function() {
1829 + fetchStatusUpdates();
1830 + }, refreshInterval);
1831 +
1832 + // Attach event listener to stop button to clear the interval
1833 + $('.mxchat-stop-form').on('submit', function() {
1834 + clearInterval(updateIntervalId);
1835 + });
1836 +
1837 + // Do an initial fetch
1838 + fetchStatusUpdates();
1839 + }
1840 + }
1841 +
1842 + function fetchStatusUpdates() {
1843 + // If user is actively viewing the failed URLs, don't refresh as frequently
1844 + const $details = $('.mxchat-failed-urls-container details');
1845 + const isUserViewing = $details.length > 0 && $details.prop('open');
1846 +
1847 + // If details are open, we'll refresh at a slower rate (or not at all)
1848 + if (isUserViewing) {
1849 + // Optional: Skip this refresh completely when details are open
1850 + // return;
1851 +
1852 + // Alternative: Update less frequently when details are open
1853 + setTimeout(function() {
1854 + performStatusUpdate();
1855 + }, 5000); // Slow down updates to every 5 seconds when details are open
1856 + } else {
1857 + performStatusUpdate();
1858 + }
1859 + }
1860 +
1861 + function performStatusUpdate() {
1862 + $.ajax({
1863 + url: ajaxurl,
1864 + type: 'POST',
1865 + data: {
1866 + action: 'mxchat_get_status_updates',
1867 + nonce: mxchatAdmin.status_nonce
1868 + },
1869 + success: function(response) {
1870 + if (response && response.is_processing) {
1871 + updateStatusUI(response);
1872 + }
1873 + },
1874 + error: function(xhr, status, error) {
1875 + console.error('Status update failed:', error);
1876 + }
1877 + });
1878 + }
1879 +
1880 + function updateStatusUI(data) {
1881 + // Update sitemap status if available
1882 + if (data.sitemap_status) {
1883 + // Update basic info
1884 + const sitemapStatus = data.sitemap_status;
1885 + $('.mxchat-progress-fill').css('width', sitemapStatus.percentage + '%');
1886 + $('.mxchat-status-details p').text(
1887 + 'Progress: ' + sitemapStatus.processed_urls + ' of ' +
1888 + sitemapStatus.total_urls + ' URLs (' + sitemapStatus.percentage + '%)'
1889 + );
1890 +
1891 + // Check if details is already open before updating
1892 + const isDetailsOpen = $('.mxchat-failed-urls-container details').prop('open');
1893 +
1894 + // Update errors display
1895 + const $errorContainer = $('.mxchat-error-notice');
1896 + if ($errorContainer.length === 0 && (sitemapStatus.error || sitemapStatus.last_error || sitemapStatus.failed_urls_list.length > 0)) {
1897 + // Create error container if it doesn't exist
1898 + $('.mxchat-status-details').append('<div class="mxchat-error-notice"></div>');
1899 + }
1900 +
1901 + // Update or create error notices
1902 + const $newErrorContainer = $('.mxchat-error-notice');
1903 + if ($newErrorContainer.length > 0) {
1904 + let errorHTML = '';
1905 +
1906 + if (sitemapStatus.error) {
1907 + errorHTML += '<p class="error">' + sitemapStatus.error + '</p>';
1908 + }
1909 +
1910 + if (sitemapStatus.last_error) {
1911 + errorHTML += '<p class="last-error">Last error: ' + sitemapStatus.last_error + '</p>';
1912 + }
1913 +
1914 + // Add failed URLs list
1915 + if (sitemapStatus.failed_urls_list && sitemapStatus.failed_urls_list.length > 0) {
1916 + errorHTML += '<div class="mxchat-failed-urls-container">';
1917 + errorHTML += '<h4>Failed URLs (' + sitemapStatus.failed_urls_list.length + ')</h4>';
1918 +
1919 + // Set the 'open' attribute based on previous state
1920 + errorHTML += '<details' + (isDetailsOpen ? ' open' : '') + '>';
1921 + errorHTML += '<summary>Show Failed URLs</summary>';
1922 + errorHTML += '<div class="mxchat-failed-urls-list">';
1923 +
1924 + // Sort by most recent first
1925 + const sortedFailedUrls = [...sitemapStatus.failed_urls_list].sort((a, b) => b.time - a.time);
1926 +
1927 + // Limit to at most 50 URLs to display
1928 + const displayUrls = sortedFailedUrls.slice(0, 50);
1929 +
1930 + displayUrls.forEach(item => {
1931 + const timeAgo = formatTimeAgo(item.time);
1932 + errorHTML += '<div class="mxchat-failed-url-item">';
1933 + errorHTML += '<span class="mxchat-failed-url-address">' +
1934 + '<a href="' + item.url + '" target="_blank">' + truncateUrl(item.url) + '</a></span>';
1935 + errorHTML += '<span class="mxchat-failed-url-error">' + item.error + '</span>';
1936 + errorHTML += '<span class="mxchat-failed-url-time">' + timeAgo + '</span>';
1937 + errorHTML += '</div>';
1938 + });
1939 +
1940 + if (sitemapStatus.failed_urls_list.length > 50) {
1941 + errorHTML += '<div class="mxchat-failed-urls-more">+ ' +
1942 + (sitemapStatus.failed_urls_list.length - 50) +
1943 + ' more failed URLs not shown</div>';
1944 + }
1945 +
1946 + errorHTML += '</div>'; // End of failed-urls-list
1947 + errorHTML += '</details>';
1948 + errorHTML += '</div>'; // End of failed-urls-container
1949 + }
1950 +
1951 + $newErrorContainer.html(errorHTML);
1952 +
1953 + // Additionally, add a click handler to pause refreshes when viewing details
1954 + $('.mxchat-failed-urls-container details').on('toggle', function() {
1955 + if (this.open) {
1956 + // User opened the details - set a flag
1957 + $(this).data('user-opened', true);
1958 + } else {
1959 + // User closed the details - remove the flag
1960 + $(this).data('user-opened', false);
1961 + }
1962 + });
1963 + }
1964 + }
1965 + }
1966 +
1967 + // Helper function to format time ago
1968 + function formatTimeAgo(timestamp) {
1969 + const now = Math.floor(Date.now() / 1000);
1970 + const seconds = now - timestamp;
1971 +
1972 + if (seconds < 60) {
1973 + return seconds + ' seconds ago';
1974 + } else if (seconds < 3600) {
1975 + return Math.floor(seconds / 60) + ' minutes ago';
1976 + } else if (seconds < 86400) {
1977 + return Math.floor(seconds / 3600) + ' hours ago';
1978 + } else {
1979 + return Math.floor(seconds / 86400) + ' days ago';
1980 + }
1981 + }
1982 +
1983 + // Helper function to truncate long URLs
1984 + function truncateUrl(url) {
1985 + const maxLength = 50;
1986 + if (url.length <= maxLength) return url;
1987 +
1988 + // Remove protocol
1989 + let displayUrl = url.replace(/^https?:\/\//, '');
1990 +
1991 + if (displayUrl.length <= maxLength) return displayUrl;
1992 +
1993 + // Keep the domain and truncate the path
1994 + const domainMatch = displayUrl.match(/^([^\/]+)\//);
1995 + if (domainMatch) {
1996 + const domain = domainMatch[1];
1997 + const path = displayUrl.substring(domain.length);
1998 +
1999 + if (path.length > 10) {
2000 + return domain + path.substring(0, maxLength - domain.length - 3) + '...';
2001 + }
2002 + }
2003 +
2004 + // Final fallback for very long strings
2005 + return displayUrl.substring(0, maxLength - 3) + '...';
2006 + }
2007 +});