// 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 for intents/actions 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(); } // Live Agent Notice Dismissal Function function dismissLiveAgentNotice() { if (typeof jQuery !== 'undefined' && typeof mxchatLiveAgent !== 'undefined') { jQuery.post(mxchatLiveAgent.ajaxurl, { action: 'dismiss_live_agent_notice', nonce: mxchatLiveAgent.nonce }, function(response) { if (response.success) { jQuery('#mxchat-disabled-notice').fadeOut(300); } }).fail(function() { // Fallback: just hide the notice if AJAX fails jQuery('#mxchat-disabled-notice').fadeOut(300); }); } else { // Fallback for cases where jQuery or localized data isn't available var notice = document.getElementById('mxchat-disabled-notice'); if (notice) { notice.style.display = 'none'; } } } // Updated mxchatOpenActionModal function to integrate with the new selector function mxchatOpenActionModal(isEdit = false, actionId = '', label = '', phrases = '', threshold = 85, callbackFunction = '') { const modal = document.getElementById('mxchat-action-modal'); if (!modal) return; // Get form fields const actionIdField = document.getElementById('edit_action_id'); const labelField = document.getElementById('intent_label'); const phrasesField = document.getElementById('action_phrases'); const formActionType = document.getElementById('form_action_type'); const callbackGroup = document.getElementById('callback_selection_group'); const callbackSelect = document.getElementById('callback_function'); const saveButton = document.getElementById('mxchat-save-action-btn'); const nonceContainer = document.getElementById('action-nonce-container'); const thresholdSlider = document.getElementById('similarity_threshold'); const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display'); // Set up modal for edit or create if (isEdit) { saveButton.textContent = 'Update Action'; formActionType.value = 'mxchat_edit_intent'; actionIdField.value = actionId; labelField.value = label; phrasesField.value = phrases; callbackGroup.style.display = 'none'; // Hide callback selection when editing thresholdSlider.value = threshold; // Set the current threshold value thresholdDisplay.textContent = threshold + '%'; // Update display // Remove the required attribute when editing callbackSelect.removeAttribute('required'); // Update the nonce field for editing nonceContainer.innerHTML = ''; // Clear existing nonce if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.edit_intent_nonce) { nonceContainer.innerHTML = ``; } } else { saveButton.textContent = 'Save Action'; formActionType.value = 'mxchat_add_intent'; actionIdField.value = ''; labelField.value = ''; phrasesField.value = ''; callbackGroup.style.display = 'block'; // Show callback selection when creating thresholdSlider.value = 85; // Default value for new actions thresholdDisplay.textContent = '85%'; // Default display // Ensure the required attribute is present when adding callbackSelect.setAttribute('required', 'required'); // Update the nonce field for adding nonceContainer.innerHTML = ''; // Clear existing nonce if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.add_intent_nonce) { nonceContainer.innerHTML = ``; } } // 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(); } }; // Escape key to close modal document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && modal.classList.contains('active')) { closeModal(); } }, { once: true }); // Focus the first field labelField.focus(); // Dispatch an event for the action type selector to catch const event = new CustomEvent('mxchatModalOpened', { detail: { isEdit: isEdit, callbackFunction: callbackFunction || (isEdit ? callbackSelect.value : '') } }); document.dispatchEvent(event); return closeModal; // Return close function for external use } // Initialize event listeners document.addEventListener('DOMContentLoaded', () => { // Set up edit button handlers for intents document.querySelectorAll('.mxchat-edit-button').forEach(button => { button.onclick = () => { const intentId = button.dataset.intentId; const phrases = button.dataset.phrases; mxchatOpenEditModal(intentId, phrases); }; }); document.querySelectorAll('.mxchat-action-card .mxchat-edit-button').forEach(button => { button.onclick = () => { const actionId = button.dataset.actionId; const phrases = button.dataset.phrases; const label = button.dataset.label; const threshold = button.dataset.threshold || 85; const callbackFunction = button.dataset.callbackFunction; const enabledBots = button.dataset.enabledBots; // ADD THIS LINE mxchatOpenActionModal(true, actionId, label, phrases, threshold, callbackFunction, enabledBots); }; }); // Set up add new action buttons (new functionality) const addActionBtn = document.getElementById('mxchat-add-action-btn'); if (addActionBtn) { addActionBtn.onclick = () => mxchatOpenActionModal(); } const createFirstAction = document.getElementById('mxchat-create-first-action'); if (createFirstAction) { createFirstAction.onclick = () => mxchatOpenActionModal(); } // Setup category-specific new action buttons (new functionality) document.querySelectorAll('.mxchat-new-action-button').forEach(button => { button.onclick = () => { const category = button.closest('.mxchat-new-action-card').dataset.category; const closeModal = mxchatOpenActionModal(); // Pre-select the appropriate callback based on category if (category) { const callbackSelect = document.getElementById('callback_function'); if (callbackSelect) { setTimeout(() => { // Map categories to default callbacks const categoryToCallback = { 'data_collection': 'mxchat_handle_form_collection', 'integrations': 'mxchat_handle_slack_message', 'custom_actions': 'mxchat_handle_custom_action', 'recommendations': 'mxchat_handle_product_recommendations' // Add more mappings as needed }; if (categoryToCallback[category]) { callbackSelect.value = categoryToCallback[category]; } }, 100); } } }; }); // Handle action toggle switches (new functionality) document.querySelectorAll('.mxchat-action-toggle').forEach(toggle => { toggle.onchange = function() { const actionId = this.dataset.actionId; const isEnabled = this.checked; // Show loading indicator const loadingEl = document.getElementById('mxchat-action-loading'); if (loadingEl) loadingEl.style.display = 'flex'; // Send AJAX request to update status fetch(ajaxurl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'mxchat_toggle_action', intent_id: actionId, enabled: isEnabled ? 1 : 0, nonce: mxchatAdmin.toggle_action_nonce // Use the correct nonce }) }) .then(response => response.json()) .then(data => { if (!data.success) { alert('Failed to update action status: ' + (data.data?.message || 'Unknown error')); this.checked = !isEnabled; // Revert the toggle } }) .catch(error => { //console.error('Error:', error); alert('Server error. Please try again.'); this.checked = !isEnabled; // Revert the toggle }) .finally(() => { if (loadingEl) loadingEl.style.display = 'none'; }); }; }); // Handle threshold sliders in action cards (new functionality) document.querySelectorAll('.mxchat-threshold-slider').forEach(slider => { slider.oninput = function() { const actionId = this.id.replace('intent_threshold_', ''); document.getElementById('threshold_output_' + actionId).textContent = this.value + '%'; }; }); // Handle threshold save buttons in action cards (new functionality) document.querySelectorAll('.mxchat-threshold-save').forEach(button => { button.onclick = function(e) { e.preventDefault(); const form = this.closest('form'); const intentId = form.querySelector('input[name="intent_id"]').value; const threshold = form.querySelector('input[name="intent_threshold"]').value; const nonce = form.querySelector('input[name="_wpnonce"]').value; // Show loading indicator const loadingEl = document.getElementById('mxchat-action-loading'); if (loadingEl) loadingEl.style.display = 'flex'; // Send AJAX request fetch(ajaxurl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'mxchat_update_intent_threshold', intent_id: intentId, intent_threshold: threshold, _wpnonce: nonce }) }) .then(response => response.json()) .then(data => { if (data.success) { // Visual feedback of success const card = this.closest('.mxchat-action-card'); card.style.background = 'rgba(120, 115, 245, 0.1)'; setTimeout(() => { card.style.background = 'white'; }, 300); } else { alert('Failed to update threshold: ' + (data.data?.message || 'Unknown error')); } }) .catch(error => { //console.error('Error:', error); alert('Server error. Please try again.'); }) .finally(() => { if (loadingEl) loadingEl.style.display = 'none'; }); }; }); }); 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 --- let $autosaveSections = $('.mxchat-autosave-section'); // *** ADD THIS: Extend auto-save sections to include Pinecone settings *** const $pineconeAutosaveSection = $('#mxchat-kb-tab-pinecone'); if ($pineconeAutosaveSection.length) { $autosaveSections = $autosaveSections.add($pineconeAutosaveSection); //console.log('Added Pinecone section to auto-save monitoring'); } // Track whether fields have been modified by user const userModifiedFields = new Set(); if ($autosaveSections.length) { // Track user interactions with input fields to determine if changes are user-initiated $autosaveSections.find('input, textarea, select').on('focus keydown paste', function() { const fieldName = $(this).attr('name'); if (fieldName) { userModifiedFields.add(fieldName); } }); // 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'); // Skip saving for API key fields that haven't been interacted with and are empty const isApiKeyField = name && ( name === 'loops_api_key' || name === 'api_key' || name === 'xai_api_key' || name === 'claude_api_key' || name === 'voyage_api_key' || name === 'gemini_api_key' || name === 'deepseek_api_key' || name.indexOf('_api_key') !== -1 ); // Skip processing if: // 1. It's an API key field // 2. The user hasn't interacted with it // 3. The field is empty if (isApiKeyField && !userModifiedFields.has(name) && (!$field.val() || $field.val().trim() === '')) { //console.log('Skipping auto-save for untouched API key field:', name); return; } let value; // Handle different input types if ($field.attr('type') === 'checkbox') { // *** UPDATED: Handle Pinecone checkboxes differently *** if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) { value = $field.is(':checked') ? '1' : '0'; } else { 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; // *** UPDATED: Add Pinecone fields to prompts action *** if (name.indexOf('mxchat_prompts_options') !== -1 || name === 'mxchat_auto_sync_posts' || name === 'mxchat_auto_sync_pages' || name.indexOf('mxchat_auto_sync_') === 0 || name.indexOf('mxchat_pinecone_addon_options') !== -1) { // *** ADD THIS LINE *** 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; } // *** ADD THIS: Debug logging for Pinecone fields *** if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) { //console.log('Saving Pinecone field:', name, '=', value); } // 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(); }); }); // *** ADD THIS: Update Pinecone checkbox state after successful save *** if (name && name.indexOf('mxchat_pinecone_addon_options[mxchat_use_pinecone]') !== -1) { //console.log('Pinecone toggle saved successfully, value:', value); // The checkbox state is already updated by the user interaction // But let's make sure the UI state matches the saved value var $checkbox = $('input[name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"]'); var settingsDiv = $('.mxchat-pinecone-settings'); // Double-check the UI state matches what was saved if (value === '1' && !$checkbox.is(':checked')) { $checkbox.prop('checked', true); settingsDiv.slideDown(300); } else if (value === '0' && $checkbox.is(':checked')) { $checkbox.prop('checked', false); settingsDiv.slideUp(300); } //console.log('Pinecone UI state synchronized'); // Check if Knowledge Import tab is currently active if ($('.mxchat-kb-tab-button[data-tab="import"]').hasClass('active')) { // Show a notice that we need to refresh var $knowledgeCard = $('#mxchat-kb-tab-import .mxchat-card').eq(1); if ($knowledgeCard.length > 0) { // Add a refresh notice at the top of the knowledge base card var refreshNotice = $('
' + '

