jQuery(document).ready(function($) { // ======================================== // UNIQUE URL GENERATOR FOR DIRECT CONTENT // ======================================== // Show/hide generate button based on checkbox $('#mxchat-unique-url-toggle').on('change', function() { const $button = $('#mxchat-generate-unique-url'); const $urlInput = $('#article_url'); if ($(this).is(':checked')) { $button.show(); // If URL field is empty and checkbox is enabled, generate immediately if (!$urlInput.val()) { generateUniqueUrl(); } } else { $button.hide(); } }); // Generate unique URL when button is clicked $('#mxchat-generate-unique-url').on('click', function() { generateUniqueUrl(); }); // Function to generate unique URL function generateUniqueUrl() { const $urlInput = $('#article_url'); let baseUrl = $urlInput.val().trim(); // If no URL provided, use a default if (!baseUrl) { baseUrl = window.location.origin; } // Split URL into base and hash fragment let hashFragment = ''; if (baseUrl.includes('#')) { const parts = baseUrl.split('#'); baseUrl = parts[0]; hashFragment = '#' + parts[1]; } // Remove any existing ref parameter (matches ref=anything up to & or end of string) baseUrl = baseUrl.replace(/[?&]ref=[^&]+(&|$)/, function(match, ending) { // If it ends with &, keep it; otherwise remove the whole thing return ending === '&' ? '&' : ''; }); // Clean up any trailing ? or & from URL baseUrl = baseUrl.replace(/[?&]$/, ''); // Generate unique reference (timestamp only for cleaner URLs) const timestamp = Date.now(); const uniqueRef = timestamp; // Add the unique reference as a query parameter (before hash) const separator = baseUrl.includes('?') ? '&' : '?'; const uniqueUrl = baseUrl + separator + 'ref=' + uniqueRef + hashFragment; // Update the input field $urlInput.val(uniqueUrl); // Visual feedback $urlInput.css('background-color', '#e7f7e7'); setTimeout(function() { $urlInput.css('background-color', ''); }, 1000); } // ======================================== // QUEUE PROCESSING SYSTEM // ======================================== let isProcessingQueue = false; let currentQueueId = null; let currentQueueType = null; // Process 5 items at a time const BATCH_SIZE = 5; // Check if we should start queue processing on page load checkForActiveQueues(); // Form submission handler - triggers queue processing $('#mxchat-url-form').on('submit', function(e) { //console.log('MxChat: Form submitted, queue will be created'); // Don't prevent default - let form submit normally // But schedule a check after redirect localStorage.setItem('mxchat_check_queue_after_submit', Date.now().toString()); }); // Check if we just submitted a form and need to start processing const justSubmitted = localStorage.getItem('mxchat_check_queue_after_submit'); if (justSubmitted) { const submitTime = parseInt(justSubmitted); const now = Date.now(); // If submitted within last 10 seconds, wait for queue to be created if (now - submitTime < 10000) { //console.log('MxChat: Form was just submitted, waiting for queue creation...'); localStorage.removeItem('mxchat_check_queue_after_submit'); // Show processing message if ($('.mxchat-processing-message').length === 0) { const message = $('
⏳ Queue created! Processing will start in a moment...
'); $('.mxchat-import-section').after(message); } // Check for queue multiple times with increasing delays setTimeout(function() { checkForActiveQueues(); }, 1000); setTimeout(function() { checkForActiveQueues(); }, 2000); setTimeout(function() { checkForActiveQueues(); }, 3000); setTimeout(function() { checkForActiveQueues(); }, 5000); } } function checkForActiveQueues() { $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_get_status_updates', nonce: mxchatAdmin.status_nonce }, success: function(response) { //console.log('MxChat: Checking for active queues...', response); if (response.sitemap_queue_id && response.sitemap_status) { if (response.sitemap_status.status === 'processing') { //console.log('MxChat: Found active sitemap queue:', response.sitemap_queue_id); startQueueProcessing(response.sitemap_queue_id, 'sitemap'); } else if (response.sitemap_status.status === 'complete') { //console.log('MxChat: Sitemap queue already complete'); // ADD THIS LINE: $('.mxchat-processing-message').remove(); // Show completed status card (no auto-refresh) updateSitemapStatus(response.sitemap_status); } } if (response.pdf_queue_id && response.pdf_status) { if (response.pdf_status.status === 'processing') { //console.log('MxChat: Found active PDF queue:', response.pdf_queue_id); startQueueProcessing(response.pdf_queue_id, 'pdf'); } else if (response.pdf_status.status === 'complete') { //console.log('MxChat: PDF queue already complete'); // ADD THIS LINE: $('.mxchat-processing-message').remove(); // Show completed status card (no auto-refresh) updatePdfStatus(response.pdf_status); } } // ADD THIS: If no queues found at all, remove the message if (!response.sitemap_queue_id && !response.pdf_queue_id) { $('.mxchat-processing-message').remove(); } }, error: function(xhr, status, error) { console.error('MxChat: Error checking for active queues:', error); // ADD THIS: Remove message on error too $('.mxchat-processing-message').remove(); } }); } /** * Start processing a queue */ function startQueueProcessing(queueId, queueType) { if (isProcessingQueue) { //console.log('MxChat: Already processing a queue, skipping'); return; } isProcessingQueue = true; currentQueueId = queueId; currentQueueType = queueType; //console.log('MxChat: Starting queue processing:', queueId, queueType); // Remove any "waiting" messages $('.mxchat-processing-message').remove(); // Create or update status card createOrUpdateStatusCard(queueType); // Start real-time entries polling if function exists if (typeof startEntriesPolling === 'function') { startEntriesPolling(); } // Start the batch processing loop processNextBatch(); } /** * Process the next batch of items (5 at a time) */ function processNextBatch() { if (!isProcessingQueue) { //console.log('MxChat: Processing stopped'); return; } // Fetch the next batch of items const fetchPromises = []; for (let i = 0; i < BATCH_SIZE; i++) { const promise = $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_get_next_queue_item', nonce: mxchatAdmin.queue_nonce, queue_id: currentQueueId } }); fetchPromises.push(promise); } // Wait for all fetch requests to complete Promise.all(fetchPromises).then(function(responses) { // Filter out completed/error responses and extract items const items = []; let queueComplete = false; for (let response of responses) { if (response.success && response.data && !response.data.complete) { items.push(response.data.item); } else if (response.data && response.data.complete) { queueComplete = true; } } // If no items to process, queue is done if (items.length === 0) { if (queueComplete) { handleQueueComplete(); } else { verifyQueueCompletion(); } return; } // Process all items in this batch simultaneously const processPromises = items.map(item => processQueueItem(item)); // Wait for all items to finish processing Promise.all(processPromises).then(function() { // Update progress after batch completes updateQueueProgress(); // If we got fewer items than batch size, queue might be done if (items.length < BATCH_SIZE || queueComplete) { verifyQueueCompletion(); } else { // Process next batch immediately processNextBatch(); } }).catch(function(error) { console.error('MxChat: Error processing batch:', error); // Continue anyway updateQueueProgress(); setTimeout(function() { processNextBatch(); }, 1000); }); }).catch(function(error) { console.error('MxChat: Error fetching batch:', error); // Verify queue status before retrying setTimeout(function() { verifyQueueCompletion(); }, 2000); }); } /** * Verify if queue is actually complete * Prevents infinite loops when last item fails */ function verifyQueueCompletion() { //console.log('MxChat: Verifying queue completion status...'); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_get_queue_status', nonce: mxchatAdmin.queue_nonce, queue_id: currentQueueId }, success: function(response) { if (response.success) { const status = response.data; // If no pending or processing items, queue is done if (status.pending === 0 && status.processing === 0) { //console.log('MxChat: Queue verified as complete'); handleQueueComplete(); } else { // Still has items, try to continue //console.log('MxChat: Queue still has pending items, continuing...'); processNextBatch(); } } else { // Can't verify, assume complete to prevent infinite loop //console.log('MxChat: Could not verify queue status, assuming complete'); handleQueueComplete(); } }, error: function() { // Can't verify, assume complete to prevent infinite loop //console.log('MxChat: Network error verifying queue, assuming complete'); handleQueueComplete(); } }); } /** * Process a single queue item * Returns a Promise that resolves when processing is complete */ function processQueueItem(item) { return $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_process_queue_item', nonce: mxchatAdmin.queue_nonce, item_id: item.id, item_type: item.type, item_data: item.data, bot_id: item.bot_id } }).then(function(response) { if (response.success) { // Item processed successfully //console.log('MxChat: Item processed successfully:', item.id); return true; } else { // Item failed but we KEEP GOING console.warn('MxChat: Item processing failed (will continue):', item.type, item.id); console.warn('MxChat: Error details:', response.data); return false; } }).catch(function(xhr, status, error) { // Network error - log it but KEEP GOING console.error('MxChat: AJAX/Network error processing item:', item.id, error); return false; }); } /** * Update queue progress (fetches latest stats) */ function updateQueueProgress() { $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_get_queue_status', nonce: mxchatAdmin.queue_nonce, queue_id: currentQueueId }, success: function(response) { if (response.success) { const status = response.data; // Update the appropriate status card if (currentQueueType === 'pdf') { updatePdfStatusFromQueue(status); } else { updateSitemapStatusFromQueue(status); } } } }); } /** * Handle queue completion * NO AUTO-REFRESH - Show completed card with errors until dismissed */ function handleQueueComplete() { isProcessingQueue = false; // Stop real-time entries polling if function exists if (typeof stopEntriesPolling === 'function') { stopEntriesPolling(); } // Refresh the knowledge base table with properly grouped entries refreshKnowledgeBaseTable(); // Get final status with error details $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_get_queue_status', nonce: mxchatAdmin.queue_nonce, queue_id: currentQueueId }, success: function(response) { if (response.success) { const status = response.data; // Show completed status card (NO REFRESH) if (currentQueueType === 'pdf') { showCompletedPdfCard(status); } else { showCompletedSitemapCard(status); } // Mark the queue as complete on server markQueueAsComplete(currentQueueId); // Show notification based on results if (status.failed > 0) { showNotification('warning', `Processing completed: ${status.completed} succeeded, ${status.failed} failed. ` + `Review errors below and dismiss when ready.` ); } else { showNotification('success', `Processing completed successfully! All ${status.completed} items processed. ` + `Dismiss the status card when ready.` ); } // NO AUTO-REFRESH - User must manually dismiss } else { // Couldn't get final status, just show generic completion showNotification('success', 'Processing completed! Refresh page to see final results.'); } }, error: function() { // Error getting final status showNotification('success', 'Processing completed! Refresh page to see final results.'); } }); } /** * Show completed PDF card with full error details (NO RETRY BUTTON) */ function showCompletedPdfCard(status) { let $card = $('.mxchat-status-card:contains("PDF Processing")'); if ($card.length === 0) { return; } // Remove processing UI elements $card.find('.mxchat-stop-form').remove(); $card.find('.mxchat-status-warning').remove(); // Update header with completion badge $card.find('.mxchat-status-badge').remove(); if (status.failed > 0) { $card.find('.mxchat-status-header h4').after( '⚠️ Completed with ' + status.failed + ' failures - Refresh to view entries' ); } else { $card.find('.mxchat-status-header h4').after( 'βœ“ Complete - Refresh to view entries' ); } // Add dismiss button addDismissButton($card); // Update progress bar to 100% $card.find('.mxchat-progress-fill').css('width', '100%'); // Update details with final stats let detailsHtml = '
'; detailsHtml += '

πŸ“Š Final Results

'; detailsHtml += '

Total Pages: ' + status.total + '

'; detailsHtml += '

βœ“ Successfully Processed: ' + status.completed + '

'; if (status.failed > 0) { detailsHtml += '

βœ— Failed: ' + status.failed + '

'; } detailsHtml += '
'; // Add error details if there are failures (NO RETRY BUTTON) if (status.failed > 0 && status.failed_items && status.failed_items.length > 0) { detailsHtml += '
'; detailsHtml += '

⚠️ Failed Pages

'; detailsHtml += '
'; detailsHtml += ''; detailsHtml += ''; detailsHtml += ''; status.failed_items.forEach(function(item) { let data; try { data = JSON.parse(item.item_data); } catch(e) { data = {}; } const pageNum = data.page_number || 'Unknown'; detailsHtml += ''; detailsHtml += ''; detailsHtml += ''; detailsHtml += ''; detailsHtml += ''; }); detailsHtml += '
PageErrorAttempts
Page ' + pageNum + '' + (item.error_message || 'Unknown error') + '' + item.attempts + '
'; detailsHtml += '
'; detailsHtml += '
'; } $card.find('.mxchat-status-details').html(detailsHtml); } /** * Show completed sitemap card with full error details (NO RETRY BUTTON) */ function showCompletedSitemapCard(status) { let $card = $('.mxchat-status-card:contains("Sitemap Processing")'); if ($card.length === 0) { return; } // Remove processing UI elements $card.find('.mxchat-stop-form').remove(); $card.find('.mxchat-status-warning').remove(); // Update header with completion badge $card.find('.mxchat-status-badge').remove(); if (status.failed > 0) { $card.find('.mxchat-status-header h4').after( '⚠️ Completed with ' + status.failed + ' failures - Refresh to view entries' ); } else { $card.find('.mxchat-status-header h4').after( 'βœ“ Complete - Refresh to view entries' ); } // Add dismiss button addDismissButton($card); // Update progress bar to 100% $card.find('.mxchat-progress-fill').css('width', '100%'); // Update details with final stats let detailsHtml = '
'; detailsHtml += '

πŸ“Š Final Results

'; detailsHtml += '

Total URLs: ' + status.total + '

'; detailsHtml += '

βœ“ Successfully Processed: ' + status.completed + '

'; if (status.failed > 0) { detailsHtml += '

βœ— Failed: ' + status.failed + '

'; } detailsHtml += '
'; // Add error details if there are failures (NO RETRY BUTTON) if (status.failed > 0 && status.failed_items && status.failed_items.length > 0) { detailsHtml += '
'; detailsHtml += '

⚠️ Failed URLs

'; detailsHtml += '
'; detailsHtml += ''; detailsHtml += ''; detailsHtml += ''; status.failed_items.forEach(function(item) { let data; try { data = JSON.parse(item.item_data); } catch(e) { data = {}; } const url = data.url || data.pdf_url || 'Unknown URL'; const pageInfo = data.page_number ? ' (page ' + data.page_number + ')' : ''; const displayUrl = (url.length > 60 ? url.substring(0, 57) + '...' : url) + pageInfo; detailsHtml += ''; detailsHtml += ''; detailsHtml += ''; detailsHtml += ''; detailsHtml += ''; }); detailsHtml += '
URLErrorAttempts
' + displayUrl + '' + (item.error_message || 'Unknown error') + '' + item.attempts + '
'; detailsHtml += '
'; detailsHtml += '
'; } $card.find('.mxchat-status-details').html(detailsHtml); } /** * Mark queue as complete on server side * This prevents it from auto-starting on page refresh */ function markQueueAsComplete(queueId) { // This is a fire-and-forget call to update queue status $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_mark_queue_complete', nonce: mxchatAdmin.queue_nonce, queue_id: queueId }, success: function(response) { //console.log('MxChat: Queue marked as complete on server'); }, error: function() { //console.log('MxChat: Could not mark queue as complete, but continuing'); } }); } /** * Stop processing button handler */ $(document).on('submit', '.mxchat-stop-form', function() { //console.log('MxChat: Stop processing requested'); isProcessingQueue = false; currentQueueId = null; currentQueueType = null; }); /** * Create or update status card */ function createOrUpdateStatusCard(queueType) { const cardTitle = queueType === 'pdf' ? 'PDF Processing Status' : 'Sitemap Processing Status'; let $card = $('.mxchat-status-card:contains("' + cardTitle + '")'); if ($card.length === 0) { // Create new card let html = '
'; html += '
'; html += '

' + cardTitle + '

'; html += '
'; html += '⚠️ Keep this tab open - Processing runs in your browser'; html += '
'; html += '
'; html += ''; html += '
'; html += '
'; html += '
'; html += '
'; html += '
'; html += '
'; html += '

Initializing...

'; html += '
'; html += '
'; // Insert card let $importSection = $('.mxchat-import-section'); if ($importSection.length > 0) { $importSection.after($(html)); } } } /** * Update PDF status from queue data (DURING PROCESSING) */ function updatePdfStatusFromQueue(status) { let $card = $('.mxchat-status-card:contains("PDF Processing")'); if ($card.length === 0) { return; } // Update progress bar $card.find('.mxchat-progress-fill').css('width', status.percentage + '%'); // Update details let detailsHtml = '

Progress: ' + (status.completed + status.failed) + ' of ' + status.total + ' pages (' + status.percentage + '%)

'; if (status.completed > 0) { detailsHtml += '

βœ“ Processed successfully: ' + status.completed + '

'; } if (status.failed > 0) { detailsHtml += '

βœ— Failed pages: ' + status.failed + '

'; } detailsHtml += '

Status: Processing

'; $card.find('.mxchat-status-details').html(detailsHtml); } /** * Update sitemap status from queue data (DURING PROCESSING) */ function updateSitemapStatusFromQueue(status) { let $card = $('.mxchat-status-card:contains("Sitemap Processing")'); if ($card.length === 0) { return; } // Update progress bar $card.find('.mxchat-progress-fill').css('width', status.percentage + '%'); // Update details let detailsHtml = '

Progress: ' + (status.completed + status.failed) + ' of ' + status.total + ' URLs (' + status.percentage + '%)

'; if (status.completed > 0) { detailsHtml += '

βœ“ Processed successfully: ' + status.completed + '

'; } if (status.failed > 0) { detailsHtml += '

βœ— Failed URLs: ' + status.failed + '

'; } detailsHtml += '

Status: Processing

'; $card.find('.mxchat-status-details').html(detailsHtml); } // ======================================== // STATUS UPDATES FOR COMPLETED QUEUES (FROM SERVER) // ======================================== /** * Dismiss completed status button handler */ $(document).on('click', '.mxchat-dismiss-button', function() { const $button = $(this); const $card = $button.closest('.mxchat-status-card'); let cardType = $card.data('card-type'); if (!cardType) { cardType = $card.find('h4').text().includes('PDF') ? 'pdf' : 'sitemap'; } $card.fadeOut(300, function() { $(this).remove(); }); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_clear_queue', nonce: mxchatAdmin.queue_nonce, queue_id: $card.data('queue-id') || '' }, success: function(response) { //console.log('MxChat: Queue cleared'); } }); }); /** * Update PDF status card (for already completed queues on page load) */ function updatePdfStatus(status) { let $pdfCard = $('.mxchat-status-card:contains("PDF Processing")'); if ($pdfCard.length === 0 && status) { createPdfStatusCard(status); $pdfCard = $('.mxchat-status-card:contains("PDF Processing")'); } if ($pdfCard.length > 0 && status.status === 'complete') { // Show as completed (same as showCompletedPdfCard but from server data) showCompletedPdfCard(status); } } /** * Update sitemap status card (for already completed queues on page load) */ function updateSitemapStatus(status) { let $sitemapCard = $('.mxchat-status-card:contains("Sitemap Processing")'); if ($sitemapCard.length === 0 && status) { createSitemapStatusCard(status); $sitemapCard = $('.mxchat-status-card:contains("Sitemap Processing")'); } if ($sitemapCard.length > 0 && status.status === 'complete') { // Show as completed (same as showCompletedSitemapCard but from server data) showCompletedSitemapCard(status); } } /** * Create PDF status card */ function createPdfStatusCard(status) { let html = '
'; html += '
'; html += '

PDF Processing Status

'; html += '
'; html += '
'; html += '
'; html += '
'; html += '
'; html += '

Progress: ' + status.processed_pages + ' of ' + status.total_pages + ' pages

'; html += '
'; html += '
'; $('.mxchat-import-section').after($(html)); } /** * Create sitemap status card */ function createSitemapStatusCard(status) { let html = '
'; html += '
'; html += '

Sitemap Processing Status

'; html += '
'; html += '
'; html += '
'; html += '
'; html += '
'; html += '

Progress: ' + status.processed_urls + ' of ' + status.total_urls + ' URLs

'; html += '
'; html += '
'; $('.mxchat-import-section').after($(html)); } /** * Add dismiss button to completed cards */ function addDismissButton($card) { if ($card.find('.mxchat-dismiss-button').length === 0) { const dismissButton = $(''); $card.find('.mxchat-status-header').append(dismissButton); } } /** * Show notification helper */ function showNotification(type, message) { const $notification = $('
' + message + '
'); $('.mxchat-content, body').first().prepend($notification); setTimeout(function() { $notification.fadeOut(300, function() { $(this).remove(); }); }, 5000); } // ======================================== // ROLE-BASED CONTENT RESTRICTIONS (Keep existing code) // ======================================== if ($('#mxchat-mappings-container').length > 0) { loadTagRoleMappings(); } $('#mxchat-add-tag-role').on('click', function() { const tagSlug = $('#mxchat-tag-input').val().trim(); const roleRestriction = $('#mxchat-role-select').val(); if (!tagSlug) { alert('Please enter a tag name'); return; } const $btn = $(this); $btn.prop('disabled', true).html(' Adding...'); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_add_tag_role_mapping', nonce: mxchatAdmin.settings_nonce, tag_slug: tagSlug, role_restriction: roleRestriction }, success: function(response) { if (response.success) { $('#mxchat-tag-input').val(''); $('#mxchat-role-select').val('public'); loadTagRoleMappings(); showNotification('success', 'Tag-role mapping added successfully!'); } else { alert('Error: ' + response.data); } $btn.prop('disabled', false).html(' Add Mapping'); }, error: function() { alert('Network error occurred'); $btn.prop('disabled', false).html(' Add Mapping'); } }); }); $(document).on('click', '.mxchat-delete-mapping', function() { if (!confirm('Are you sure you want to delete this mapping?')) { return; } const $btn = $(this); const $row = $btn.closest('tr'); const tagSlug = $btn.data('tag-slug'); $btn.html(' Deleting...'); $row.addClass('mxchat-row-deleting'); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_delete_tag_role_mapping', nonce: mxchatAdmin.settings_nonce, tag_slug: tagSlug }, success: function(response) { if (response.success) { $row.fadeOut(300, function() { $(this).remove(); if ($('.mxchat-mappings-table tbody tr').length === 0) { $('.mxchat-mappings-table').hide(); $('#mxchat-no-mappings').show(); } }); showNotification('success', 'Mapping deleted successfully!'); } else { alert('Error: ' + response.data); $btn.html(' Delete'); $row.removeClass('mxchat-row-deleting'); } }, error: function() { alert('Network error occurred'); $btn.html(' Delete'); $row.removeClass('mxchat-row-deleting'); } }); }); $('#mxchat-bulk-update-roles').on('click', function() { if (!confirm('This will update role restrictions for all existing content with mapped tags. Continue?')) { return; } const $btn = $(this); const $progress = $('#mxchat-bulk-update-progress'); const $result = $('#mxchat-bulk-update-result'); $progress.show(); $result.hide(); $btn.prop('disabled', true); $progress.find('.mxchat-progress-text').text('Starting bulk update...'); $progress.find('.mxchat-progress-fill').css('width', '0%'); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_bulk_update_tag_roles', nonce: mxchatAdmin.settings_nonce }, success: function(response) { $progress.hide(); $btn.prop('disabled', false); if (response.success) { $result.removeClass('error').addClass('success'); let resultHtml = '
Bulk Update Complete
'; resultHtml += '

Total Updated: ' + response.data.updated_count + '

'; resultHtml += '

Tags Processed: ' + response.data.tags_processed + '

'; if (response.data.details && response.data.details.length > 0) { resultHtml += ''; } $result.html(resultHtml).show(); showNotification('success', 'Bulk update completed successfully!'); } else { $result.removeClass('success').addClass('error'); $result.html('
Update Failed

' + response.data + '

').show(); } }, error: function() { $progress.hide(); $btn.prop('disabled', false); $result.removeClass('success').addClass('error'); $result.html('
Network Error

Please try again.

').show(); } }); }); function loadTagRoleMappings() { const $container = $('#mxchat-mappings-container'); $container.html('
Loading mappings...
'); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_get_tag_role_mappings', nonce: mxchatAdmin.settings_nonce }, success: function(response) { if (response.success && response.data.mappings.length > 0) { $('#mxchat-no-mappings').hide(); let html = ''; html += ''; response.data.mappings.forEach(function(mapping) { html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; }); html += '
TagRole RestrictionPosts with TagActions
' + mapping.tag_slug + '' + mapping.role_label + '' + mapping.post_count + '
'; $container.html(html); } else { $container.html(''); $('#mxchat-no-mappings').show(); } }, error: function() { $container.html('
Failed to load mappings. Please refresh the page.
'); } }); } // ======================================== // PINECONE DELETE HANDLER (Keep existing code) // ======================================== $(document).on('click', '.delete-button-ajax', function(e) { e.preventDefault(); if (!confirm('Are you sure you want to delete this entry?')) { return; } var $button = $(this); var $row = $button.closest('tr'); var vectorId = $button.data('vector-id'); var botId = $button.data('bot-id') || 'default'; var nonce = $button.data('nonce'); $button.prop('disabled', true); $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update-alt'); $row.addClass('mxchat-row-deleting'); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_delete_pinecone_prompt', nonce: nonce, vector_id: vectorId, bot_id: botId }, success: function(response) { if (response.success) { $row.fadeOut(500, function() { $(this).remove(); // Update entry count displays (header and sidebar) var $countSpan = $('#mxchat-entry-count'); if ($countSpan.length) { var currentText = $countSpan.text(); var match = currentText.match(/\((\d+)\)/); if (match) { var newCount = Math.max(0, parseInt(match[1]) - 1); updateEntryCount(newCount); } } }); $('

Entry deleted successfully from Pinecone.

') .insertAfter('.mxchat-hero') .delay(3000) .fadeOut(); } else { $button.prop('disabled', false); $button.find('.dashicons').removeClass('dashicons-update-alt').addClass('dashicons-trash'); $row.removeClass('mxchat-row-deleting'); alert('Error: ' + response.data); } }, error: function() { $button.prop('disabled', false); $button.find('.dashicons').removeClass('dashicons-update-alt').addClass('dashicons-trash'); $row.removeClass('mxchat-row-deleting'); alert('Network error occurred'); } }); }); // ======================================== // WORDPRESS DATABASE DELETE HANDLER (AJAX) // ======================================== $(document).on('click', '.delete-button-wordpress', function(e) { e.preventDefault(); if (!confirm('Are you sure you want to delete this entry?')) { return; } var $button = $(this); var $row = $button.closest('tr'); var entryId = $button.data('entry-id'); var nonce = $button.data('nonce'); $button.prop('disabled', true); $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update-alt spin'); $row.addClass('mxchat-row-deleting'); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_delete_wordpress_prompt', nonce: nonce, entry_id: entryId }, success: function(response) { if (response.success) { $row.fadeOut(500, function() { $(this).remove(); // Update entry count displays (header and sidebar) var $countSpan = $('#mxchat-entry-count'); if ($countSpan.length) { var currentText = $countSpan.text(); var match = currentText.match(/\((\d+)\)/); if (match) { var newCount = Math.max(0, parseInt(match[1]) - 1); updateEntryCount(newCount); } } }); $('

Entry deleted successfully.

') .insertAfter('.mxchat-hero') .delay(3000) .fadeOut(); } else { $button.prop('disabled', false); $button.find('.dashicons').removeClass('dashicons-update-alt spin').addClass('dashicons-trash'); $row.removeClass('mxchat-row-deleting'); alert('Error: ' + response.data); } }, error: function() { $button.prop('disabled', false); $button.find('.dashicons').removeClass('dashicons-update-alt spin').addClass('dashicons-trash'); $row.removeClass('mxchat-row-deleting'); alert('Network error occurred'); } }); }); // ======================================== // BULK SELECTION FOR KNOWLEDGE ENTRIES // ======================================== var selectedKnowledgeEntries = new Set(); // Update selection UI function updateKnowledgeSelectionUI() { var count = selectedKnowledgeEntries.size; var $countEl = $('#mxchat-selected-entry-count'); var $deleteBtn = $('#mxchat-delete-selected-entries'); var $deleteAllForm = $('#mxchat-delete-all-form'); if (count > 0) { // Show Delete Selected button, hide Delete All form $deleteAllForm.hide(); $deleteBtn.show(); $countEl.text('(' + count + ')'); } else { // Show Delete All form, hide Delete Selected button $deleteAllForm.show(); $deleteBtn.hide(); $countEl.text(''); } // Update select all checkbox state var totalItems = $('.mxchat-entry-checkbox').length; var checkedItems = $('.mxchat-entry-checkbox:checked').length; $('.mxchat-entry-checkbox-all').prop('checked', totalItems > 0 && checkedItems === totalItems); $('.mxchat-entry-checkbox-all').prop('indeterminate', checkedItems > 0 && checkedItems < totalItems); } // Select all checkbox handler $(document).on('change', '.mxchat-entry-checkbox-all', function() { var isChecked = $(this).is(':checked'); // Sync all select-all checkboxes $('.mxchat-entry-checkbox-all').prop('checked', isChecked); $('.mxchat-entry-checkbox').prop('checked', isChecked); if (isChecked) { $('.mxchat-entry-checkbox').each(function() { var entryData = { id: $(this).data('entry-id'), source: $(this).data('source'), sourceUrl: $(this).data('source-url'), isGroup: $(this).data('is-group'), chunkCount: $(this).data('chunk-count') || 1 }; selectedKnowledgeEntries.add(JSON.stringify(entryData)); $(this).closest('tr').addClass('selected'); }); } else { selectedKnowledgeEntries.clear(); $('tr.selected').removeClass('selected'); } updateKnowledgeSelectionUI(); }); // Individual checkbox handler $(document).on('change', '.mxchat-entry-checkbox', function() { var $checkbox = $(this); var $row = $checkbox.closest('tr'); var entryData = { id: $checkbox.data('entry-id'), source: $checkbox.data('source'), sourceUrl: $checkbox.data('source-url'), isGroup: $checkbox.data('is-group'), chunkCount: $checkbox.data('chunk-count') || 1 }; var entryKey = JSON.stringify(entryData); if ($checkbox.is(':checked')) { selectedKnowledgeEntries.add(entryKey); $row.addClass('selected'); } else { selectedKnowledgeEntries.delete(entryKey); $row.removeClass('selected'); } updateKnowledgeSelectionUI(); }); // Bulk delete button handler $(document).on('click', '#mxchat-delete-selected-entries', function() { var count = selectedKnowledgeEntries.size; if (count === 0) return; if (!confirm('Are you sure you want to delete ' + count + ' selected entries? This action cannot be undone.')) { return; } var $button = $(this); var nonce = $button.data('nonce'); var botId = $button.data('bot-id'); // Parse selected entries var entries = Array.from(selectedKnowledgeEntries).map(function(entryStr) { return JSON.parse(entryStr); }); // Show loading state $button.prop('disabled', true); $button.find('.mxchat-bulk-delete-text').text('Deleting...'); $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update spin'); // Mark rows as deleting entries.forEach(function(entry) { $('#prompt-' + entry.id).addClass('mxchat-row-deleting'); }); $.ajax({ url: ajaxurl, type: 'POST', timeout: 120000, // 120 seconds β€” matches server-side set_time_limit data: { action: 'mxchat_bulk_delete_knowledge', entries: entries, bot_id: botId, nonce: nonce }, success: function(response) { if (response.success) { var data = response.data; // Remove successful rows if (data.success_ids && data.success_ids.length > 0) { data.success_ids.forEach(function(id) { var $row = $('#prompt-' + id); // Also remove child chunk rows if it's a group var groupId = $row.data('group-id'); if (groupId) { $('.mxchat-chunk-row.' + groupId).fadeOut(300, function() { $(this).remove(); }); } $row.fadeOut(300, function() { $(this).remove(); }); }); } // Handle failed entries if (data.failed_ids && data.failed_ids.length > 0) { data.failed_ids.forEach(function(id) { $('#prompt-' + id).removeClass('mxchat-row-deleting').addClass('mxchat-row-error'); }); } // Show result message var successCount = data.success_ids ? data.success_ids.length : 0; var failedCount = data.failed_ids ? data.failed_ids.length : 0; var message = 'Deleted ' + successCount + ' entries.'; if (failedCount > 0) { message += ' ' + failedCount + ' entries failed to delete.'; } $('

' + message + '

') .insertAfter('.mxchat-hero') .delay(5000) .fadeOut(); // Clear selection selectedKnowledgeEntries.clear(); updateKnowledgeSelectionUI(); // Update entry count displays (header and sidebar) if (successCount > 0) { var $countSpan = $('#mxchat-entry-count'); if ($countSpan.length) { var currentText = $countSpan.text(); var match = currentText.match(/\((\d+)\)/); if (match) { var newCount = Math.max(0, parseInt(match[1]) - successCount); updateEntryCount(newCount); } } } } else { alert('Error: ' + (response.data || 'Unknown error')); $('tr.mxchat-row-deleting').removeClass('mxchat-row-deleting'); } }, error: function(jqXHR, textStatus) { var message = 'An error occurred while deleting entries.'; if (textStatus === 'timeout') { message = 'The deletion request timed out. Please refresh the page to check which entries were deleted, then try again for any remaining.'; } else if (textStatus === 'error' && jqXHR.status === 0) { message = 'Network error: The server took too long to respond. Please refresh and try deleting fewer entries at a time.'; } else if (jqXHR.responseJSON && jqXHR.responseJSON.data) { message = 'Error: ' + jqXHR.responseJSON.data; } alert(message); $('tr.mxchat-row-deleting').removeClass('mxchat-row-deleting'); }, complete: function() { $button.prop('disabled', selectedKnowledgeEntries.size === 0); $button.find('.mxchat-bulk-delete-text').text('Delete Selected'); $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash'); } }); }); // Clear selection when page changes $(document).on('click', '.mxchat-page-link', function() { selectedKnowledgeEntries.clear(); updateKnowledgeSelectionUI(); }); // ======================================== // AJAX PAGINATION FOR KNOWLEDGE BASE ENTRIES // ======================================== // Handle pagination link clicks $(document).on('click', '.mxchat-page-link', function(e) { e.preventDefault(); var $link = $(this); var page = $link.data('page'); var $paginationWrapper = $('#mxchat-kb-pagination'); var $tbody = $('#mxchat-entries-tbody'); if (!page || $link.hasClass('loading')) { return; } // Show loading state $link.addClass('loading'); $tbody.css('opacity', '0.5'); // Add loading indicator to pagination var $loadingIndicator = $(''); $paginationWrapper.find('.mxchat-ajax-pagination').append($loadingIndicator); // Get search and filter values from pagination wrapper var searchQuery = $paginationWrapper.data('search') || ''; var contentType = $paginationWrapper.data('content-type') || ''; $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_paginate_entries', nonce: mxchatAdmin.entries_nonce, bot_id: mxchatAdmin.bot_id || 'default', page: page, search: searchQuery, content_type: contentType }, success: function(response) { $link.removeClass('loading'); $tbody.css('opacity', '1'); $loadingIndicator.remove(); if (response.success && response.data) { // Update the table body with new HTML $tbody.html(response.data.html); // Update the pagination - find the inner container if (response.data.pagination_html) { // Replace the inner pagination div content $paginationWrapper.html(response.data.pagination_html); } // Update data attributes on wrapper (preserve search/filter for next pagination) $paginationWrapper.attr('data-current-page', response.data.page); if (response.data.total_pages) { $paginationWrapper.attr('data-total-pages', response.data.total_pages); } // Preserve search and content_type on the wrapper from the inner pagination div var $innerPagination = $paginationWrapper.find('.mxchat-ajax-pagination'); if ($innerPagination.length) { $paginationWrapper.attr('data-search', $innerPagination.data('search') || ''); $paginationWrapper.attr('data-content-type', $innerPagination.data('content-type') || ''); } // Scroll to top of the table smoothly $('html, body').animate({ scrollTop: $('#knowledge-base').offset().top - 50 }, 300); // Update URL hash to stay on knowledge-base tab if (window.history && window.history.replaceState) { window.history.replaceState(null, '', window.location.pathname + window.location.search + '#knowledge-base'); } // Show success feedback showNotification('success', 'Page ' + response.data.page + ' loaded'); } else { showNotification('error', 'Failed to load page: ' + (response.data?.message || 'Unknown error')); } }, error: function(xhr, status, error) { $link.removeClass('loading'); $tbody.css('opacity', '1'); $loadingIndicator.remove(); showNotification('error', 'Network error while loading page'); } }); }); // ======================================== // PINECONE REFRESH ENTRIES BUTTON // ======================================== $('#mxchat-refresh-pinecone-entries').on('click', function() { var $button = $(this); var $icon = $button.find('.dashicons'); var $tbody = $('#mxchat-entries-tbody'); var $paginationWrapper = $('#mxchat-kb-pagination'); // Show loading state $button.prop('disabled', true); $icon.addClass('spin'); $tbody.css('opacity', '0.5'); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_refresh_pinecone_entries', nonce: mxchatAdmin.entries_nonce, bot_id: mxchatAdmin.bot_id || 'default', page: 1 }, success: function(response) { $button.prop('disabled', false); $icon.removeClass('spin'); $tbody.css('opacity', '1'); if (response.success && response.data) { // Update the table body with new HTML $tbody.html(response.data.html); // Update the pagination (same pattern as refreshKnowledgeBaseTable) if ($paginationWrapper.length) { if (response.data.pagination_html) { $paginationWrapper.html(response.data.pagination_html); $paginationWrapper.attr('style', 'padding: 16px; border-top: 1px solid var(--mxch-card-border); text-align: center;'); } else { $paginationWrapper.html(''); $paginationWrapper.attr('style', ''); } } // Update the count display if (response.data.total_count !== undefined) { updateEntryCount(response.data.total_count); } // Show success feedback showNotification('success', 'Entries refreshed successfully!'); } else { showNotification('error', 'Failed to refresh entries: ' + (response.data?.message || 'Unknown error')); } }, error: function(xhr, status, error) { $button.prop('disabled', false); $icon.removeClass('spin'); $tbody.css('opacity', '1'); showNotification('error', 'Network error while refreshing entries'); } }); }); // ======================================== // ACCORDION FUNCTIONALITY // ======================================== // Handle expand/collapse toggle $(document).on('click', '.mxchat-expand-toggle', function(e) { e.preventDefault(); e.stopPropagation(); const $button = $(this); const $wrapper = $button.closest('.mxchat-accordion-wrapper'); const $preview = $wrapper.find('.mxchat-content-preview'); const $fullContent = $wrapper.find('.mxchat-content-full'); // Toggle expanded state if ($fullContent.is(':visible')) { // Collapse $fullContent.slideUp(300); $button.removeClass('expanded'); } else { // Expand $fullContent.slideDown(300); $button.addClass('expanded'); } }); // Click anywhere on preview to toggle (expand or collapse) $(document).on('click', '.mxchat-content-preview', function(e) { // Only trigger if not clicking the button directly if (!$(e.target).closest('.mxchat-expand-toggle').length) { const $preview = $(this); const $wrapper = $preview.closest('.mxchat-accordion-wrapper'); const $button = $preview.find('.mxchat-expand-toggle'); // Only trigger if there's a button (meaning content is long enough to expand) if ($button.length) { $button.trigger('click'); } } }); // ======================================== // CHUNK GROUP TOGGLE FUNCTIONALITY // ======================================== // Handle chunk group expand/collapse toggle $(document).on('click', '.mxchat-chunk-toggle', function(e) { e.preventDefault(); e.stopPropagation(); const $button = $(this); const groupId = $button.data('group-id'); const $chunkRows = $('.mxchat-chunk-row.' + groupId); // Toggle expanded state if ($button.hasClass('expanded')) { // Collapse $chunkRows.slideUp(300); $button.removeClass('expanded'); } else { // Expand $chunkRows.slideDown(300); $button.addClass('expanded'); } }); // Handle delete button for chunk groups $(document).on('click', '.delete-button-group', function(e) { e.preventDefault(); e.stopPropagation(); const $button = $(this); const sourceUrl = $button.data('source-url'); const chunkCount = $button.data('chunk-count'); const dataSource = $button.data('data-source'); const botId = $button.data('bot-id'); const nonce = $button.data('nonce'); // Confirm deletion if (!confirm('Are you sure you want to delete all ' + chunkCount + ' chunks for this URL? This action cannot be undone.')) { return; } // Show loading state $button.prop('disabled', true); $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update spin'); // Make AJAX request to delete all chunks for this URL $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_delete_chunks_by_url', source_url: sourceUrl, data_source: dataSource, bot_id: botId, nonce: nonce }, success: function(response) { if (response.success) { // Remove the group header row and all chunk rows const $headerRow = $button.closest('tr'); const groupId = $headerRow.data('group-id'); $('.mxchat-chunk-row.' + groupId).fadeOut(300, function() { $(this).remove(); }); $headerRow.fadeOut(300, function() { $(this).remove(); }); } else { var errorMsg = response.data || 'Unknown error'; if (typeof response.data === 'object' && response.data.message) { errorMsg = response.data.message; } alert('Error deleting chunks: ' + errorMsg); $button.prop('disabled', false); $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash'); } }, error: function(xhr, status, error) { console.error('AJAX error:', xhr.responseText); alert('Error deleting chunks: ' + (error || 'Server error')); $button.prop('disabled', false); $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash'); } }); }); // ======================================== // REAL-TIME KNOWLEDGE ENTRIES UPDATE // ======================================== let lastEntryId = 0; let entriesPollingInterval = null; let isEntriesPolling = false; // Initialize: get the highest ID from the current table function initializeLastEntryId() { const $tbody = $('#mxchat-entries-tbody'); if ($tbody.length === 0) return; // Get the highest ID from the table $tbody.find('tr').each(function() { const idText = $(this).find('td:first').text().trim(); const id = parseInt(idText); if (!isNaN(id) && id > lastEntryId) { lastEntryId = id; } }); } // Poll for new entries during processing function startEntriesPolling() { if (entriesPollingInterval) return; // Already polling isEntriesPolling = true; // Fetch immediately, then start interval fetchNewEntries(); entriesPollingInterval = setInterval(function() { fetchNewEntries(); }, 3000); // Poll every 3 seconds } function stopEntriesPolling() { if (entriesPollingInterval) { clearInterval(entriesPollingInterval); entriesPollingInterval = null; } isEntriesPolling = false; } // Track Pinecone count for change detection var lastPineconeCount = 0; var pineconeRefreshPending = false; // Fetch new entries from server function fetchNewEntries() { if (!mxchatAdmin.entries_nonce) { return; } $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_get_recent_entries', nonce: mxchatAdmin.entries_nonce, last_id: lastEntryId, bot_id: mxchatAdmin.bot_id || 'default', limit: 20 }, success: function(response) { if (response.success && response.data) { // Update count if (response.data.total_count !== undefined) { updateEntryCount(response.data.total_count); } // Handle Pinecone data source differently if (response.data.data_source === 'pinecone') { var newCount = response.data.total_count || 0; // If count changed and we haven't scheduled a refresh yet if (newCount !== lastPineconeCount && !pineconeRefreshPending) { lastPineconeCount = newCount; // Schedule a table refresh after processing completes // Show a "refresh to see entries" message var $tbody = $('#mxchat-entries-tbody'); var $refreshNotice = $tbody.find('.mxchat-pinecone-refresh-notice'); if ($refreshNotice.length === 0 && newCount > 0) { var noticeHtml = '' + '' + '' + '' + newCount + ' entries in Pinecone. ' + 'Refresh to see new entries' + ''; $tbody.prepend(noticeHtml); // Handle refresh link click $tbody.find('.mxchat-refresh-table-link').on('click', function(e) { e.preventDefault(); location.reload(); }); } else if ($refreshNotice.length > 0) { // Update the count in existing notice $refreshNotice.find('strong').text(newCount + ' entries in Pinecone.'); } } } else { // WordPress DB - Track new entries and show refresh notice // Don't add rows individually during processing as they need to be grouped by source_url if (response.data.entries && response.data.entries.length > 0) { // Update last ID to track progress if (response.data.max_id > lastEntryId) { lastEntryId = response.data.max_id; } // Show/update refresh notice (similar to Pinecone handling) var $tbody = $('#mxchat-entries-tbody'); var $refreshNotice = $tbody.find('.mxchat-wordpress-refresh-notice'); var newCount = response.data.total_count || 0; if ($refreshNotice.length === 0 && newCount > 0) { var noticeHtml = '' + '' + '' + 'Processing... ' + newCount + ' entries' + ''; $tbody.prepend(noticeHtml); } else if ($refreshNotice.length > 0) { // Update the count in existing notice $refreshNotice.find('.mxchat-processing-count').text(newCount); } } else { // Update last ID even if no new entries if (response.data.max_id > lastEntryId) { lastEntryId = response.data.max_id; } } } } }, error: function(xhr, status, error) { console.error('MxChat: Error fetching new entries:', error, xhr.responseText); } }); } // Update the entry count display function updateEntryCount(count) { // Update main table count const $countSpan = $('#mxchat-entry-count'); if ($countSpan.length) { $countSpan.text('(' + count + ')'); // Flash animation to indicate update $countSpan.addClass('mxchat-count-updated'); setTimeout(function() { $countSpan.removeClass('mxchat-count-updated'); }, 1000); } // Update sidebar badge count const $sidebarCount = $('#mxchat-sidebar-count'); if ($sidebarCount.length) { $sidebarCount.text(count); // Flash animation for sidebar too $sidebarCount.addClass('mxchat-count-updated'); setTimeout(function() { $sidebarCount.removeClass('mxchat-count-updated'); }, 1000); } } // Refresh the knowledge base table via AJAX pagination // This ensures entries are properly grouped by source_url function refreshKnowledgeBaseTable() { var $paginationWrapper = $('#mxchat-kb-pagination'); var $tbody = $('#mxchat-entries-tbody'); if ($tbody.length === 0) { return; } // Remove any processing notice $tbody.find('.mxchat-wordpress-refresh-notice, .mxchat-pinecone-refresh-notice').remove(); // Show loading state $tbody.css('opacity', '0.5'); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_paginate_entries', nonce: mxchatAdmin.entries_nonce, bot_id: mxchatAdmin.bot_id || 'default', page: 1 // Always go to first page to see newest entries }, success: function(response) { $tbody.css('opacity', '1'); if (response.success && response.data) { // Update the table body with properly grouped HTML $tbody.html(response.data.html); // Update the pagination if ($paginationWrapper.length) { if (response.data.pagination_html) { // Add pagination content and styling $paginationWrapper.html(response.data.pagination_html); $paginationWrapper.attr('style', 'padding: 16px; border-top: 1px solid var(--mxch-card-border); text-align: center;'); } else { // No pagination needed - clear and hide $paginationWrapper.html(''); $paginationWrapper.attr('style', ''); } } // Update count display if (response.data.total_count !== undefined) { updateEntryCount(response.data.total_count); } } }, error: function(xhr, status, error) { $tbody.css('opacity', '1'); console.error('MxChat: Error refreshing table:', error); } }); } // Expose refreshKnowledgeBaseTable for external access (content-selector.js) window.refreshKnowledgeBaseTable = refreshKnowledgeBaseTable; // Add new entries to the table (kept for backwards compatibility but not used during processing) function addNewEntriesToTable(entries) { const $tbody = $('#mxchat-entries-tbody'); if ($tbody.length === 0) return; // Remove "no entries" message if present const $noEntries = $tbody.find('td[colspan="4"]').closest('tr'); if ($noEntries.length) { $noEntries.remove(); } // Add entries in reverse order (oldest first, so newest ends up at top) entries.reverse().forEach(function(entry) { // Check if entry already exists if ($tbody.find('tr[data-entry-id="' + entry.id + '"]').length > 0) { return; } const sourceHtml = entry.has_link ? ' View' : 'Manual'; const deleteUrl = mxchatAdmin.admin_url + 'admin-post.php?action=mxchat_delete_prompt&id=' + entry.id + '&bot_id=' + encodeURIComponent(mxchatAdmin.bot_id || 'default') + '&_wpnonce=' + entry.delete_nonce; // Check if content needs expand button (content longer than preview) const needsExpand = entry.content_length > entry.preview_length; // Build accordion-style content cell (matching initial page load structure) let contentHtml = '
' + '
' + '' + entry.preview + ''; if (needsExpand) { contentHtml += ''; } contentHtml += '
' + '' + '
'; const $row = $('' + '' + entry.id + '' + '' + contentHtml + '' + '' + sourceHtml + '' + '' + '' + '' + '' + '' + '' + ''); // Add highlight class and prepend to tbody $row.addClass('mxchat-new-entry'); $tbody.prepend($row); $row.slideDown(300); // Remove highlight after animation setTimeout(function() { $row.removeClass('mxchat-new-entry'); }, 2000); }); } // Initialize entry ID tracking initializeLastEntryId(); // Check on page load if there's already active processing (e.g., page was refreshed during processing) if ($('.mxchat-status-card').length > 0) { // Check if processing is active via AJAX $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_get_status_updates', nonce: mxchatAdmin.status_nonce }, success: function(response) { if (response.is_processing) { startEntriesPolling(); } } }); } // Expose functions for external access window.mxchatEntriesPolling = { start: startEntriesPolling, stop: stopEntriesPolling, fetch: fetchNewEntries }; // ======================================== // SITEMAP DETECTION FUNCTIONALITY // ======================================== let sitemapDetectionInitialized = false; /** * Initialize sitemap detection when Sitemap Import is clicked */ function initSitemapDetection() { if (sitemapDetectionInitialized) return; const loadingEl = document.getElementById('mxchat-sitemaps-loading'); const detectedEl = document.getElementById('mxchat-detected-sitemaps'); const noSitemapsEl = document.getElementById('mxchat-no-sitemaps'); const listEl = document.getElementById('mxchat-sitemaps-list'); const refreshBtn = document.getElementById('mxchat-refresh-sitemaps'); const nonceEl = document.getElementById('mxchat-detect-sitemaps-nonce'); if (!loadingEl || !nonceEl) return; sitemapDetectionInitialized = true; function detectSitemaps() { // Show loading loadingEl.style.display = 'block'; if (detectedEl) detectedEl.style.display = 'none'; if (noSitemapsEl) noSitemapsEl.style.display = 'none'; // Disable refresh button if (refreshBtn) { refreshBtn.disabled = true; var refreshIcon = refreshBtn.querySelector('.dashicons'); if (refreshIcon) refreshIcon.classList.add('spin'); } $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mxchat_detect_sitemaps', nonce: nonceEl.value }, timeout: 60000, // 60 second timeout for slow servers success: function(data) { loadingEl.style.display = 'none'; // Re-enable refresh button if (refreshBtn) { refreshBtn.disabled = false; var refreshIcon = refreshBtn.querySelector('.dashicons'); if (refreshIcon) refreshIcon.classList.remove('spin'); } if (data.success && data.data && data.data.sitemaps && data.data.sitemaps.length > 0) { renderSitemaps(data.data.sitemaps); if (detectedEl) detectedEl.style.display = 'block'; } else { if (noSitemapsEl) { noSitemapsEl.style.display = 'block'; $(noSitemapsEl).data('was-shown', true); } } }, error: function(xhr, status, error) { loadingEl.style.display = 'none'; if (noSitemapsEl) { noSitemapsEl.style.display = 'block'; $(noSitemapsEl).data('was-shown', true); } if (refreshBtn) { refreshBtn.disabled = false; var refreshIcon = refreshBtn.querySelector('.dashicons'); if (refreshIcon) refreshIcon.classList.remove('spin'); } } }); } function renderSitemaps(sitemaps) { if (!listEl) return; var html = ''; var botIdEl = document.getElementById('mxchat-sitemap-bot-id'); var botId = botIdEl ? botIdEl.value : ''; sitemaps.forEach(function(sitemap) { if (sitemap.type === 'index' && sitemap.sub_sitemaps && sitemap.sub_sitemaps.length > 0) { // Render sitemap index with sub-sitemaps html += '
'; html += '
'; html += '
'; html += ''; html += ''; html += '
'; html += 'Sitemap Index'; html += '' + sitemap.source + ''; html += '
'; html += '
'; html += ''; html += sitemap.sub_sitemaps.length + ' sitemaps'; html += ''; html += '
'; html += '
'; sitemap.sub_sitemaps.forEach(function(sub) { html += renderSitemapRow(sub, botId, true); }); html += '
'; html += '
'; } else if (sitemap.type !== 'index') { // Render standalone sitemap html += renderSitemapRow(sitemap, botId, false); } }); listEl.innerHTML = html; // Add click handlers for group toggles $(listEl).find('.mxchat-sitemap-group-header').on('click', function() { var $group = $(this).parent(); var $subList = $group.find('.mxchat-sitemap-sub-list'); var $arrow = $(this).find('.dashicons-arrow-right-alt2'); $group.toggleClass('expanded'); if ($group.hasClass('expanded')) { $subList.slideDown(200); $arrow.css('transform', 'rotate(90deg)'); } else { $subList.slideUp(200); $arrow.css('transform', 'rotate(0deg)'); } }); // Add click handlers for process buttons $(listEl).find('.mxchat-process-sitemap-btn').on('click', function() { var url = $(this).data('url'); var type = $(this).data('sitemap-type'); processSitemap(url, type, this); }); } function renderSitemapRow(sitemap, botId, isSubItem) { var typeLabels = { 'content': 'Content', 'taxonomy': 'Taxonomy', 'author': 'Authors' }; var typeLabel = typeLabels[sitemap.type] || sitemap.type; var displayName = sitemap.name || sitemap.url.split('/').pop(); var urlCount = sitemap.url_count || 0; var paddingLeft = isSubItem ? '40px' : '16px'; var html = '
'; html += '
'; html += ''; html += '
'; html += '
'; html += displayName; html += '
'; html += '
'; html += '' + typeLabel + ''; if (urlCount > 0) { html += urlCount + ' URLs'; } html += '
'; html += '
'; html += '
'; html += ''; html += '
'; return html; } function processSitemap(url, type, buttonEl) { var $button = $(buttonEl); var originalHtml = $button.html(); // Update button to show loading $button.prop('disabled', true); $button.html(' Processing...'); // Fill in the sitemap URL form and submit var $form = $('#mxchat-url-form'); var $urlInput = $('#sitemap_url'); var $importType = $('#import_type'); if ($urlInput.length) { $urlInput.val(url); } if ($importType.length) { $importType.val('sitemap'); } // Add a hidden submit field if not present (required by the PHP handler) if ($form.find('input[name="submit_sitemap"]').length === 0) { $form.append(''); } // Submit the form $form.submit(); } // Refresh button handler if (refreshBtn) { $(refreshBtn).on('click', detectSitemaps); } // Start detection detectSitemaps(); } // Expose initSitemapDetection globally so it can be called from the import options handler window.mxchatInitSitemapDetection = initSitemapDetection; // ======================================== // ADMIN NOTICE DISMISS FUNCTIONALITY // ======================================== // Initialize dismissible notices - add dismiss button if missing function initDismissibleNotices() { $('.notice.is-dismissible').each(function() { var $notice = $(this); // Skip if already has a dismiss button if ($notice.find('.notice-dismiss').length > 0) { return; } // Add dismiss button var $dismissButton = $(''); $notice.append($dismissButton); }); } // Initialize on page load initDismissibleNotices(); // Use event delegation for dismiss button clicks - works for existing and dynamically added notices $(document).on('click', '.notice.is-dismissible .notice-dismiss', function(e) { e.preventDefault(); e.stopPropagation(); var $notice = $(this).closest('.notice'); $notice.fadeTo(100, 0, function() { $notice.slideUp(100, function() { $notice.remove(); }); }); }); // Re-initialize when new notices are added dynamically (e.g., via AJAX) $(document).on('DOMNodeInserted', function(e) { if ($(e.target).hasClass('notice') && $(e.target).hasClass('is-dismissible')) { setTimeout(initDismissibleNotices, 10); } }); // ======================================== // CUSTOM META-KEY DISCOVERY PICKER (plan-mxchat-20260709-fe8e4e) // Mirror the ACF picker's discover-and-click UX for non-ACF post meta: scan // published content for meta keys, render clickable chips, and append chosen // keys into the existing whitelist textarea (its autosave then persists them). // ======================================== (function() { var $scanBtn = $('#mxchat-scan-meta-btn'); if (!$scanBtn.length) { return; } var $results = $('#mxchat-meta-scan-results'); var $textarea = $('#mxchat_custom_meta_whitelist'); var $internal = $('#mxchat-scan-internal'); var $label = $scanBtn.find('.mxchat-scan-label'); var defaultLabel = $label.text(); function currentKeys() { return ($textarea.val() || '') .split('\n') .map(function(k) { return k.trim(); }) .filter(function(k) { return k.length; }); } function addKey(key) { var keys = currentKeys(); if (keys.indexOf(key) !== -1) { return false; } keys.push(key); $textarea.val(keys.join('\n')); // Fire the existing autosave (bound on 'change' in mxchat-admin.js). $textarea.trigger('change'); return true; } function escapeHtml(s) { return String(s).replace(/[&<>"']/g, function(c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); } function humanize(key) { var h = key.replace(/^_+/, '').replace(/[_-]+/g, ' ').trim(); return h.length ? h.charAt(0).toUpperCase() + h.slice(1) : key; } function renderChips(keys) { if (!keys || !keys.length) { $results.html('

' + 'No custom meta keys found on your published content. Try enabling internal keys, or add keys manually below.' + '

'); return; } var existing = currentKeys(); var html = '
'; keys.forEach(function(item) { var already = existing.indexOf(item.key) !== -1; html += ''; }); html += '
'; $results.html(html); } $scanBtn.on('click', function() { var nonce = $scanBtn.data('nonce') || (window.mxchatAdmin && mxchatAdmin.settings_nonce) || ''; var ajaxUrl = (window.mxchatAdmin && (mxchatAdmin.ajax_url || mxchatAdmin.ajaxurl)) || window.ajaxurl || '/wp-admin/admin-ajax.php'; $scanBtn.prop('disabled', true); $label.text('Scanning…'); $results.html('

Scanning your content for meta keys…

'); $.post(ajaxUrl, { action: 'mxchat_scan_custom_meta_keys', _ajax_nonce: nonce, include_internal: $internal.is(':checked') ? '1' : '0' }).done(function(resp) { if (resp && resp.success && resp.data && resp.data.keys) { renderChips(resp.data.keys); } else { var msg = (resp && resp.data && resp.data.message) ? resp.data.message : 'Scan failed. Please try again.'; $results.html('

' + escapeHtml(msg) + '

'); } }).fail(function() { $results.html('

Scan request failed. Please try again.

'); }).always(function() { $scanBtn.prop('disabled', false); $label.text(defaultLabel); }); }); // Chip click -> add the raw key to the whitelist textarea (dedup) + autosave. $results.on('click', '.mxchat-meta-chip', function() { var $chip = $(this); if ($chip.hasClass('is-added')) { return; } var key = String($chip.data('key')); if (addKey(key)) { $chip.addClass('is-added').attr('aria-pressed', 'true'); $chip.find('.mxchat-meta-chip-tick').text('βœ“ Added'); } }); })(); // ======================================== // VIDEO CARDS β€” threshold follows the master switch (plan f52492) // ======================================== // Presentation only. The server gate does NOT consult the threshold when // the switch is off, so hiding the field never changes behavior β€” it just // stops the page offering a number that is currently inert. Mirrors the // YouTube import form's own show/hide idiom on this same page. Autosave is // untouched: both fields keep their own change handler in mxchat-admin.js. $('#mxchat_video_embed_enabled').on('change', function() { $('#mxchat-video-embed-threshold-field').toggle($(this).is(':checked')); }); });