// Simple debounce function implementation function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Helper function to open edit modal function mxchatOpenEditModal(intentId, phrases) { const modal = document.getElementById('mxchat-edit-modal'); if (!modal) return; // Get form fields const intentIdField = document.getElementById('edit_intent_id'); const phrasesField = document.getElementById('edit_phrases'); // Set values intentIdField.value = intentId; phrasesField.value = phrases; // Show modal with animation modal.style.display = 'flex'; requestAnimationFrame(() => { modal.classList.add('active'); }); // Set up close handlers const closeModal = () => { modal.classList.remove('active'); setTimeout(() => { modal.style.display = 'none'; }, 300); // Match the CSS transition time }; // Close button handler const closeBtn = modal.querySelector('.mxchat-modal-close'); if (closeBtn) { closeBtn.onclick = closeModal; } // Cancel button handler const cancelBtn = modal.querySelector('.mxchat-modal-cancel'); if (cancelBtn) { cancelBtn.onclick = closeModal; } // Click outside modal to close modal.onclick = (e) => { if (e.target === modal) { closeModal(); } }; // Focus the textarea phrasesField.focus(); } // Initialize event listeners document.addEventListener('DOMContentLoaded', () => { // Set up edit button handlers document.querySelectorAll('.mxchat-edit-button').forEach(button => { button.onclick = () => { const intentId = button.dataset.intentId; const phrases = button.dataset.phrases; mxchatOpenEditModal(intentId, phrases); }; }); }); jQuery(document).ready(function($) { // Ensure we have a debounce function (use lodash if available, otherwise use our implementation) const useDebounce = (window._ && window._.debounce) ? window._.debounce : debounce; // --- AJAX Auto-Save --- const $autosaveSections = $('.mxchat-autosave-section'); if ($autosaveSections.length) { // Handle real-time range slider value updates $autosaveSections.find('input[type="range"]').on('input', function() { const value = $(this).val(); $('#threshold_value').text(value); }); // Handle all input changes (including range slider) $autosaveSections.find('input, textarea, select').on('change', function() { const $field = $(this); const name = $field.attr('name'); let value; // Handle different input types if ($field.attr('type') === 'checkbox') { value = $field.is(':checked') ? 'on' : 'off'; } else { value = $field.val(); } // Create feedback container const feedbackContainer = $('
'); const spinner = $(''); const successIcon = $(''); // Position feedback container based on input type if ($field.closest('.toggle-switch').length) { $field.closest('td').append(feedbackContainer); } else if ($field.closest('.mxchat-toggle-switch').length) { $field.closest('.mxchat-toggle-container').append(feedbackContainer); } else if ($field.closest('.slider-container').length) { $field.closest('.slider-container').after(feedbackContainer); } else { $field.after(feedbackContainer); } feedbackContainer.append(spinner); // Determine which AJAX action and nonce to use: var ajaxAction, nonce; // Use the new AJAX action for submenu fields: if ( name.indexOf('mxchat_prompts_options') !== -1 || name === 'mxchat_auto_sync_posts' || name === 'mxchat_auto_sync_pages' ) { ajaxAction = 'mxchat_save_prompts_setting'; nonce = mxchatPromptsAdmin.prompts_setting_nonce; } else { // Otherwise, use the existing AJAX action. ajaxAction = 'mxchat_save_setting'; nonce = mxchatAdmin.setting_nonce; } // AJAX save request $.ajax({ url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url, type: 'POST', data: { action: ajaxAction, name: name, value: value, _ajax_nonce: nonce }, success: function(response) { if (response.success) { spinner.fadeOut(200, function() { feedbackContainer.append(successIcon); successIcon.fadeIn(200).delay(1000).fadeOut(200, function() { feedbackContainer.remove(); }); }); } else { alert('Error saving: ' + (response.data?.message || 'Unknown error')); if ($field.attr('type') === 'checkbox') { $field.prop('checked', !$field.is(':checked')); } feedbackContainer.remove(); } }, error: function() { alert('An error occurred while saving.'); if ($field.attr('type') === 'checkbox') { $field.prop('checked', !$field.is(':checked')); } feedbackContainer.remove(); } }); }); // Initialize color pickers with debouncing $autosaveSections.find('.my-color-field').each(function() { const $colorField = $(this); $(this).wpColorPicker({ change: useDebounce(function(event, ui) { // Safety check - ensure we have a valid field and value if (!$colorField || !$colorField.val()) { console.warn('Color picker not ready'); return; } const name = $colorField.attr('name'); const value = $colorField.val(); if (!name || !value) { console.warn('Missing required color picker values'); return; } // Create feedback container const feedbackContainer = $(''); const spinner = $(''); const successIcon = $(''); // Position feedback container $colorField.closest('.wp-picker-container').after(feedbackContainer); feedbackContainer.append(spinner); // Determine AJAX action and nonce for color fields: var ajaxAction, nonce; if ( name.indexOf('mxchat_prompts_options') !== -1 || name === 'mxchat_auto_sync_posts' || name === 'mxchat_auto_sync_pages' ) { ajaxAction = 'mxchat_save_prompts_setting'; nonce = mxchatPromptsAdmin.prompts_setting_nonce; } else { ajaxAction = 'mxchat_save_setting'; nonce = mxchatAdmin.setting_nonce; } // AJAX save request $.ajax({ url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url, type: 'POST', data: { action: ajaxAction, name: name, value: value, _ajax_nonce: nonce }, success: function(response) { if (response.success) { spinner.fadeOut(200, function() { feedbackContainer.append(successIcon); successIcon.fadeIn(200).delay(1000).fadeOut(200, function() { feedbackContainer.remove(); }); }); } else { alert('Error saving: ' + (response.data?.message || 'Unknown error')); feedbackContainer.remove(); } }, error: function() { alert('An error occurred while saving.'); feedbackContainer.remove(); } }); }, 500) }); }); // Reinitialize color pickers when switching tabs $('.mxchat-tab-button').on('click.mxchat', function() { setTimeout(function() { $('.my-color-field:visible').wpColorPicker('close'); }, 100); }); } // Initialize tabs system function initTabs() { // Remove any existing handlers first $('.mxchat-tab-button').off('click.mxchat'); // Add new click handlers $('.mxchat-tab-button').on('click.mxchat', function(e) { e.preventDefault(); e.stopPropagation(); var $this = $(this); // Get tab ID from data-tab attribute var tabId = $this.data('tab') || 'chatbot'; // Safety check for empty tabId if (!tabId) { console.warn('No tab identifier found'); return; } // Update tab buttons $('.mxchat-tab-button').removeClass('active'); $this.addClass('active'); // Update content areas - with safety check $('.mxchat-tab-content').removeClass('active'); var $targetTab = $('#' + tabId); if ($targetTab.length) { $targetTab.addClass('active'); // Store active tab try { localStorage.setItem('mxchat_active_tab', tabId); } catch (e) { console.warn('LocalStorage not available:', e); } } else { console.warn('Tab content #' + tabId + ' not found'); } }); } // Initialize tabs and handle events initTabs(); $(document).on('widget-added widget-updated postbox-toggled', initTabs); // Activate initial tab try { var savedTab = localStorage.getItem('mxchat_active_tab'); if (savedTab && $('#' + savedTab).length > 0) { $('.mxchat-tab-button[data-tab="' + savedTab + '"]').trigger('click.mxchat'); } else { $('.mxchat-tab-button').first().trigger('click.mxchat'); } } catch (e) { $('.mxchat-tab-button').first().trigger('click.mxchat'); } // Attach edit modal event handler $(document).on('click', '.mxchat-edit-button', function() { const intentId = $(this).data('intent-id'); const phrases = $(this).data('phrases'); mxchatOpenEditModal(intentId, phrases); }); // Toggle visibility handlers function toggleVisibility(selector) { $(selector).on('click', function() { var inputField = $(this).prev('input'); if (inputField.attr('type') === 'password') { inputField.attr('type', 'text'); $(this).text('Hide'); } else { inputField.attr('type', 'password'); $(this).text('Show'); } }); } // Initialize all toggle visibility buttons [ '#toggleApiKeyVisibility', '#toggleWooCommerceSecretVisibility', '#toggleVoyageAPIKeyVisibility', '#toggleLoopsApiKeyVisibility', '#toggleXaiApiKeyVisibility', '#toggleClaudeApiKeyVisibility', '#toggleBraveApiKeyVisibility', '#toggleWebhookUrlVisibility', '#toggleSecretKeyVisibility', '#toggleBotTokenVisibility', '#toggleDeepSeekApiKeyVisibility' ].forEach(toggleVisibility); // Handle API key visibility based on model selection // Handle API key visibility based on model selection function setupAPIKeyVisibility() { // Cache the selectors const $chatModelSelect = $('#model'); const $embeddingModelSelect = $('#embedding_model'); // First, locate and mark the API key rows setupAPIKeyRows(); // Initial setup based on current selections updateApiKeyVisibility(); // Listen for changes to the model selectors $chatModelSelect.on('change', updateApiKeyVisibility); $embeddingModelSelect.on('change', updateApiKeyVisibility); /** * Locate and mark rows that contain API key fields */ function setupAPIKeyRows() { // Find key rows by their field IDs const providerMap = { 'api_key': 'openai', 'xai_api_key': 'xai', 'claude_api_key': 'claude', 'deepseek_api_key': 'deepseek', 'voyage_api_key': 'voyage' }; $.each(providerMap, function(fieldId, provider) { const $field = $('#' + fieldId); if ($field.length) { const $row = $field.closest('tr'); $row.addClass('mxchat-setting-row'); $row.attr('data-provider', provider); } }); } /** * Updates the visibility of API key fields based on current model selections */ function updateApiKeyVisibility() { const chatModel = $chatModelSelect.val(); const embeddingModel = $embeddingModelSelect.val(); // Determine which providers are needed const isOpenAIChat = chatModel && chatModel.startsWith('gpt-'); const isXAI = chatModel && chatModel.startsWith('grok-'); const isClaude = chatModel && chatModel.startsWith('claude-'); const isDeepSeek = chatModel && chatModel.startsWith('deepseek-'); const isOpenAIEmbedding = embeddingModel && embeddingModel.startsWith('text-embedding-'); const isVoyage = embeddingModel && embeddingModel.startsWith('voyage-'); // Update API key visibility for each provider updateWrapperVisibility('openai', isOpenAIChat || isOpenAIEmbedding); updateWrapperVisibility('xai', isXAI); updateWrapperVisibility('claude', isClaude); updateWrapperVisibility('deepseek', isDeepSeek); updateWrapperVisibility('voyage', isVoyage); // Update provider-specific notices for OpenAI if (isOpenAIChat && isOpenAIEmbedding) { $('div[data-provider="openai"] .api-key-notice').text( 'Required for your selected chat model and embedding model. Important: You must add credits before use.' ); } else if (isOpenAIChat) { $('div[data-provider="openai"] .api-key-notice').text( 'Required for your selected chat model. Important: You must add credits before use.' ); } else if (isOpenAIEmbedding) { $('div[data-provider="openai"] .api-key-notice').text( 'Required for your selected embedding model. Important: You must add credits before use.' ); } } /** * Updates visibility of a specific provider's API key wrapper */ function updateWrapperVisibility(provider, isVisible) { const $row = $('tr.mxchat-setting-row[data-provider="' + provider + '"]'); if (!$row.length) { console.warn('API key row not found for provider: ' + provider); return; } if (isVisible) { $row.show(); if (!$row.hasClass('highlighted')) { $row.addClass('highlighted'); setTimeout(() => { $row.removeClass('highlighted'); }, 1500); } } else { $row.hide(); } } } // Initialize API key visibility setupAPIKeyVisibility(); // Add Intent Form Submission $('#mxchat-add-intent-form').on('submit', function(event) { $('#mxchat-intent-loading').show(); $('#mxchat-intent-loading-text').show(); $(this).find('button[type="submit"]').hide(); }); // Inline Edit Functionality $('.edit-button').on('click', function() { var row = $(this).closest('tr'); row.find('.content-view, .url-view').hide(); row.find('.content-edit, .url-edit').show(); row.find('.edit-button').hide(); row.find('.save-button').show(); }); // Save button handler $('.save-button').on('click', function() { var button = $(this); var row = button.closest('tr'); var id = button.data('id'); var newContent = row.find('.content-edit').val(); var newUrl = row.find('.url-edit').val(); button.prop('disabled', true); button.text('Saving...'); $.ajax({ url: mxchatAdmin.ajax_url, type: 'POST', data: { action: 'mxchat_save_inline_prompt', id: id, article_content: newContent, article_url: newUrl, _ajax_nonce: mxchatAdmin.inline_edit_nonce }, success: function(response) { button.prop('disabled', false); button.text('Save'); if (response.success) { row.find('.content-view').html(newContent.replace(/\n/g, "