' + '' + 'Database settings have changed. ' + 'Click here to refresh to see the updated knowledge base.' + '

'); $knowledgeCard.prepend(refreshNotice); } } else { // If not on import tab, set a flag to refresh when they go there sessionStorage.setItem('mxchat_pinecone_changed', 'true'); } } // *** ADD THIS: Debug logging for successful saves *** if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) { //console.log('Pinecone field saved successfully:', name, '=', value); } // Check if the response contains a "no changes" message and log it if (response.data && response.data.message === 'No changes detected') { //console.log('No changes detected for field:', name); } } else { // Only show alert for actual errors, not for "no changes" let errorMessage = response.data?.message || 'Unknown error'; // Don't display an alert for "no changes" message if (errorMessage !== 'No changes detected' && errorMessage !== 'Update failed or no changes') { alert('Error saving: ' + errorMessage); } else { // Still provide visual feedback that no changes were needed spinner.fadeOut(200, function() { feedbackContainer.append(successIcon); successIcon.fadeIn(200).delay(1000).fadeOut(200, function() { feedbackContainer.remove(); }); }); //console.log('No changes detected for field:', name); return; } // Only revert checkbox state if it was an actual error if (errorMessage !== 'No changes detected' && errorMessage !== 'Update failed or no changes') { if ($field.attr('type') === 'checkbox') { $field.prop('checked', !$field.is(':checked')); } } // Always clean up the feedback container feedbackContainer.remove(); } }, error: function(xhr, textStatus, error) { //console.error('AJAX Error:', textStatus, error); alert('An error occurred while saving. Please try again.'); // Revert checkbox state on error 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 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' || name.indexOf('mxchat_auto_sync_') === 0) { // Modified to catch all auto-sync fields 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')); 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'); // Removed localStorage saving functionality } else { //console.warn('Tab content #' + tabId + ' not found'); } }); } // Initialize tabs and handle events initTabs(); $(document).on('widget-added widget-updated postbox-toggled', initTabs); // Always activate the first tab (Chatbot) $('.mxchat-tab-button').first().trigger('click.mxchat'); // 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', '#toggleGeminiApiKeyVisibility' // Added Gemini toggle ].forEach(toggleVisibility); // 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', 'gemini_api_key': 'gemini' // Added Gemini API key mapping }; $.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 isGemini = chatModel && chatModel.startsWith('gemini-'); // Added Gemini detection const isOpenAIEmbedding = embeddingModel && embeddingModel.startsWith('text-embedding-'); const isVoyage = embeddingModel && embeddingModel.startsWith('voyage-'); const isGeminiEmbedding = embeddingModel && embeddingModel.startsWith('gemini-embedding-'); // Update API key visibility for each provider updateWrapperVisibility('openai', isOpenAIChat || isOpenAIEmbedding); updateWrapperVisibility('xai', isXAI); updateWrapperVisibility('claude', isClaude); updateWrapperVisibility('deepseek', isDeepSeek); updateWrapperVisibility('voyage', isVoyage); updateWrapperVisibility('gemini', isGemini || isGeminiEmbedding); // Updated Gemini visibility for both chat and embedding // 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.' ); } // Update provider-specific notices for Gemini if (isGemini && isGeminiEmbedding) { $('div[data-provider="gemini"] .api-key-notice').text( 'Required for your selected chat model and embedding model.' ); } else if (isGemini) { $('div[data-provider="gemini"] .api-key-notice').text( 'Required for your selected chat model.' ); } else if (isGeminiEmbedding) { $('div[data-provider="gemini"] .api-key-notice').text( 'Required for your selected embedding model.' ); } } /** * 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(); } } } // Add this to your JavaScript file function setupMxChatModelSelector() { const $modelSelect = $('#model'); const $modelSelectorButton = $('
`; $('body').append(modelSelectorModal); // Populate models grid // Populate models grid function populateModelsGrid(filter = '', category = 'all') { const $grid = $('#mxchat_models_grid'); $grid.empty(); const models = { gemini: [ { value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash', description: 'Next-Gen features, speed & multimodal generation' }, { value: 'gemini-2.0-flash-lite', label: 'Gemini 2.0 Flash-Lite', description: 'Cost-efficient with low latency' }, { value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro', description: 'Complex reasoning tasks requiring more intelligence' }, { value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash', description: 'Fast and versatile performance' }, ], openai: [ // NEW: GPT-5 family { value: 'gpt-5', label: 'GPT-5', description: 'Flagship for coding, reasoning, and agentic tasks across domains' }, { value: 'gpt-5-mini', label: 'GPT-5 Mini', description: 'Faster, more cost-efficient for well-defined tasks and precise prompts' }, { value: 'gpt-5-nano', label: 'GPT-5 Nano', description: 'Fastest and cheapest; ideal for summarization and classification' }, // Existing OpenAI models { value: 'gpt-4.1-2025-04-14', label: 'GPT-4.1', description: 'Flagship model for complex tasks' }, { value: 'gpt-4o', label: 'GPT-4o', description: 'Recommended for most use cases' }, { value: 'gpt-4o-mini', label: 'GPT-4o Mini', description: 'Fast and lightweight' }, { value: 'gpt-4-turbo', label: 'GPT-4 Turbo', description: 'High-performance model' }, { value: 'gpt-4', label: 'GPT-4', description: 'High intelligence model' }, { value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', description: 'Affordable and fast' }, ], claude: [ { value: 'claude-opus-4-20250514', label: 'Claude 4 Opus', description: 'Most capable Claude model' }, { value: 'claude-sonnet-4-20250514', label: 'Claude 4 Sonnet', description: 'High performance' }, { value: 'claude-3-7-sonnet-20250219', label: 'Claude 3.7 Sonnet', description: 'High intelligence' }, { value: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet', description: 'Intelligent and balanced' }, { value: 'claude-3-opus-20240229', label: 'Claude 3 Opus', description: 'Highly complex tasks' }, { value: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet', description: 'Balanced performance' }, { value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', description: 'Fastest Claude model' }, ], xai: [ { value: 'grok-4-0709', label: 'Grok 4', description: 'Latest flagship model - unparalleled performance in natural language, math and reasoning' }, // Added Grok-4 { value: 'grok-3-beta', label: 'Grok-3', description: 'Powerful model with 131K context' }, { value: 'grok-3-fast-beta', label: 'Grok-3 Fast', description: 'High performance with faster responses' }, { value: 'grok-3-mini-beta', label: 'Grok-3 Mini', description: 'Affordable model with good performance' }, { value: 'grok-3-mini-fast-beta', label: 'Grok-3 Mini Fast', description: 'Quick and cost-effective' }, { value: 'grok-2', label: 'Grok 2', description: 'Latest X.AI model' }, ], deepseek: [ { value: 'deepseek-chat', label: 'DeepSeek-V3', description: 'Advanced AI assistant' }, ], }; let allModels = []; Object.keys(models).forEach(key => { if (category === 'all' || category === key) { allModels = allModels.concat(models[key]); } }); // Filter by search term if present if (filter) { const lowerFilter = filter.toLowerCase(); allModels = allModels.filter(model => model.label.toLowerCase().includes(lowerFilter) || model.description.toLowerCase().includes(lowerFilter) ); } // Create model cards allModels.forEach(model => { const isSelected = $modelSelect.val() === model.value; const $modelCard = $(`
${getModelIcon(model.value)}

${model.label}

${model.description}

${isSelected ? '
' : ''}
`); $grid.append($modelCard); }); } // Helper function to get icon for each model function getModelIcon(modelValue) { if (modelValue.startsWith('gemini-')) return ''; if (modelValue.startsWith('gpt-')) return ''; if (modelValue.startsWith('claude-')) return ''; if (modelValue.startsWith('grok-')) return ''; if (modelValue.startsWith('deepseek-')) return ''; return ''; } // Event handlers $modelSelectorButton.on('click', function() { $('#mxchat_model_selector_modal').show(); populateModelsGrid('', 'all'); }); $('.mxchat-model-selector-modal-close, #mxchat_cancel_model_selection').on('click', function() { $('#mxchat_model_selector_modal').hide(); }); $('.mxchat-model-category-btn').on('click', function() { $('.mxchat-model-category-btn').removeClass('active'); $(this).addClass('active'); const category = $(this).data('category'); const searchTerm = $('#mxchat_model_search_input').val(); populateModelsGrid(searchTerm, category); }); $('#mxchat_model_search_input').on('input', function() { const searchTerm = $(this).val(); const activeCategory = $('.mxchat-model-category-btn.active').data('category'); populateModelsGrid(searchTerm, activeCategory); }); $(document).on('click', '.mxchat-model-selector-card', function() { const modelValue = $(this).data('value'); $modelSelect.val(modelValue).trigger('change'); updateButtonText(); $('#mxchat_model_selector_modal').hide(); }); // Close modal when clicking outside $(window).on('click', function(event) { if ($(event.target).is('#mxchat_model_selector_modal')) { $('#mxchat_model_selector_modal').hide(); } }); } // Embedding model selector - completely separate from chat model selector function setupMxChatEmbeddingModelSelector() { const $embeddingModelSelect = $('#embedding_model'); // Skip if the element doesn't exist on the page if ($embeddingModelSelect.length === 0) { return; } const $embeddingModelSelectorButton = $('
`; // Use jQuery's append to ensure it doesn't clash with existing modals $('body').append(embeddingModelSelectorModal); // Populate models grid function populateEmbeddingModelsGrid(filter = '', category = 'all') { const $grid = $('#mxchat_embedding_models_grid'); $grid.empty(); // Define embedding models with descriptions and context lengths const models = { openai: [ { value: 'text-embedding-3-small', label: 'TE3 Small', description: 'Fast and cost-effective embeddings (1536 dimensions, 8K context)' }, { value: 'text-embedding-ada-002', label: 'Ada 2', description: 'Balanced performance embeddings (1536 dimensions, 8K context)' }, { value: 'text-embedding-3-large', label: 'TE3 Large', description: 'High-performance embeddings (3072 dimensions, 8K context)' } ], voyage: [ { value: 'voyage-3-large', label: 'Voyage-3 Large', description: 'Advanced semantic search embeddings (2048 dimensions, 32K context)' } ], gemini: [ { value: 'gemini-embedding-exp-03-07', label: 'Gemini Embedding', description: 'Experimental SOTA embeddings (1536 dimensions, 8K context)' } ] }; let allModels = []; Object.keys(models).forEach(key => { if (category === 'all' || category === key) { allModels = allModels.concat(models[key]); } }); // Filter by search term if present if (filter) { const lowerFilter = filter.toLowerCase(); allModels = allModels.filter(model => model.label.toLowerCase().includes(lowerFilter) || model.description.toLowerCase().includes(lowerFilter) ); } // Create model cards allModels.forEach(model => { const isSelected = $embeddingModelSelect.val() === model.value; let providerClass = 'mxchat-embedding-model-provider-openai'; if (model.value.startsWith('voyage-')) { providerClass = 'mxchat-embedding-model-provider-voyage'; } else if (model.value.startsWith('gemini-embedding-')) { providerClass = 'mxchat-embedding-model-provider-gemini'; } let iconHTML = ''; if (model.value.startsWith('voyage-')) { iconHTML = ''; } else if (model.value.startsWith('gemini-embedding-')) { iconHTML = ''; } else { iconHTML = ''; } const $modelCard = $(`
${iconHTML}

${model.label}

${model.description}

${isSelected ? '
' : ''}
`); $grid.append($modelCard); }); } // Event handlers - use namespaced events to avoid conflicts $embeddingModelSelectorButton.on('click.embeddingModelSelector', function(e) { e.stopPropagation(); // Prevent event bubbling $('#' + embeddingModalId).show(); populateEmbeddingModelsGrid('', 'all'); }); $('.mxchat-embedding-model-selector-modal-close, #mxchat_cancel_embedding_model_selection').on('click.embeddingModelSelector', function(e) { e.stopPropagation(); // Prevent event bubbling $('#' + embeddingModalId).hide(); }); $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn').on('click.embeddingModelSelector', function(e) { e.stopPropagation(); // Prevent event bubbling $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn').removeClass('active'); $(this).addClass('active'); const category = $(this).data('category'); const searchTerm = $('#mxchat_embedding_model_search_input').val(); populateEmbeddingModelsGrid(searchTerm, category); }); $('#mxchat_embedding_model_search_input').on('input.embeddingModelSelector', function() { const searchTerm = $(this).val(); const activeCategory = $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn.active').data('category'); populateEmbeddingModelsGrid(searchTerm, activeCategory); }); // Use a direct selector to avoid conflicts with other card elements $(document).on('click.embeddingModelSelector', '.mxchat-embedding-model-selector-grid .mxchat-embedding-model-selector-card', function(e) { e.stopPropagation(); // Prevent event bubbling const modelValue = $(this).data('value'); // Important: Only update this specific select element $embeddingModelSelect.val(modelValue); // Manually trigger change only on this element const changeEvent = new Event('change', { bubbles: true }); $embeddingModelSelect[0].dispatchEvent(changeEvent); // Update button text updateButtonText(); // Hide modal $('#' + embeddingModalId).hide(); }); // Close modal when clicking outside - use namespaced events $(window).on('click.embeddingModelSelector', function(event) { if ($(event.target).is('#' + embeddingModalId)) { $('#' + embeddingModalId).hide(); } }); } // Call this function after the DOM is fully loaded $(document).ready(function() { setupMxChatModelSelector(); setupMxChatEmbeddingModelSelector(); }); // 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 handler $('.save-button').on('click', function() { var button = $(this); var row = button.closest('tr'); var id = button.data('id'); var nonce = button.data('nonce'); // Get nonce from button data attribute var newContent = row.find('.content-edit').val(); var newUrl = row.find('.url-edit').val(); //console.log('Nonce from button:', nonce); // Debug 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: nonce // Use nonce from button }, success: function(response) { button.prop('disabled', false); button.text('Save'); if (response.success) { row.find('.content-view').html(newContent.replace(/\n/g, "
")); if (newUrl) { row.find('.url-view').html('' + newUrl + ''); } else { row.find('.url-view').html('N/A'); } row.find('.content-edit, .url-edit').hide(); row.find('.content-view, .url-view').show(); row.find('.save-button').hide(); row.find('.edit-button').show(); } else { alert('Error saving content: ' + (response.data?.message || 'Unknown error')); } }, error: function() { button.prop('disabled', false); button.text('Save'); alert('An error occurred while saving.'); } }); }); // Questions handling $('.mxchat-add-question').on('click', function () { const container = $('#mxchat-additional-questions-container'); const questionCount = container.find('.mxchat-question-row').length + 4; const questionIndex = container.find('.mxchat-question-row').length; const newQuestion = `
`; container.append(newQuestion); }); $(document).on('click', '.mxchat-remove-question', function () { $(this).closest('.mxchat-question-row').remove(); saveQuestions(); }); $(document).on('change', '.mxchat-question-input', function() { saveQuestions(); }); function saveQuestions() { const questions = []; $('.mxchat-question-input').each(function() { const value = $(this).val().trim(); if (value) { questions.push(value); } }); const feedbackContainer = $('
'); const spinner = $('
'); const successIcon = $('
'); // Append feedback after the add button $('.mxchat-add-question').after(feedbackContainer); feedbackContainer.append(spinner); // Save via AJAX $.ajax({ url: mxchatAdmin.ajax_url, type: 'POST', data: { action: 'mxchat_save_setting', name: 'additional_popular_questions', value: JSON.stringify(questions), _ajax_nonce: mxchatAdmin.setting_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 questions: ' + (response.data?.message || 'Unknown error')); feedbackContainer.remove(); } }, error: function() { alert('An error occurred while saving questions.'); feedbackContainer.remove(); } }); } // Live agent status handler const statusToggle = document.getElementById('live_agent_status'); const statusText = statusToggle?.parentElement.nextElementSibling?.querySelector('.status-text'); if (statusToggle && statusText) { statusToggle.addEventListener('change', function() { // Update display text statusText.textContent = this.checked ? 'Online' : 'Offline'; // Send the correct on/off value to the server if (window.mxchatSaveSetting) { window.mxchatSaveSetting('live_agent_status', this.checked ? 'on' : 'off'); } }); } // Function to adjust the textarea height to content function adjustTextareaHeight() { this.style.height = 'auto'; // Reset to auto to calculate scrollHeight this.style.height = this.scrollHeight + 'px'; // Expand to content height } // Function to reset the textarea height to initial function resetTextareaHeight() { this.style.height = ''; // Remove inline height, reverting to CSS default } // Target the specific textarea by ID var $textarea = $('#system_prompt_instructions'); // Bind events $textarea.on('focus input', adjustTextareaHeight) // Expand on focus and input .on('blur', resetTextareaHeight); // Reset on blur }); document.addEventListener('DOMContentLoaded', function() { // Check if we're on the correct page before initializing const modal = document.getElementById('mxchat-action-modal'); // Only initialize if the modal exists on this page if (modal) { //console.log('MXChat Action Modal JS Loaded'); // Initialize the action modal functionality initStepBasedActionModal(); } // Function to initialize the step-based action modal function initStepBasedActionModal() { // We already checked for modal existence above, so no need to check again const actionStep1 = document.getElementById('mxchat-action-step-1'); const actionStep2 = document.getElementById('mxchat-action-step-2'); const backToStep1Btn = document.getElementById('mxchat-back-to-step-1'); const searchInput = document.getElementById('action-type-search'); const categoryButtons = modal.querySelectorAll('.mxchat-category-button'); const actionCards = modal.querySelectorAll('.mxchat-action-type-card'); const actionForm = document.getElementById('mxchat-action-form'); const callbackInput = document.getElementById('callback_function'); const actionIdField = document.getElementById('edit_action_id'); const labelField = document.getElementById('intent_label'); const phrasesField = document.getElementById('action_phrases'); const formActionType = document.getElementById('form_action_type'); const nonceContainer = document.getElementById('action-nonce-container'); const thresholdSlider = document.getElementById('similarity_threshold'); const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display'); // Rest of your initialization code remains the same... // Log the structure of one action card for debugging if (actionCards.length > 0) { //console.log('First action card data attributes:', actionCards[0].dataset); //console.log('First action card HTML:', actionCards[0].outerHTML); } // Add click event listeners to category buttons categoryButtons.forEach(button => { button.addEventListener('click', function() { //console.log('Category button clicked:', this.dataset.category); // Remove active class from all buttons categoryButtons.forEach(btn => btn.classList.remove('active')); // Add active class to clicked button this.classList.add('active'); // Get selected category const category = this.dataset.category; // Filter action cards filterActionCards(category, searchInput.value); }); }); // Add search functionality if (searchInput) { searchInput.addEventListener('input', function() { // Get active category const activeCategory = modal.querySelector('.mxchat-category-button.active')?.dataset.category || 'all'; //console.log('Search input changed, active category:', activeCategory); // Filter action cards filterActionCards(activeCategory, this.value); }); } // Add click event listeners to action cards actionCards.forEach(card => { card.addEventListener('click', function() { // Get the action data const isPro = this.dataset.pro === 'true'; const isInstalled = this.dataset.installed === 'true'; const addonName = this.dataset.addon || ''; const actionValue = this.dataset.value; const actionLabel = this.dataset.label; const actionIcon = this.querySelector('.dashicons').getAttribute('class').replace('dashicons dashicons-', ''); const actionDescription = this.querySelector('p').textContent; // Pro check using the proper detection method const proIsActivated = typeof mxchatAdmin !== 'undefined' && (mxchatAdmin.is_activated === '1' || mxchatAdmin.is_activated === 'true' || mxchatAdmin.is_activated === true); // Handle different states if (isPro && !proIsActivated) { // Pro feature but no Pro license showProFeatureNotice(); return; } if (addonName && !isInstalled) { // Add-on required but not installed const addonDisplayName = this.querySelector('.mxchat-addon-info')?.textContent?.replace('Requires ', '') || addonName + ' Add-on'; showAddonRequiredNotice(addonDisplayName); return; } // If we get here, the action is available - proceed as normal callbackInput.value = actionValue; // Update the selected action display in step 2 document.getElementById('selected-action-title').textContent = actionLabel; document.getElementById('selected-action-description').textContent = actionDescription; document.getElementById('selected-action-icon').innerHTML = ``; // Set a default label based on the action type (user can change it) if (!labelField.value) { labelField.value = actionLabel; } // Move to step 2 actionStep1.classList.remove('active'); actionStep2.classList.add('active'); // Update modal title }); }); // Back button functionality if (backToStep1Btn) { backToStep1Btn.addEventListener('click', function() { //console.log('Back button clicked'); actionStep2.classList.remove('active'); actionStep1.classList.add('active'); }); } // Function to filter action cards by category and search term function filterActionCards(category, searchTerm) { searchTerm = searchTerm.toLowerCase().trim(); //console.log(`Filtering cards by category: "${category}", search: "${searchTerm}"`); let visibleCount = 0; // Show all cards initially with animation actionCards.forEach((card, index) => { // Reset animation card.style.animation = 'none'; // Trigger reflow void card.offsetWidth; // Determine if card should be visible based on category and search term const cardCategory = card.dataset.category || ''; const matchesCategory = category === 'all' || cardCategory === category; const cardTitle = card.querySelector('h4')?.textContent?.toLowerCase() || ''; const cardDesc = card.querySelector('p')?.textContent?.toLowerCase() || ''; const matchesSearch = searchTerm === '' || cardTitle.includes(searchTerm) || cardDesc.includes(searchTerm); // Show/hide card with animation if (matchesCategory && matchesSearch) { card.style.display = 'flex'; // Staggered animation for cards card.style.animation = `fadeIn 0.2s ease forwards ${index * 0.03}s`; visibleCount++; } else { card.style.display = 'none'; } }); //console.log(`Filter results: ${visibleCount} cards visible out of ${actionCards.length}`); } // Function to show notice for Pro features function showProFeatureNotice() { //console.log('Showing Pro feature notice'); // Check if we already have a notification container let noticeContainer = document.querySelector('.mxchat-pro-notice'); if (!noticeContainer) { // Create the notice container noticeContainer = document.createElement('div'); noticeContainer.className = 'mxchat-pro-notice'; // Create content noticeContainer.innerHTML = `

MxChat Pro Feature

This action is available in the Pro version only.

Upgrade to Pro
`; // Append to body document.body.appendChild(noticeContainer); // Add close functionality const closeButton = noticeContainer.querySelector('.mxchat-pro-notice-close'); closeButton.addEventListener('click', function() { noticeContainer.classList.remove('active'); setTimeout(() => { noticeContainer.remove(); }, 300); }); // Click outside to close noticeContainer.addEventListener('click', function(e) { if (e.target === noticeContainer) { closeButton.click(); } }); // Show with animation setTimeout(() => { noticeContainer.classList.add('active'); }, 10); } else { // If it already exists, just make it visible again noticeContainer.classList.add('active'); } } // Function to show notice for add-on requirements function showAddonRequiredNotice(addonName) { //console.log(`Showing add-on notice for: ${addonName}`); // Check if we already have a notification container let noticeContainer = document.querySelector('.mxchat-addon-notice'); if (!noticeContainer) { // Create the notice container noticeContainer = document.createElement('div'); noticeContainer.className = 'mxchat-addon-notice'; // Create content noticeContainer.innerHTML = `
🧩

Add-on Required

This action requires the ${addonName} add-on to be installed.

Get Add-ons
`; // Append to body document.body.appendChild(noticeContainer); // Add close functionality const closeButton = noticeContainer.querySelector('.mxchat-addon-notice-close'); closeButton.addEventListener('click', function() { noticeContainer.classList.remove('active'); setTimeout(() => { noticeContainer.remove(); }, 300); }); // Click outside to close noticeContainer.addEventListener('click', function(e) { if (e.target === noticeContainer) { closeButton.click(); } }); // Show with animation setTimeout(() => { noticeContainer.classList.add('active'); }, 10); } else { // If it already exists, update the content const addonNameElement = noticeContainer.querySelector('p strong'); if (addonNameElement) { addonNameElement.textContent = addonName; } // Make it visible again noticeContainer.classList.add('active'); } } // Form submission handling if (actionForm) { actionForm.addEventListener('submit', function() { //console.log('Form submitted'); document.getElementById('mxchat-action-loading').style.display = 'flex'; this.querySelector('button[type="submit"]').disabled = true; }); } } // Setup add action buttons (only if we're on the correct page) if (modal) { // Update the modal open function to support the step-based flow window.mxchatOpenActionModal = function(isEdit = false, actionId = '', label = '', phrases = '', threshold = 85, callbackFunction = '', enabledBots = null) { //console.log('Modal opening, edit mode:', isEdit); // Get form fields const actionIdField = document.getElementById('edit_action_id'); const labelField = document.getElementById('intent_label'); const phrasesField = document.getElementById('action_phrases'); const formActionType = document.getElementById('form_action_type'); const callbackInput = document.getElementById('callback_function'); const saveButton = document.getElementById('mxchat-save-action-btn'); const nonceContainer = document.getElementById('action-nonce-container'); const thresholdSlider = document.getElementById('similarity_threshold'); const thresholdDisplay = document.querySelector('.mxchat-threshold-value-display'); const actionStep1 = document.getElementById('mxchat-action-step-1'); const actionStep2 = document.getElementById('mxchat-action-step-2'); const searchInput = document.getElementById('action-type-search'); // Set up modal for edit or create if (isEdit) { saveButton.textContent = 'Update Action'; formActionType.value = 'mxchat_edit_intent'; actionIdField.value = actionId; labelField.value = label; phrasesField.value = phrases; callbackInput.value = callbackFunction; thresholdSlider.value = threshold; // Set the current threshold value thresholdDisplay.textContent = threshold + '%'; // Update display // NEW: Handle bot selection checkboxes for editing // First uncheck all bot checkboxes document.querySelectorAll('input[name="enabled_bots[]"]').forEach(checkbox => { checkbox.checked = false; }); // Then check the ones that should be enabled if (enabledBots) { let botsArray; if (typeof enabledBots === 'string') { try { botsArray = JSON.parse(enabledBots); } catch (e) { console.warn('Failed to parse enabledBots:', enabledBots); botsArray = ['default']; // fallback } } else if (Array.isArray(enabledBots)) { botsArray = enabledBots; } else { botsArray = ['default']; // fallback } botsArray.forEach(botId => { const checkbox = document.querySelector(`input[name="enabled_bots[]"][value="${botId}"]`); if (checkbox) { checkbox.checked = true; } }); } else { // Fallback to default if no bot data const defaultCheckbox = document.querySelector('input[name="enabled_bots[]"][value="default"]'); if (defaultCheckbox) { defaultCheckbox.checked = true; } } // Update the nonce field for editing nonceContainer.innerHTML = ''; // Clear existing nonce if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.edit_intent_nonce) { nonceContainer.innerHTML = ``; } // For editing, go directly to step 2 and update the selected action display actionStep1.classList.remove('active'); actionStep2.classList.add('active'); // Find the matching action card to get its details const actionCards = document.querySelectorAll('.mxchat-action-type-card'); let foundCard = null; actionCards.forEach(card => { if (card.dataset.value === callbackFunction) { foundCard = card; } }); if (foundCard) { //console.log('Found matching action card for:', callbackFunction); const actionLabel = foundCard.dataset.label || foundCard.querySelector('h4')?.textContent || ''; const actionIconElement = foundCard.querySelector('.dashicons'); const actionIcon = actionIconElement ? actionIconElement.getAttribute('class').replace('dashicons dashicons-', '') : 'admin-generic'; const actionDescription = foundCard.querySelector('p')?.textContent || ''; document.getElementById('selected-action-title').textContent = actionLabel; document.getElementById('selected-action-description').textContent = actionDescription; document.getElementById('selected-action-icon').innerHTML = ``; } else { //console.log('No matching action card found for:', callbackFunction); // Fallback if we can't find the card document.getElementById('selected-action-title').textContent = label; document.getElementById('selected-action-description').textContent = 'Configure this action for your chatbot'; document.getElementById('selected-action-icon').innerHTML = ``; } } else { //console.log('Setting up create mode'); saveButton.textContent = 'Save Action'; formActionType.value = 'mxchat_add_intent'; actionIdField.value = ''; labelField.value = ''; phrasesField.value = ''; callbackInput.value = ''; thresholdSlider.value = 85; // Default value for new actions thresholdDisplay.textContent = '85%'; // Default display // For new actions, ensure default is checked and others are unchecked document.querySelectorAll('input[name="enabled_bots[]"]').forEach(checkbox => { checkbox.checked = (checkbox.value === 'default'); }); // Update the nonce field for adding nonceContainer.innerHTML = ''; // Clear existing nonce if (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.add_intent_nonce) { nonceContainer.innerHTML = ``; } // For creating new, start at step 1 actionStep1.classList.add('active'); actionStep2.classList.remove('active'); } // Show modal with animation modal.style.display = 'flex'; requestAnimationFrame(() => { modal.classList.add('active'); }); // Set up close handlers const closeModal = () => { //console.log('Closing modal'); 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 cancelBtns = modal.querySelectorAll('.mxchat-modal-cancel'); if (cancelBtns) { cancelBtns.forEach(btn => { btn.onclick = closeModal; }); } // Click outside modal to close modal.onclick = (e) => { if (e.target === modal) { closeModal(); } }; // Escape key to close modal document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && modal.classList.contains('active')) { closeModal(); } }, { once: true }); // Focus appropriate field based on current step if (isEdit || actionStep2.classList.contains('active')) { if (labelField) labelField.focus(); } else { if (searchInput) searchInput.focus(); } return closeModal; // Return close function for external use }; // Setup add action buttons const addActionBtn = document.getElementById('mxchat-add-action-btn'); if (addActionBtn) { //console.log('Add action button found'); addActionBtn.onclick = () => window.mxchatOpenActionModal(); } const createFirstAction = document.getElementById('mxchat-create-first-action'); if (createFirstAction) { //console.log('Create first action button found'); createFirstAction.onclick = () => window.mxchatOpenActionModal(); } // Setup edit buttons const editButtons = document.querySelectorAll('.mxchat-action-card .mxchat-edit-button'); //console.log('Edit buttons found:', editButtons.length); editButtons.forEach(button => { button.onclick = () => { const actionId = button.dataset.actionId; const phrases = button.dataset.phrases; const label = button.dataset.label; const threshold = button.dataset.threshold || 85; const callbackFunction = button.dataset.callbackFunction; const enabledBots = button.dataset.enabledBots; // ADD THIS LINE window.mxchatOpenActionModal(true, actionId, label, phrases, threshold, callbackFunction, enabledBots); }; }); } }); jQuery(document).ready(function($) { // Toggle custom post types container $('#mxchat-custom-post-types-toggle').on('click', function(e) { e.preventDefault(); $('#mxchat-custom-post-types-container').slideToggle(300); // Rotate the toggle icon const $icon = $(this).find('.mxchat-accordion-icon'); if ($('#mxchat-custom-post-types-container').is(':visible')) { $icon.css('transform', 'rotate(180deg)'); $(this).closest('.mxchat-settings-accordion').addClass('active'); } else { $icon.css('transform', 'rotate(0deg)'); $(this).closest('.mxchat-settings-accordion').removeClass('active'); } }); // If there are any selections made, auto-expand the container function autoExpandIfNeeded() { // Check if any checkbox in the container is checked const hasCheckedItems = $('#mxchat-custom-post-types-container input[type="checkbox"]:checked').length > 0; if (hasCheckedItems) { $('#mxchat-custom-post-types-container').show(); $('#mxchat-custom-post-types-toggle .mxchat-accordion-icon').css('transform', 'rotate(180deg)'); $('.mxchat-settings-accordion').addClass('active'); } } // Run on page load autoExpandIfNeeded(); }); document.addEventListener('DOMContentLoaded', function() { var viewSampleBtn = document.getElementById('mxchatViewSampleBtn'); var modal = document.getElementById('mxchatSampleModal'); var modalClose = document.getElementById('mxchatModalClose'); var closeBtn = document.getElementById('mxchatCloseBtn'); var copyBtn = document.getElementById('mxchatCopyBtn'); var instructionsContent = document.querySelector('.mxchat-instructions-content'); var modalContent = document.querySelector('.mxchat-instructions-modal-content'); if (!viewSampleBtn || !modal) { return; } // Open modal viewSampleBtn.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); modal.classList.add('mxchat-instructions-show'); }); // Close modal function function closeModal(e) { if (e) { e.preventDefault(); e.stopPropagation(); } modal.classList.remove('mxchat-instructions-show'); } // Close modal events if (modalClose) { modalClose.addEventListener('click', function(e) { closeModal(e); }); } if (closeBtn) { closeBtn.addEventListener('click', function(e) { closeModal(e); }); } // Close on backdrop click ONLY (not on hover) modal.addEventListener('click', function(e) { // Only close if clicking directly on the overlay, not on child elements if (e.target === modal) { closeModal(e); } }); // Prevent modal content clicks from closing the modal if (modalContent) { modalContent.addEventListener('click', function(e) { e.stopPropagation(); }); } // Close on escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && modal.classList.contains('mxchat-instructions-show')) { closeModal(); } }); // Copy functionality if (copyBtn && instructionsContent) { copyBtn.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); var text = instructionsContent.textContent; if (navigator.clipboard) { navigator.clipboard.writeText(text).then(function() { showCopySuccess(); }).catch(function() { fallbackCopy(text); }); } else { fallbackCopy(text); } }); } function fallbackCopy(text) { var textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; textArea.style.top = '-999999px'; document.body.appendChild(textArea); textArea.select(); try { document.execCommand('copy'); showCopySuccess(); } catch (err) { console.error('Copy failed'); } document.body.removeChild(textArea); } function showCopySuccess() { var originalText = copyBtn.innerHTML; copyBtn.innerHTML = 'Copied!'; setTimeout(function() { copyBtn.innerHTML = originalText; }, 2000); } }); jQuery(document).ready(function($) { var emailDependentFields = [ '#email_blocker_header_content', '#email_blocker_button_text', '#enable_name_field', '#name_field_placeholder' ]; // Add visual indicators emailDependentFields.forEach(function(fieldId) { var $row = $(fieldId).closest('tr'); $row.addClass('email-dependent-field'); // Add an icon to show it's dependent var $label = $row.find('th label, th'); $label.prepend(''); }); // Hide initially with opacity for smoother transition emailDependentFields.forEach(function(fieldId) { $(fieldId).closest('tr').hide().css('opacity', '0'); }); function toggleEmailFields() { var emailEnabled = $('#enable_email_block').is(':checked'); emailDependentFields.forEach(function(fieldId) { var $row = $(fieldId).closest('tr'); if (emailEnabled) { $row.slideDown(400).animate({opacity: 1}, 200); } else { $row.animate({opacity: 0}, 200).slideUp(400); } }); } toggleEmailFields(); $('#enable_email_block').on('change', toggleEmailFields); }); //Handle role restriction changes jQuery(document).ready(function($) { //Handle role restriction changes for both data sources $(document).on('change', '.mxchat-role-select', function() { const $select = $(this); const entryId = $select.data('entry-id'); const dataSource = $select.data('data-source'); const roleRestriction = $select.val(); const nonce = $select.data('nonce'); // Visual feedback $select.prop('disabled', true).addClass('updating'); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_update_role_restriction', nonce: nonce, entry_id: entryId, data_source: dataSource, // NEW: Include data source role_restriction: roleRestriction }, success: function(response) { if (response.success) { // Show success feedback $select.removeClass('updating').addClass('updated'); setTimeout(() => { $select.removeClass('updated'); }, 2000); } else { alert('Failed to update role restriction: ' + response.data); // Revert selection $select.val($select.data('original-value')); } }, error: function() { alert('Error updating role restriction'); // Revert selection $select.val($select.data('original-value')); }, complete: function() { $select.prop('disabled', false); } }); // Store original value for potential revert $select.data('original-value', roleRestriction); }); // Store initial values $('.mxchat-role-select').each(function() { $(this).data('original-value', $(this).val()); }); });