PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.5.2
MxChat – AI Chatbot & Content Generation for WordPress v2.5.2
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | js/knowledge-processing.js +122 -1367 3.2.92.5.2 View file →
@@ -1,86 +1,13 @@
1 1 jQuery(document).ready(function($) {
2 2 // ========================================
3 - // UNIQUE URL GENERATOR FOR DIRECT CONTENT
4 - // ========================================
5 -
6 - // Show/hide generate button based on checkbox
7 - $('#mxchat-unique-url-toggle').on('change', function() {
8 - const $button = $('#mxchat-generate-unique-url');
9 - const $urlInput = $('#article_url');
10 -
11 - if ($(this).is(':checked')) {
12 - $button.show();
13 - // If URL field is empty and checkbox is enabled, generate immediately
14 - if (!$urlInput.val()) {
15 - generateUniqueUrl();
16 - }
17 - } else {
18 - $button.hide();
19 - }
20 - });
21 -
22 - // Generate unique URL when button is clicked
23 - $('#mxchat-generate-unique-url').on('click', function() {
24 - generateUniqueUrl();
25 - });
26 -
27 - // Function to generate unique URL
28 - function generateUniqueUrl() {
29 - const $urlInput = $('#article_url');
30 - let baseUrl = $urlInput.val().trim();
31 -
32 - // If no URL provided, use a default
33 - if (!baseUrl) {
34 - baseUrl = window.location.origin;
35 - }
36 -
37 - // Split URL into base and hash fragment
38 - let hashFragment = '';
39 - if (baseUrl.includes('#')) {
40 - const parts = baseUrl.split('#');
41 - baseUrl = parts[0];
42 - hashFragment = '#' + parts[1];
43 - }
44 -
45 - // Remove any existing ref parameter (matches ref=anything up to & or end of string)
46 - baseUrl = baseUrl.replace(/[?&]ref=[^&]+(&|$)/, function(match, ending) {
47 - // If it ends with &, keep it; otherwise remove the whole thing
48 - return ending === '&' ? '&' : '';
49 - });
50 -
51 - // Clean up any trailing ? or & from URL
52 - baseUrl = baseUrl.replace(/[?&]$/, '');
53 -
54 - // Generate unique reference (timestamp only for cleaner URLs)
55 - const timestamp = Date.now();
56 - const uniqueRef = timestamp;
57 -
58 - // Add the unique reference as a query parameter (before hash)
59 - const separator = baseUrl.includes('?') ? '&' : '?';
60 - const uniqueUrl = baseUrl + separator + 'ref=' + uniqueRef + hashFragment;
61 -
62 - // Update the input field
63 - $urlInput.val(uniqueUrl);
64 -
65 - // Visual feedback
66 - $urlInput.css('background-color', '#e7f7e7');
67 - setTimeout(function() {
68 - $urlInput.css('background-color', '');
69 - }, 1000);
70 - }
71 -
72 - // ========================================
73 3 // QUEUE PROCESSING SYSTEM
74 4 // ========================================
75 -
5 +
76 6 let isProcessingQueue = false;
77 7 let currentQueueId = null;
78 8 let currentQueueType = null;
79 -
80 - // Process 5 items at a time
81 - const BATCH_SIZE = 5;
82 -
9 +
83 10 // Check if we should start queue processing on page load
84 11 checkForActiveQueues();
85 12
86 13 // Form submission handler - triggers queue processing
@@ -85,14 +12,14 @@
85 12
86 13 // Form submission handler - triggers queue processing
87 14 $('#mxchat-url-form').on('submit', function(e) {
88 15 //console.log('MxChat: Form submitted, queue will be created');
89 -
16 +
90 17 // Don't prevent default - let form submit normally
91 18 // But schedule a check after redirect
92 19 localStorage.setItem('mxchat_check_queue_after_submit', Date.now().toString());
93 20 });
94 -
21 +
95 22 // Check if we just submitted a form and need to start processing
96 23 const justSubmitted = localStorage.getItem('mxchat_check_queue_after_submit');
97 24 if (justSubmitted) {
98 25 const submitTime = parseInt(justSubmitted);
@@ -174,109 +101,71 @@
174 101 if (isProcessingQueue) {
175 102 //console.log('MxChat: Already processing a queue, skipping');
176 103 return;
177 104 }
178 -
105 +
179 106 isProcessingQueue = true;
180 107 currentQueueId = queueId;
181 108 currentQueueType = queueType;
182 -
109 +
183 110 //console.log('MxChat: Starting queue processing:', queueId, queueType);
184 -
111 +
185 112 // Remove any "waiting" messages
186 113 $('.mxchat-processing-message').remove();
187 -
114 +
188 115 // Create or update status card
189 116 createOrUpdateStatusCard(queueType);
190 -
191 - // Start real-time entries polling if function exists
192 - if (typeof startEntriesPolling === 'function') {
193 - startEntriesPolling();
194 - }
195 -
196 - // Start the batch processing loop
197 - processNextBatch();
117 +
118 + // Start the processing loop
119 + processNextQueueItem();
198 120 }
199 -
121 +
200 122 /**
201 - * Process the next batch of items (5 at a time)
123 + * Process the next item in the queue
202 124 */
203 - function processNextBatch() {
125 + function processNextQueueItem() {
204 126 if (!isProcessingQueue) {
205 127 //console.log('MxChat: Processing stopped');
206 128 return;
207 129 }
208 -
209 - // Fetch the next batch of items
210 - const fetchPromises = [];
211 -
212 - for (let i = 0; i < BATCH_SIZE; i++) {
213 - const promise = $.ajax({
214 - url: ajaxurl,
215 - type: 'POST',
216 - data: {
217 - action: 'mxchat_get_next_queue_item',
218 - nonce: mxchatAdmin.queue_nonce,
219 - queue_id: currentQueueId
130 +
131 + // Get next item from queue
132 + $.ajax({
133 + url: ajaxurl,
134 + type: 'POST',
135 + data: {
136 + action: 'mxchat_get_next_queue_item',
137 + nonce: mxchatAdmin.queue_nonce,
138 + queue_id: currentQueueId
139 + },
140 + success: function(response) {
141 + if (!response.success) {
142 + console.error('MxChat: Error getting next queue item:', response.data);
143 + // Check if queue is actually complete despite error
144 + verifyQueueCompletion();
145 + return;
220 146 }
221 - });
222 - fetchPromises.push(promise);
223 - }
224 -
225 - // Wait for all fetch requests to complete
226 - Promise.all(fetchPromises).then(function(responses) {
227 - // Filter out completed/error responses and extract items
228 - const items = [];
229 - let queueComplete = false;
230 -
231 - for (let response of responses) {
232 - if (response.success && response.data && !response.data.complete) {
233 - items.push(response.data.item);
234 - } else if (response.data && response.data.complete) {
235 - queueComplete = true;
147 +
148 + if (response.data.complete) {
149 + // Queue is complete!
150 + //console.log('MxChat: Queue processing complete!');
151 + handleQueueComplete();
152 + return;
236 153 }
237 - }
238 -
239 - // If no items to process, queue is done
240 - if (items.length === 0) {
241 - if (queueComplete) {
242 - handleQueueComplete();
243 - } else {
154 +
155 + // Process this item
156 + const item = response.data.item;
157 + //console.log('MxChat: Processing item:', item.type, item.id);
158 +
159 + processQueueItem(item);
160 + },
161 + error: function(xhr, status, error) {
162 + console.error('MxChat: AJAX error getting next item:', error);
163 + // Network error - verify queue status before retrying
164 + setTimeout(function() {
244 165 verifyQueueCompletion();
245 - }
246 - return;
166 + }, 2000);
247 167 }
248 -
249 - // Process all items in this batch simultaneously
250 - const processPromises = items.map(item => processQueueItem(item));
251 -
252 - // Wait for all items to finish processing
253 - Promise.all(processPromises).then(function() {
254 - // Update progress after batch completes
255 - updateQueueProgress();
256 -
257 - // If we got fewer items than batch size, queue might be done
258 - if (items.length < BATCH_SIZE || queueComplete) {
259 - verifyQueueCompletion();
260 - } else {
261 - // Process next batch immediately
262 - processNextBatch();
263 - }
264 - }).catch(function(error) {
265 - console.error('MxChat: Error processing batch:', error);
266 - // Continue anyway
267 - updateQueueProgress();
268 - setTimeout(function() {
269 - processNextBatch();
270 - }, 1000);
271 - });
272 -
273 - }).catch(function(error) {
274 - console.error('MxChat: Error fetching batch:', error);
275 - // Verify queue status before retrying
276 - setTimeout(function() {
277 - verifyQueueCompletion();
278 - }, 2000);
279 168 });
280 169 }
281 170
282 171 /**
@@ -304,9 +193,9 @@
304 193 handleQueueComplete();
305 194 } else {
306 195 // Still has items, try to continue
307 196 //console.log('MxChat: Queue still has pending items, continuing...');
308 - processNextBatch();
197 + processNextQueueItem();
309 198 }
310 199 } else {
311 200 // Can't verify, assume complete to prevent infinite loop
312 201 //console.log('MxChat: Could not verify queue status, assuming complete');
@@ -322,12 +211,12 @@
322 211 }
323 212
324 213 /**
325 214 * Process a single queue item
326 - * Returns a Promise that resolves when processing is complete
215 + * Never stops the queue - always continues regardless of success/failure
327 216 */
328 217 function processQueueItem(item) {
329 - return $.ajax({
218 + $.ajax({
330 219 url: ajaxurl,
331 220 type: 'POST',
332 221 data: {
333 222 action: 'mxchat_process_queue_item',
@@ -335,24 +224,48 @@
335 224 item_id: item.id,
336 225 item_type: item.type,
337 226 item_data: item.data,
338 227 bot_id: item.bot_id
228 + },
229 + success: function(response) {
230 + if (response.success) {
231 + // Item processed successfully
232 + //console.log('MxChat: Item processed successfully:', item.id);
233 +
234 + // Update progress
235 + updateQueueProgress();
236 +
237 + // Small delay to prevent server overload, then process next
238 + setTimeout(function() {
239 + processNextQueueItem();
240 + }, 500); // 500ms delay between items
241 +
242 + } else {
243 + // Item failed but we KEEP GOING
244 + console.warn('MxChat: Item processing failed (will continue):', item.type, item.id);
245 + console.warn('MxChat: Error details:', response.data);
246 +
247 + // Update progress to reflect the attempt
248 + updateQueueProgress();
249 +
250 + // Continue to next item regardless
251 + setTimeout(function() {
252 + processNextQueueItem();
253 + }, 500);
254 + }
255 + },
256 + error: function(xhr, status, error) {
257 + // Network error - log it but KEEP GOING
258 + console.error('MxChat: AJAX/Network error processing item:', item.id, error);
259 +
260 + // Update progress
261 + updateQueueProgress();
262 +
263 + // Wait a bit longer for network errors, then continue
264 + setTimeout(function() {
265 + processNextQueueItem();
266 + }, 1000);
339 267 }
340 - }).then(function(response) {
341 - if (response.success) {
342 - // Item processed successfully
343 - //console.log('MxChat: Item processed successfully:', item.id);
344 - return true;
345 - } else {
346 - // Item failed but we KEEP GOING
347 - console.warn('MxChat: Item processing failed (will continue):', item.type, item.id);
348 - console.warn('MxChat: Error details:', response.data);
349 - return false;
350 - }
351 - }).catch(function(xhr, status, error) {
352 - // Network error - log it but KEEP GOING
353 - console.error('MxChat: AJAX/Network error processing item:', item.id, error);
354 - return false;
355 268 });
356 269 }
357 270
358 271 /**
@@ -387,17 +300,11 @@
387 300 * NO AUTO-REFRESH - Show completed card with errors until dismissed
388 301 */
389 302 function handleQueueComplete() {
390 303 isProcessingQueue = false;
391 -
392 - // Stop real-time entries polling if function exists
393 - if (typeof stopEntriesPolling === 'function') {
394 - stopEntriesPolling();
395 - }
396 -
397 - // Refresh the knowledge base table with properly grouped entries
398 - refreshKnowledgeBaseTable();
399 -
304 +
305 + //console.log('MxChat: Queue processing completed - showing final results');
306 +
400 307 // Get final status with error details
401 308 $.ajax({
402 309 url: ajaxurl,
403 310 type: 'POST',
@@ -495,18 +402,20 @@
495 402 if (status.failed > 0 && status.failed_items && status.failed_items.length > 0) {
496 403 detailsHtml += '<div class="mxchat-error-summary" style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-top: 15px;">';
497 404 detailsHtml += '<h4 style="margin: 0 0 10px 0; color: #856404;">⚠️ Failed Pages</h4>';
498 405
406 + detailsHtml += '<details style="cursor: pointer;">';
407 + detailsHtml += '<summary style="font-weight: bold; color: #856404; padding: 5px 0;">Click to view ' + status.failed_items.length + ' failed pages</summary>';
408 +
499 409 detailsHtml += '<div style="margin-top: 10px; max-height: 400px; overflow-y: auto;">';
500 410 detailsHtml += '<table style="width: 100%; border-collapse: collapse;">';
501 411 detailsHtml += '<thead><tr style="background: #f5f5f5;"><th style="padding: 8px; text-align: left;">Page</th><th style="padding: 8px; text-align: left;">Error</th><th style="padding: 8px; text-align: left;">Attempts</th></tr></thead>';
502 412 detailsHtml += '<tbody>';
503 -
413 +
504 414 status.failed_items.forEach(function(item) {
505 - let data;
506 - try { data = JSON.parse(item.item_data); } catch(e) { data = {}; }
415 + const data = JSON.parse(item.item_data);
507 416 const pageNum = data.page_number || 'Unknown';
508 -
417 +
509 418 detailsHtml += '<tr style="border-bottom: 1px solid #ddd;">';
510 419 detailsHtml += '<td style="padding: 8px;">Page ' + pageNum + '</td>';
511 420 detailsHtml += '<td style="padding: 8px; word-break: break-word;">' + (item.error_message || 'Unknown error') + '</td>';
512 421 detailsHtml += '<td style="padding: 8px;">' + item.attempts + '</td>';
@@ -511,12 +420,13 @@
511 420 detailsHtml += '<td style="padding: 8px; word-break: break-word;">' + (item.error_message || 'Unknown error') + '</td>';
512 421 detailsHtml += '<td style="padding: 8px;">' + item.attempts + '</td>';
513 422 detailsHtml += '</tr>';
514 423 });
515 -
424 +
516 425 detailsHtml += '</tbody></table>';
517 426 detailsHtml += '</div>';
518 -
427 + detailsHtml += '</details>';
428 +
519 429 detailsHtml += '</div>';
520 430 }
521 431
522 432 $card.find('.mxchat-status-details').html(detailsHtml);
@@ -571,20 +481,21 @@
571 481 if (status.failed > 0 && status.failed_items && status.failed_items.length > 0) {
572 482 detailsHtml += '<div class="mxchat-error-summary" style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-top: 15px;">';
573 483 detailsHtml += '<h4 style="margin: 0 0 10px 0; color: #856404;">⚠️ Failed URLs</h4>';
574 484
485 + detailsHtml += '<details style="cursor: pointer;">';
486 + detailsHtml += '<summary style="font-weight: bold; color: #856404; padding: 5px 0;">Click to view ' + status.failed_items.length + ' failed URLs</summary>';
487 +
575 488 detailsHtml += '<div style="margin-top: 10px; max-height: 400px; overflow-y: auto;">';
576 489 detailsHtml += '<table style="width: 100%; border-collapse: collapse;">';
577 490 detailsHtml += '<thead><tr style="background: #f5f5f5;"><th style="padding: 8px; text-align: left;">URL</th><th style="padding: 8px; text-align: left;">Error</th><th style="padding: 8px; text-align: left;">Attempts</th></tr></thead>';
578 491 detailsHtml += '<tbody>';
579 -
492 +
580 493 status.failed_items.forEach(function(item) {
581 - let data;
582 - try { data = JSON.parse(item.item_data); } catch(e) { data = {}; }
583 - const url = data.url || data.pdf_url || 'Unknown URL';
584 - const pageInfo = data.page_number ? ' (page ' + data.page_number + ')' : '';
585 - const displayUrl = (url.length > 60 ? url.substring(0, 57) + '...' : url) + pageInfo;
586 -
494 + const data = JSON.parse(item.item_data);
495 + const url = data.url || 'Unknown URL';
496 + const displayUrl = url.length > 60 ? url.substring(0, 57) + '...' : url;
497 +
587 498 detailsHtml += '<tr style="border-bottom: 1px solid #ddd;">';
588 499 detailsHtml += '<td style="padding: 8px;"><a href="' + url + '" target="_blank" style="color: #0073aa; text-decoration: none;">' + displayUrl + '</a></td>';
589 500 detailsHtml += '<td style="padding: 8px; word-break: break-word;">' + (item.error_message || 'Unknown error') + '</td>';
590 501 detailsHtml += '<td style="padding: 8px;">' + item.attempts + '</td>';
@@ -589,15 +500,16 @@
589 500 detailsHtml += '<td style="padding: 8px; word-break: break-word;">' + (item.error_message || 'Unknown error') + '</td>';
590 501 detailsHtml += '<td style="padding: 8px;">' + item.attempts + '</td>';
591 502 detailsHtml += '</tr>';
592 503 });
593 -
504 +
594 505 detailsHtml += '</tbody></table>';
595 506 detailsHtml += '</div>';
596 -
507 + detailsHtml += '</details>';
508 +
597 509 detailsHtml += '</div>';
598 510 }
599 -
511 +
600 512 $card.find('.mxchat-status-details').html(detailsHtml);
601 513 }
602 514
603 515 /**
@@ -1083,20 +995,21 @@
1083 995 success: function(response) {
1084 996 if (response.success) {
1085 997 $row.fadeOut(500, function() {
1086 998 $(this).remove();
1087 - // Update entry count displays (header and sidebar)
1088 - var $countSpan = $('#mxchat-entry-count');
999 +
1000 + var $countSpan = $('.mxchat-record-count');
1089 1001 if ($countSpan.length) {
1090 1002 var currentText = $countSpan.text();
1091 - var match = currentText.match(/\((\d+)\)/);
1092 - if (match) {
1093 - var newCount = Math.max(0, parseInt(match[1]) - 1);
1094 - updateEntryCount(newCount);
1003 + var matches = currentText.match(/\((\d+)/);
1004 + if (matches) {
1005 + var currentCount = parseInt(matches[1]);
1006 + var newCount = Math.max(0, currentCount - 1);
1007 + $countSpan.text($countSpan.text().replace(/\(\d+/, '(' + newCount));
1095 1008 }
1096 1009 }
1097 1010 });
1098 -
1011 +
1099 1012 $('<div class="notice notice-success is-dismissible"><p>Entry deleted successfully from Pinecone.</p></div>')
1100 1013 .insertAfter('.mxchat-hero')
1101 1014 .delay(3000)
1102 1015 .fadeOut();
@@ -1113,1164 +1026,6 @@
1113 1026 $row.removeClass('mxchat-row-deleting');
1114 1027 alert('Network error occurred');
1115 1028 }
1116 1029 });
1117 - });
1118 -
1119 - // ========================================
1120 - // WORDPRESS DATABASE DELETE HANDLER (AJAX)
1121 - // ========================================
1122 -
1123 - $(document).on('click', '.delete-button-wordpress', function(e) {
1124 - e.preventDefault();
1125 -
1126 - if (!confirm('Are you sure you want to delete this entry?')) {
1127 - return;
1128 - }
1129 -
1130 - var $button = $(this);
1131 - var $row = $button.closest('tr');
1132 - var entryId = $button.data('entry-id');
1133 - var nonce = $button.data('nonce');
1134 -
1135 - $button.prop('disabled', true);
1136 - $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update-alt spin');
1137 - $row.addClass('mxchat-row-deleting');
1138 -
1139 - $.ajax({
1140 - url: ajaxurl,
1141 - type: 'POST',
1142 - data: {
1143 - action: 'mxchat_delete_wordpress_prompt',
1144 - nonce: nonce,
1145 - entry_id: entryId
1146 - },
1147 - success: function(response) {
1148 - if (response.success) {
1149 - $row.fadeOut(500, function() {
1150 - $(this).remove();
1151 - // Update entry count displays (header and sidebar)
1152 - var $countSpan = $('#mxchat-entry-count');
1153 - if ($countSpan.length) {
1154 - var currentText = $countSpan.text();
1155 - var match = currentText.match(/\((\d+)\)/);
1156 - if (match) {
1157 - var newCount = Math.max(0, parseInt(match[1]) - 1);
1158 - updateEntryCount(newCount);
1159 - }
1160 - }
1161 - });
1162 -
1163 - $('<div class="notice notice-success is-dismissible"><p>Entry deleted successfully.</p></div>')
1164 - .insertAfter('.mxchat-hero')
1165 - .delay(3000)
1166 - .fadeOut();
1167 - } else {
1168 - $button.prop('disabled', false);
1169 - $button.find('.dashicons').removeClass('dashicons-update-alt spin').addClass('dashicons-trash');
1170 - $row.removeClass('mxchat-row-deleting');
1171 - alert('Error: ' + response.data);
1172 - }
1173 - },
1174 - error: function() {
1175 - $button.prop('disabled', false);
1176 - $button.find('.dashicons').removeClass('dashicons-update-alt spin').addClass('dashicons-trash');
1177 - $row.removeClass('mxchat-row-deleting');
1178 - alert('Network error occurred');
1179 - }
1180 - });
1181 - });
1182 -
1183 - // ========================================
1184 - // BULK SELECTION FOR KNOWLEDGE ENTRIES
1185 - // ========================================
1186 -
1187 - var selectedKnowledgeEntries = new Set();
1188 -
1189 - // Update selection UI
1190 - function updateKnowledgeSelectionUI() {
1191 - var count = selectedKnowledgeEntries.size;
1192 - var $countEl = $('#mxchat-selected-entry-count');
1193 - var $deleteBtn = $('#mxchat-delete-selected-entries');
1194 - var $deleteAllForm = $('#mxchat-delete-all-form');
1195 -
1196 - if (count > 0) {
1197 - // Show Delete Selected button, hide Delete All form
1198 - $deleteAllForm.hide();
1199 - $deleteBtn.show();
1200 - $countEl.text('(' + count + ')');
1201 - } else {
1202 - // Show Delete All form, hide Delete Selected button
1203 - $deleteAllForm.show();
1204 - $deleteBtn.hide();
1205 - $countEl.text('');
1206 - }
1207 -
1208 - // Update select all checkbox state
1209 - var totalItems = $('.mxchat-entry-checkbox').length;
1210 - var checkedItems = $('.mxchat-entry-checkbox:checked').length;
1211 - $('.mxchat-entry-checkbox-all').prop('checked', totalItems > 0 && checkedItems === totalItems);
1212 - $('.mxchat-entry-checkbox-all').prop('indeterminate', checkedItems > 0 && checkedItems < totalItems);
1213 - }
1214 -
1215 - // Select all checkbox handler
1216 - $(document).on('change', '.mxchat-entry-checkbox-all', function() {
1217 - var isChecked = $(this).is(':checked');
1218 -
1219 - // Sync all select-all checkboxes
1220 - $('.mxchat-entry-checkbox-all').prop('checked', isChecked);
1221 -
1222 - $('.mxchat-entry-checkbox').prop('checked', isChecked);
1223 -
1224 - if (isChecked) {
1225 - $('.mxchat-entry-checkbox').each(function() {
1226 - var entryData = {
1227 - id: $(this).data('entry-id'),
1228 - source: $(this).data('source'),
1229 - sourceUrl: $(this).data('source-url'),
1230 - isGroup: $(this).data('is-group'),
1231 - chunkCount: $(this).data('chunk-count') || 1
1232 - };
1233 - selectedKnowledgeEntries.add(JSON.stringify(entryData));
1234 - $(this).closest('tr').addClass('selected');
1235 - });
1236 - } else {
1237 - selectedKnowledgeEntries.clear();
1238 - $('tr.selected').removeClass('selected');
1239 - }
1240 -
1241 - updateKnowledgeSelectionUI();
1242 - });
1243 -
1244 - // Individual checkbox handler
1245 - $(document).on('change', '.mxchat-entry-checkbox', function() {
1246 - var $checkbox = $(this);
1247 - var $row = $checkbox.closest('tr');
1248 - var entryData = {
1249 - id: $checkbox.data('entry-id'),
1250 - source: $checkbox.data('source'),
1251 - sourceUrl: $checkbox.data('source-url'),
1252 - isGroup: $checkbox.data('is-group'),
1253 - chunkCount: $checkbox.data('chunk-count') || 1
1254 - };
1255 - var entryKey = JSON.stringify(entryData);
1256 -
1257 - if ($checkbox.is(':checked')) {
1258 - selectedKnowledgeEntries.add(entryKey);
1259 - $row.addClass('selected');
1260 - } else {
1261 - selectedKnowledgeEntries.delete(entryKey);
1262 - $row.removeClass('selected');
1263 - }
1264 -
1265 - updateKnowledgeSelectionUI();
1266 - });
1267 -
1268 - // Bulk delete button handler
1269 - $(document).on('click', '#mxchat-delete-selected-entries', function() {
1270 - var count = selectedKnowledgeEntries.size;
1271 - if (count === 0) return;
1272 -
1273 - if (!confirm('Are you sure you want to delete ' + count + ' selected entries? This action cannot be undone.')) {
1274 - return;
1275 - }
1276 -
1277 - var $button = $(this);
1278 - var nonce = $button.data('nonce');
1279 - var botId = $button.data('bot-id');
1280 -
1281 - // Parse selected entries
1282 - var entries = Array.from(selectedKnowledgeEntries).map(function(entryStr) {
1283 - return JSON.parse(entryStr);
1284 - });
1285 -
1286 - // Show loading state
1287 - $button.prop('disabled', true);
1288 - $button.find('.mxchat-bulk-delete-text').text('Deleting...');
1289 - $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update spin');
1290 -
1291 - // Mark rows as deleting
1292 - entries.forEach(function(entry) {
1293 - $('#prompt-' + entry.id).addClass('mxchat-row-deleting');
1294 - });
1295 -
1296 - $.ajax({
1297 - url: ajaxurl,
1298 - type: 'POST',
1299 - timeout: 120000, // 120 seconds — matches server-side set_time_limit
1300 - data: {
1301 - action: 'mxchat_bulk_delete_knowledge',
1302 - entries: entries,
1303 - bot_id: botId,
1304 - nonce: nonce
1305 - },
1306 - success: function(response) {
1307 - if (response.success) {
1308 - var data = response.data;
1309 -
1310 - // Remove successful rows
1311 - if (data.success_ids && data.success_ids.length > 0) {
1312 - data.success_ids.forEach(function(id) {
1313 - var $row = $('#prompt-' + id);
1314 - // Also remove child chunk rows if it's a group
1315 - var groupId = $row.data('group-id');
1316 - if (groupId) {
1317 - $('.mxchat-chunk-row.' + groupId).fadeOut(300, function() {
1318 - $(this).remove();
1319 - });
1320 - }
1321 - $row.fadeOut(300, function() {
1322 - $(this).remove();
1323 - });
1324 - });
1325 - }
1326 -
1327 - // Handle failed entries
1328 - if (data.failed_ids && data.failed_ids.length > 0) {
1329 - data.failed_ids.forEach(function(id) {
1330 - $('#prompt-' + id).removeClass('mxchat-row-deleting').addClass('mxchat-row-error');
1331 - });
1332 - }
1333 -
1334 - // Show result message
1335 - var successCount = data.success_ids ? data.success_ids.length : 0;
1336 - var failedCount = data.failed_ids ? data.failed_ids.length : 0;
1337 - var message = 'Deleted ' + successCount + ' entries.';
1338 - if (failedCount > 0) {
1339 - message += ' ' + failedCount + ' entries failed to delete.';
1340 - }
1341 -
1342 - $('<div class="notice notice-success is-dismissible"><p>' + message + '</p></div>')
1343 - .insertAfter('.mxchat-hero')
1344 - .delay(5000)
1345 - .fadeOut();
1346 -
1347 - // Clear selection
1348 - selectedKnowledgeEntries.clear();
1349 - updateKnowledgeSelectionUI();
1350 -
1351 - // Update entry count displays (header and sidebar)
1352 - if (successCount > 0) {
1353 - var $countSpan = $('#mxchat-entry-count');
1354 - if ($countSpan.length) {
1355 - var currentText = $countSpan.text();
1356 - var match = currentText.match(/\((\d+)\)/);
1357 - if (match) {
1358 - var newCount = Math.max(0, parseInt(match[1]) - successCount);
1359 - updateEntryCount(newCount);
1360 - }
1361 - }
1362 - }
1363 -
1364 - } else {
1365 - alert('Error: ' + (response.data || 'Unknown error'));
1366 - $('tr.mxchat-row-deleting').removeClass('mxchat-row-deleting');
1367 - }
1368 - },
1369 - error: function(jqXHR, textStatus) {
1370 - var message = 'An error occurred while deleting entries.';
1371 - if (textStatus === 'timeout') {
1372 - message = 'The deletion request timed out. Please refresh the page to check which entries were deleted, then try again for any remaining.';
1373 - } else if (textStatus === 'error' && jqXHR.status === 0) {
1374 - message = 'Network error: The server took too long to respond. Please refresh and try deleting fewer entries at a time.';
1375 - } else if (jqXHR.responseJSON && jqXHR.responseJSON.data) {
1376 - message = 'Error: ' + jqXHR.responseJSON.data;
1377 - }
1378 - alert(message);
1379 - $('tr.mxchat-row-deleting').removeClass('mxchat-row-deleting');
1380 - },
1381 - complete: function() {
1382 - $button.prop('disabled', selectedKnowledgeEntries.size === 0);
1383 - $button.find('.mxchat-bulk-delete-text').text('Delete Selected');
1384 - $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash');
1385 - }
1386 - });
1387 - });
1388 -
1389 - // Clear selection when page changes
1390 - $(document).on('click', '.mxchat-page-link', function() {
1391 - selectedKnowledgeEntries.clear();
1392 - updateKnowledgeSelectionUI();
1393 - });
1394 -
1395 - // ========================================
1396 - // AJAX PAGINATION FOR KNOWLEDGE BASE ENTRIES
1397 - // ========================================
1398 -
1399 - // Handle pagination link clicks
1400 - $(document).on('click', '.mxchat-page-link', function(e) {
1401 - e.preventDefault();
1402 -
1403 - var $link = $(this);
1404 - var page = $link.data('page');
1405 - var $paginationWrapper = $('#mxchat-kb-pagination');
1406 - var $tbody = $('#mxchat-entries-tbody');
1407 -
1408 - if (!page || $link.hasClass('loading')) {
1409 - return;
1410 - }
1411 -
1412 - // Show loading state
1413 - $link.addClass('loading');
1414 - $tbody.css('opacity', '0.5');
1415 -
1416 - // Add loading indicator to pagination
1417 - var $loadingIndicator = $('<span class="mxchat-pagination-loading"><span class="dashicons dashicons-update spin"></span></span>');
1418 - $paginationWrapper.find('.mxchat-ajax-pagination').append($loadingIndicator);
1419 -
1420 - // Get search and filter values from pagination wrapper
1421 - var searchQuery = $paginationWrapper.data('search') || '';
1422 - var contentType = $paginationWrapper.data('content-type') || '';
1423 -
1424 - $.ajax({
1425 - url: ajaxurl,
1426 - type: 'POST',
1427 - data: {
1428 - action: 'mxchat_paginate_entries',
1429 - nonce: mxchatAdmin.entries_nonce,
1430 - bot_id: mxchatAdmin.bot_id || 'default',
1431 - page: page,
1432 - search: searchQuery,
1433 - content_type: contentType
1434 - },
1435 - success: function(response) {
1436 - $link.removeClass('loading');
1437 - $tbody.css('opacity', '1');
1438 - $loadingIndicator.remove();
1439 -
1440 - if (response.success && response.data) {
1441 - // Update the table body with new HTML
1442 - $tbody.html(response.data.html);
1443 -
1444 - // Update the pagination - find the inner container
1445 - if (response.data.pagination_html) {
1446 - // Replace the inner pagination div content
1447 - $paginationWrapper.html(response.data.pagination_html);
1448 - }
1449 -
1450 - // Update data attributes on wrapper (preserve search/filter for next pagination)
1451 - $paginationWrapper.attr('data-current-page', response.data.page);
1452 - if (response.data.total_pages) {
1453 - $paginationWrapper.attr('data-total-pages', response.data.total_pages);
1454 - }
1455 - // Preserve search and content_type on the wrapper from the inner pagination div
1456 - var $innerPagination = $paginationWrapper.find('.mxchat-ajax-pagination');
1457 - if ($innerPagination.length) {
1458 - $paginationWrapper.attr('data-search', $innerPagination.data('search') || '');
1459 - $paginationWrapper.attr('data-content-type', $innerPagination.data('content-type') || '');
1460 - }
1461 -
1462 - // Scroll to top of the table smoothly
1463 - $('html, body').animate({
1464 - scrollTop: $('#knowledge-base').offset().top - 50
1465 - }, 300);
1466 -
1467 - // Update URL hash to stay on knowledge-base tab
1468 - if (window.history && window.history.replaceState) {
1469 - window.history.replaceState(null, '', window.location.pathname + window.location.search + '#knowledge-base');
1470 - }
1471 -
1472 - // Show success feedback
1473 - showNotification('success', 'Page ' + response.data.page + ' loaded');
1474 - } else {
1475 - showNotification('error', 'Failed to load page: ' + (response.data?.message || 'Unknown error'));
1476 - }
1477 - },
1478 - error: function(xhr, status, error) {
1479 - $link.removeClass('loading');
1480 - $tbody.css('opacity', '1');
1481 - $loadingIndicator.remove();
1482 - showNotification('error', 'Network error while loading page');
1483 - }
1484 - });
1485 - });
1486 -
1487 - // ========================================
1488 - // PINECONE REFRESH ENTRIES BUTTON
1489 - // ========================================
1490 -
1491 - $('#mxchat-refresh-pinecone-entries').on('click', function() {
1492 - var $button = $(this);
1493 - var $icon = $button.find('.dashicons');
1494 - var $tbody = $('#mxchat-entries-tbody');
1495 - var $paginationWrapper = $('#mxchat-kb-pagination');
1496 -
1497 - // Show loading state
1498 - $button.prop('disabled', true);
1499 - $icon.addClass('spin');
1500 - $tbody.css('opacity', '0.5');
1501 -
1502 - $.ajax({
1503 - url: ajaxurl,
1504 - type: 'POST',
1505 - data: {
1506 - action: 'mxchat_refresh_pinecone_entries',
1507 - nonce: mxchatAdmin.entries_nonce,
1508 - bot_id: mxchatAdmin.bot_id || 'default',
1509 - page: 1
1510 - },
1511 - success: function(response) {
1512 - $button.prop('disabled', false);
1513 - $icon.removeClass('spin');
1514 - $tbody.css('opacity', '1');
1515 -
1516 - if (response.success && response.data) {
1517 - // Update the table body with new HTML
1518 - $tbody.html(response.data.html);
1519 -
1520 - // Update the pagination (same pattern as refreshKnowledgeBaseTable)
1521 - if ($paginationWrapper.length) {
1522 - if (response.data.pagination_html) {
1523 - $paginationWrapper.html(response.data.pagination_html);
1524 - $paginationWrapper.attr('style', 'padding: 16px; border-top: 1px solid var(--mxch-card-border); text-align: center;');
1525 - } else {
1526 - $paginationWrapper.html('');
1527 - $paginationWrapper.attr('style', '');
1528 - }
1529 - }
1530 -
1531 - // Update the count display
1532 - if (response.data.total_count !== undefined) {
1533 - updateEntryCount(response.data.total_count);
1534 - }
1535 -
1536 - // Show success feedback
1537 - showNotification('success', 'Entries refreshed successfully!');
1538 - } else {
1539 - showNotification('error', 'Failed to refresh entries: ' + (response.data?.message || 'Unknown error'));
1540 - }
1541 - },
1542 - error: function(xhr, status, error) {
1543 - $button.prop('disabled', false);
1544 - $icon.removeClass('spin');
1545 - $tbody.css('opacity', '1');
1546 - showNotification('error', 'Network error while refreshing entries');
1547 - }
1548 - });
1549 - });
1550 -
1551 - // ========================================
1552 - // ACCORDION FUNCTIONALITY
1553 - // ========================================
1554 -
1555 - // Handle expand/collapse toggle
1556 - $(document).on('click', '.mxchat-expand-toggle', function(e) {
1557 - e.preventDefault();
1558 - e.stopPropagation();
1559 -
1560 - const $button = $(this);
1561 - const $wrapper = $button.closest('.mxchat-accordion-wrapper');
1562 - const $preview = $wrapper.find('.mxchat-content-preview');
1563 - const $fullContent = $wrapper.find('.mxchat-content-full');
1564 -
1565 - // Toggle expanded state
1566 - if ($fullContent.is(':visible')) {
1567 - // Collapse
1568 - $fullContent.slideUp(300);
1569 - $button.removeClass('expanded');
1570 - } else {
1571 - // Expand
1572 - $fullContent.slideDown(300);
1573 - $button.addClass('expanded');
1574 - }
1575 - });
1576 -
1577 - // Click anywhere on preview to toggle (expand or collapse)
1578 - $(document).on('click', '.mxchat-content-preview', function(e) {
1579 - // Only trigger if not clicking the button directly
1580 - if (!$(e.target).closest('.mxchat-expand-toggle').length) {
1581 - const $preview = $(this);
1582 - const $wrapper = $preview.closest('.mxchat-accordion-wrapper');
1583 - const $button = $preview.find('.mxchat-expand-toggle');
1584 -
1585 - // Only trigger if there's a button (meaning content is long enough to expand)
1586 - if ($button.length) {
1587 - $button.trigger('click');
1588 - }
1589 - }
1590 - });
1591 -
1592 - // ========================================
1593 - // CHUNK GROUP TOGGLE FUNCTIONALITY
1594 - // ========================================
1595 -
1596 - // Handle chunk group expand/collapse toggle
1597 - $(document).on('click', '.mxchat-chunk-toggle', function(e) {
1598 - e.preventDefault();
1599 - e.stopPropagation();
1600 -
1601 - const $button = $(this);
1602 - const groupId = $button.data('group-id');
1603 - const $chunkRows = $('.mxchat-chunk-row.' + groupId);
1604 -
1605 - // Toggle expanded state
1606 - if ($button.hasClass('expanded')) {
1607 - // Collapse
1608 - $chunkRows.slideUp(300);
1609 - $button.removeClass('expanded');
1610 - } else {
1611 - // Expand
1612 - $chunkRows.slideDown(300);
1613 - $button.addClass('expanded');
1614 - }
1615 - });
1616 -
1617 - // Handle delete button for chunk groups
1618 - $(document).on('click', '.delete-button-group', function(e) {
1619 - e.preventDefault();
1620 - e.stopPropagation();
1621 -
1622 - const $button = $(this);
1623 - const sourceUrl = $button.data('source-url');
1624 - const chunkCount = $button.data('chunk-count');
1625 - const dataSource = $button.data('data-source');
1626 - const botId = $button.data('bot-id');
1627 - const nonce = $button.data('nonce');
1628 -
1629 - // Confirm deletion
1630 - if (!confirm('Are you sure you want to delete all ' + chunkCount + ' chunks for this URL? This action cannot be undone.')) {
1631 - return;
1632 - }
1633 -
1634 - // Show loading state
1635 - $button.prop('disabled', true);
1636 - $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update spin');
1637 -
1638 - // Make AJAX request to delete all chunks for this URL
1639 - $.ajax({
1640 - url: ajaxurl,
1641 - type: 'POST',
1642 - data: {
1643 - action: 'mxchat_delete_chunks_by_url',
1644 - source_url: sourceUrl,
1645 - data_source: dataSource,
1646 - bot_id: botId,
1647 - nonce: nonce
1648 - },
1649 - success: function(response) {
1650 - if (response.success) {
1651 - // Remove the group header row and all chunk rows
1652 - const $headerRow = $button.closest('tr');
1653 - const groupId = $headerRow.data('group-id');
1654 - $('.mxchat-chunk-row.' + groupId).fadeOut(300, function() {
1655 - $(this).remove();
1656 - });
1657 - $headerRow.fadeOut(300, function() {
1658 - $(this).remove();
1659 - });
1660 - } else {
1661 - var errorMsg = response.data || 'Unknown error';
1662 - if (typeof response.data === 'object' && response.data.message) {
1663 - errorMsg = response.data.message;
1664 - }
1665 - alert('Error deleting chunks: ' + errorMsg);
1666 - $button.prop('disabled', false);
1667 - $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash');
1668 - }
1669 - },
1670 - error: function(xhr, status, error) {
1671 - console.error('AJAX error:', xhr.responseText);
1672 - alert('Error deleting chunks: ' + (error || 'Server error'));
1673 - $button.prop('disabled', false);
1674 - $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash');
1675 - }
1676 - });
1677 - });
1678 -
1679 - // ========================================
1680 - // REAL-TIME KNOWLEDGE ENTRIES UPDATE
1681 - // ========================================
1682 -
1683 - let lastEntryId = 0;
1684 - let entriesPollingInterval = null;
1685 - let isEntriesPolling = false;
1686 -
1687 - // Initialize: get the highest ID from the current table
1688 - function initializeLastEntryId() {
1689 - const $tbody = $('#mxchat-entries-tbody');
1690 - if ($tbody.length === 0) return;
1691 -
1692 - // Get the highest ID from the table
1693 - $tbody.find('tr').each(function() {
1694 - const idText = $(this).find('td:first').text().trim();
1695 - const id = parseInt(idText);
1696 - if (!isNaN(id) && id > lastEntryId) {
1697 - lastEntryId = id;
1698 - }
1699 - });
1700 - }
1701 -
1702 - // Poll for new entries during processing
1703 - function startEntriesPolling() {
1704 - if (entriesPollingInterval) return; // Already polling
1705 -
1706 - isEntriesPolling = true;
1707 -
1708 - // Fetch immediately, then start interval
1709 - fetchNewEntries();
1710 -
1711 - entriesPollingInterval = setInterval(function() {
1712 - fetchNewEntries();
1713 - }, 3000); // Poll every 3 seconds
1714 - }
1715 -
1716 - function stopEntriesPolling() {
1717 - if (entriesPollingInterval) {
1718 - clearInterval(entriesPollingInterval);
1719 - entriesPollingInterval = null;
1720 - }
1721 - isEntriesPolling = false;
1722 - }
1723 -
1724 - // Track Pinecone count for change detection
1725 - var lastPineconeCount = 0;
1726 - var pineconeRefreshPending = false;
1727 -
1728 - // Fetch new entries from server
1729 - function fetchNewEntries() {
1730 - if (!mxchatAdmin.entries_nonce) {
1731 - return;
1732 - }
1733 -
1734 - $.ajax({
1735 - url: ajaxurl,
1736 - type: 'POST',
1737 - data: {
1738 - action: 'mxchat_get_recent_entries',
1739 - nonce: mxchatAdmin.entries_nonce,
1740 - last_id: lastEntryId,
1741 - bot_id: mxchatAdmin.bot_id || 'default',
1742 - limit: 20
1743 - },
1744 - success: function(response) {
1745 - if (response.success && response.data) {
1746 - // Update count
1747 - if (response.data.total_count !== undefined) {
1748 - updateEntryCount(response.data.total_count);
1749 - }
1750 -
1751 - // Handle Pinecone data source differently
1752 - if (response.data.data_source === 'pinecone') {
1753 - var newCount = response.data.total_count || 0;
1754 -
1755 - // If count changed and we haven't scheduled a refresh yet
1756 - if (newCount !== lastPineconeCount && !pineconeRefreshPending) {
1757 - lastPineconeCount = newCount;
1758 -
1759 - // Schedule a table refresh after processing completes
1760 - // Show a "refresh to see entries" message
1761 - var $tbody = $('#mxchat-entries-tbody');
1762 - var $refreshNotice = $tbody.find('.mxchat-pinecone-refresh-notice');
1763 -
1764 - if ($refreshNotice.length === 0 && newCount > 0) {
1765 - var noticeHtml = '<tr class="mxchat-pinecone-refresh-notice">' +
1766 - '<td colspan="4" style="padding: 20px; text-align: center; background: #f0f7ff; border-bottom: 1px solid var(--mxch-card-border);">' +
1767 - '<span class="dashicons dashicons-update" style="color: #7873f5; margin-right: 8px;"></span>' +
1768 - '<strong>' + newCount + ' entries in Pinecone.</strong> ' +
1769 - '<a href="#" class="mxchat-refresh-table-link" style="color: #7873f5; text-decoration: underline;">Refresh to see new entries</a>' +
1770 - '</td></tr>';
1771 - $tbody.prepend(noticeHtml);
1772 -
1773 - // Handle refresh link click
1774 - $tbody.find('.mxchat-refresh-table-link').on('click', function(e) {
1775 - e.preventDefault();
1776 - location.reload();
1777 - });
1778 - } else if ($refreshNotice.length > 0) {
1779 - // Update the count in existing notice
1780 - $refreshNotice.find('strong').text(newCount + ' entries in Pinecone.');
1781 - }
1782 - }
1783 - } else {
1784 - // WordPress DB - Track new entries and show refresh notice
1785 - // Don't add rows individually during processing as they need to be grouped by source_url
1786 - if (response.data.entries && response.data.entries.length > 0) {
1787 - // Update last ID to track progress
1788 - if (response.data.max_id > lastEntryId) {
1789 - lastEntryId = response.data.max_id;
1790 - }
1791 -
1792 - // Show/update refresh notice (similar to Pinecone handling)
1793 - var $tbody = $('#mxchat-entries-tbody');
1794 - var $refreshNotice = $tbody.find('.mxchat-wordpress-refresh-notice');
1795 - var newCount = response.data.total_count || 0;
1796 -
1797 - if ($refreshNotice.length === 0 && newCount > 0) {
1798 - var noticeHtml = '<tr class="mxchat-wordpress-refresh-notice mxchat-new-entry">' +
1799 - '<td colspan="4" style="padding: 16px 20px; text-align: center; background: linear-gradient(135deg, rgba(120, 115, 245, 0.08) 0%, rgba(167, 139, 250, 0.05) 100%); border-bottom: 1px solid var(--mxch-card-border);">' +
1800 - '<span class="dashicons dashicons-update spin" style="color: #7873f5; margin-right: 8px;"></span>' +
1801 - '<strong style="color: var(--mxch-text-primary);">Processing... <span class="mxchat-processing-count">' + newCount + '</span> entries</strong>' +
1802 - '</td></tr>';
1803 - $tbody.prepend(noticeHtml);
1804 - } else if ($refreshNotice.length > 0) {
1805 - // Update the count in existing notice
1806 - $refreshNotice.find('.mxchat-processing-count').text(newCount);
1807 - }
1808 - } else {
1809 - // Update last ID even if no new entries
1810 - if (response.data.max_id > lastEntryId) {
1811 - lastEntryId = response.data.max_id;
1812 - }
1813 - }
1814 - }
1815 - }
1816 - },
1817 - error: function(xhr, status, error) {
1818 - console.error('MxChat: Error fetching new entries:', error, xhr.responseText);
1819 - }
1820 - });
1821 - }
1822 -
1823 - // Update the entry count display
1824 - function updateEntryCount(count) {
1825 - // Update main table count
1826 - const $countSpan = $('#mxchat-entry-count');
1827 - if ($countSpan.length) {
1828 - $countSpan.text('(' + count + ')');
1829 -
1830 - // Flash animation to indicate update
1831 - $countSpan.addClass('mxchat-count-updated');
1832 - setTimeout(function() {
1833 - $countSpan.removeClass('mxchat-count-updated');
1834 - }, 1000);
1835 - }
1836 -
1837 - // Update sidebar badge count
1838 - const $sidebarCount = $('#mxchat-sidebar-count');
1839 - if ($sidebarCount.length) {
1840 - $sidebarCount.text(count);
1841 -
1842 - // Flash animation for sidebar too
1843 - $sidebarCount.addClass('mxchat-count-updated');
1844 - setTimeout(function() {
1845 - $sidebarCount.removeClass('mxchat-count-updated');
1846 - }, 1000);
1847 - }
1848 - }
1849 -
1850 - // Refresh the knowledge base table via AJAX pagination
1851 - // This ensures entries are properly grouped by source_url
1852 - function refreshKnowledgeBaseTable() {
1853 - var $paginationWrapper = $('#mxchat-kb-pagination');
1854 - var $tbody = $('#mxchat-entries-tbody');
1855 -
1856 - if ($tbody.length === 0) {
1857 - return;
1858 - }
1859 -
1860 - // Remove any processing notice
1861 - $tbody.find('.mxchat-wordpress-refresh-notice, .mxchat-pinecone-refresh-notice').remove();
1862 -
1863 - // Show loading state
1864 - $tbody.css('opacity', '0.5');
1865 -
1866 - $.ajax({
1867 - url: ajaxurl,
1868 - type: 'POST',
1869 - data: {
1870 - action: 'mxchat_paginate_entries',
1871 - nonce: mxchatAdmin.entries_nonce,
1872 - bot_id: mxchatAdmin.bot_id || 'default',
1873 - page: 1 // Always go to first page to see newest entries
1874 - },
1875 - success: function(response) {
1876 - $tbody.css('opacity', '1');
1877 -
1878 - if (response.success && response.data) {
1879 - // Update the table body with properly grouped HTML
1880 - $tbody.html(response.data.html);
1881 -
1882 - // Update the pagination
1883 - if ($paginationWrapper.length) {
1884 - if (response.data.pagination_html) {
1885 - // Add pagination content and styling
1886 - $paginationWrapper.html(response.data.pagination_html);
1887 - $paginationWrapper.attr('style', 'padding: 16px; border-top: 1px solid var(--mxch-card-border); text-align: center;');
1888 - } else {
1889 - // No pagination needed - clear and hide
1890 - $paginationWrapper.html('');
1891 - $paginationWrapper.attr('style', '');
1892 - }
1893 - }
1894 -
1895 - // Update count display
1896 - if (response.data.total_count !== undefined) {
1897 - updateEntryCount(response.data.total_count);
1898 - }
1899 - }
1900 - },
1901 - error: function(xhr, status, error) {
1902 - $tbody.css('opacity', '1');
1903 - console.error('MxChat: Error refreshing table:', error);
1904 - }
1905 - });
1906 - }
1907 -
1908 - // Expose refreshKnowledgeBaseTable for external access (content-selector.js)
1909 - window.refreshKnowledgeBaseTable = refreshKnowledgeBaseTable;
1910 -
1911 - // Add new entries to the table (kept for backwards compatibility but not used during processing)
1912 - function addNewEntriesToTable(entries) {
1913 - const $tbody = $('#mxchat-entries-tbody');
1914 - if ($tbody.length === 0) return;
1915 -
1916 - // Remove "no entries" message if present
1917 - const $noEntries = $tbody.find('td[colspan="4"]').closest('tr');
1918 - if ($noEntries.length) {
1919 - $noEntries.remove();
1920 - }
1921 -
1922 - // Add entries in reverse order (oldest first, so newest ends up at top)
1923 - entries.reverse().forEach(function(entry) {
1924 - // Check if entry already exists
1925 - if ($tbody.find('tr[data-entry-id="' + entry.id + '"]').length > 0) {
1926 - return;
1927 - }
1928 -
1929 - const sourceHtml = entry.has_link
1930 - ? '<a href="' + entry.source_url + '" target="_blank" style="color: var(--mxch-primary); text-decoration: none;"><span class="dashicons dashicons-external" style="font-size: 14px;"></span> View</a>'
1931 - : '<span style="color: var(--mxch-text-muted);">Manual</span>';
1932 -
1933 - const deleteUrl = mxchatAdmin.admin_url + 'admin-post.php?action=mxchat_delete_prompt&id=' + entry.id + '&_wpnonce=' + entry.delete_nonce;
1934 -
1935 - // Check if content needs expand button (content longer than preview)
1936 - const needsExpand = entry.content_length > entry.preview_length;
1937 -
1938 - // Build accordion-style content cell (matching initial page load structure)
1939 - let contentHtml = '<div class="mxchat-accordion-wrapper">' +
1940 - '<div class="mxchat-content-preview">' +
1941 - '<span class="preview-text">' + entry.preview + '</span>';
1942 -
1943 - if (needsExpand) {
1944 - contentHtml += '<button class="mxchat-expand-toggle" type="button">' +
1945 - '<span class="dashicons dashicons-arrow-down-alt2"></span>' +
1946 - '</button>';
1947 - }
1948 -
1949 - contentHtml += '</div>' +
1950 - '<div class="mxchat-content-full" style="display: none;">' +
1951 - '<div class="content-view">' + entry.full_content + '</div>' +
1952 - '</div>' +
1953 - '</div>';
1954 -
1955 - const $row = $('<tr id="prompt-' + entry.id + '" data-entry-id="' + entry.id + '" data-source="wordpress" style="border-bottom: 1px solid var(--mxch-card-border); display: none;">' +
1956 - '<td style="padding: 12px 16px; font-size: 13px;">' + entry.id + '</td>' +
1957 - '<td class="mxchat-content-cell" style="padding: 12px 16px; font-size: 13px;">' + contentHtml + '</td>' +
1958 - '<td class="mxchat-url-cell" style="padding: 12px 16px; font-size: 13px;">' + sourceHtml + '</td>' +
1959 - '<td style="padding: 12px 16px; white-space: nowrap;">' +
1960 - '<button type="button" class="mxch-btn mxch-btn-ghost mxch-btn-sm mxchat-edit-entry-btn"' +
1961 - ' data-source-url="' + (entry.source_url || '') + '"' +
1962 - ' data-entry-id="' + entry.id + '"' +
1963 - ' data-data-source="wordpress"' +
1964 - ' data-bot-id="' + (entry.bot_id || 'default') + '"' +
1965 - ' data-nonce="' + (entry.edit_nonce || '') + '"' +
1966 - ' title="Edit content">' +
1967 - '<span class="dashicons dashicons-edit" style="font-size: 14px;"></span>' +
1968 - '</button>' +
1969 - '<a href="' + deleteUrl + '" class="mxch-btn mxch-btn-ghost mxch-btn-sm" style="color: var(--mxch-error);" onclick="return confirm(\'Delete this entry?\');">' +
1970 - '<span class="dashicons dashicons-trash" style="font-size: 14px;"></span>' +
1971 - '</a>' +
1972 - '</td>' +
1973 - '</tr>');
1974 -
1975 - // Add highlight class and prepend to tbody
1976 - $row.addClass('mxchat-new-entry');
1977 - $tbody.prepend($row);
1978 - $row.slideDown(300);
1979 -
1980 - // Remove highlight after animation
1981 - setTimeout(function() {
1982 - $row.removeClass('mxchat-new-entry');
1983 - }, 2000);
1984 - });
1985 - }
1986 -
1987 - // Initialize entry ID tracking
1988 - initializeLastEntryId();
1989 -
1990 - // Check on page load if there's already active processing (e.g., page was refreshed during processing)
1991 - if ($('.mxchat-status-card').length > 0) {
1992 - // Check if processing is active via AJAX
1993 - $.ajax({
1994 - url: ajaxurl,
1995 - type: 'POST',
1996 - data: {
1997 - action: 'mxchat_get_status_updates',
1998 - nonce: mxchatAdmin.status_nonce
1999 - },
2000 - success: function(response) {
2001 - if (response.is_processing) {
2002 - startEntriesPolling();
2003 - }
2004 - }
2005 - });
2006 - }
2007 -
2008 - // Expose functions for external access
2009 - window.mxchatEntriesPolling = {
2010 - start: startEntriesPolling,
2011 - stop: stopEntriesPolling,
2012 - fetch: fetchNewEntries
2013 - };
2014 -
2015 - // ========================================
2016 - // SITEMAP DETECTION FUNCTIONALITY
2017 - // ========================================
2018 -
2019 - let sitemapDetectionInitialized = false;
2020 -
2021 - /**
2022 - * Initialize sitemap detection when Sitemap Import is clicked
2023 - */
2024 - function initSitemapDetection() {
2025 - if (sitemapDetectionInitialized) return;
2026 -
2027 - const loadingEl = document.getElementById('mxchat-sitemaps-loading');
2028 - const detectedEl = document.getElementById('mxchat-detected-sitemaps');
2029 - const noSitemapsEl = document.getElementById('mxchat-no-sitemaps');
2030 - const listEl = document.getElementById('mxchat-sitemaps-list');
2031 - const refreshBtn = document.getElementById('mxchat-refresh-sitemaps');
2032 - const nonceEl = document.getElementById('mxchat-detect-sitemaps-nonce');
2033 -
2034 - if (!loadingEl || !nonceEl) return;
2035 -
2036 - sitemapDetectionInitialized = true;
2037 -
2038 - function detectSitemaps() {
2039 - // Show loading
2040 - loadingEl.style.display = 'block';
2041 - if (detectedEl) detectedEl.style.display = 'none';
2042 - if (noSitemapsEl) noSitemapsEl.style.display = 'none';
2043 -
2044 - // Disable refresh button
2045 - if (refreshBtn) {
2046 - refreshBtn.disabled = true;
2047 - var refreshIcon = refreshBtn.querySelector('.dashicons');
2048 - if (refreshIcon) refreshIcon.classList.add('spin');
2049 - }
2050 -
2051 - $.ajax({
2052 - url: ajaxurl,
2053 - type: 'POST',
2054 - data: {
2055 - action: 'mxchat_detect_sitemaps',
2056 - nonce: nonceEl.value
2057 - },
2058 - timeout: 60000, // 60 second timeout for slow servers
2059 - success: function(data) {
2060 - loadingEl.style.display = 'none';
2061 -
2062 - // Re-enable refresh button
2063 - if (refreshBtn) {
2064 - refreshBtn.disabled = false;
2065 - var refreshIcon = refreshBtn.querySelector('.dashicons');
2066 - if (refreshIcon) refreshIcon.classList.remove('spin');
2067 - }
2068 -
2069 - if (data.success && data.data && data.data.sitemaps && data.data.sitemaps.length > 0) {
2070 - renderSitemaps(data.data.sitemaps);
2071 - if (detectedEl) detectedEl.style.display = 'block';
2072 - } else {
2073 - if (noSitemapsEl) {
2074 - noSitemapsEl.style.display = 'block';
2075 - $(noSitemapsEl).data('was-shown', true);
2076 - }
2077 - }
2078 - },
2079 - error: function(xhr, status, error) {
2080 - loadingEl.style.display = 'none';
2081 - if (noSitemapsEl) {
2082 - noSitemapsEl.style.display = 'block';
2083 - $(noSitemapsEl).data('was-shown', true);
2084 - }
2085 - if (refreshBtn) {
2086 - refreshBtn.disabled = false;
2087 - var refreshIcon = refreshBtn.querySelector('.dashicons');
2088 - if (refreshIcon) refreshIcon.classList.remove('spin');
2089 - }
2090 - }
2091 - });
2092 - }
2093 -
2094 - function renderSitemaps(sitemaps) {
2095 - if (!listEl) return;
2096 -
2097 - var html = '';
2098 - var botIdEl = document.getElementById('mxchat-sitemap-bot-id');
2099 - var botId = botIdEl ? botIdEl.value : '';
2100 -
2101 - sitemaps.forEach(function(sitemap) {
2102 - if (sitemap.type === 'index' && sitemap.sub_sitemaps && sitemap.sub_sitemaps.length > 0) {
2103 - // Render sitemap index with sub-sitemaps
2104 - html += '<div class="mxchat-sitemap-group">';
2105 - html += '<div class="mxchat-sitemap-group-header">';
2106 - html += '<div style="display: flex; align-items: center; gap: 10px;">';
2107 - html += '<span class="dashicons dashicons-arrow-right-alt2" style="transition: transform 0.2s;"></span>';
2108 - html += '<span class="dashicons dashicons-list-view" style="color: #7873f5;"></span>';
2109 - html += '<div>';
2110 - html += '<strong style="font-size: 13px;">Sitemap Index</strong>';
2111 - html += '<span style="color: #666; font-size: 12px; margin-left: 8px;">' + sitemap.source + '</span>';
2112 - html += '</div>';
2113 - html += '</div>';
2114 - html += '<span style="background: rgba(120, 115, 245, 0.1); color: #7873f5; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600;">';
2115 - html += sitemap.sub_sitemaps.length + ' sitemaps';
2116 - html += '</span>';
2117 - html += '</div>';
2118 - html += '<div class="mxchat-sitemap-sub-list">';
2119 - sitemap.sub_sitemaps.forEach(function(sub) {
2120 - html += renderSitemapRow(sub, botId, true);
2121 - });
2122 - html += '</div>';
2123 - html += '</div>';
2124 - } else if (sitemap.type !== 'index') {
2125 - // Render standalone sitemap
2126 - html += renderSitemapRow(sitemap, botId, false);
2127 - }
2128 - });
2129 -
2130 - listEl.innerHTML = html;
2131 -
2132 - // Add click handlers for group toggles
2133 - $(listEl).find('.mxchat-sitemap-group-header').on('click', function() {
2134 - var $group = $(this).parent();
2135 - var $subList = $group.find('.mxchat-sitemap-sub-list');
2136 - var $arrow = $(this).find('.dashicons-arrow-right-alt2');
2137 -
2138 - $group.toggleClass('expanded');
2139 -
2140 - if ($group.hasClass('expanded')) {
2141 - $subList.slideDown(200);
2142 - $arrow.css('transform', 'rotate(90deg)');
2143 - } else {
2144 - $subList.slideUp(200);
2145 - $arrow.css('transform', 'rotate(0deg)');
2146 - }
2147 - });
2148 -
2149 - // Add click handlers for process buttons
2150 - $(listEl).find('.mxchat-process-sitemap-btn').on('click', function() {
2151 - var url = $(this).data('url');
2152 - var type = $(this).data('sitemap-type');
2153 - processSitemap(url, type, this);
2154 - });
2155 - }
2156 -
2157 - function renderSitemapRow(sitemap, botId, isSubItem) {
2158 - var typeLabels = {
2159 - 'content': 'Content',
2160 - 'taxonomy': 'Taxonomy',
2161 - 'author': 'Authors'
2162 - };
2163 - var typeLabel = typeLabels[sitemap.type] || sitemap.type;
2164 - var displayName = sitemap.name || sitemap.url.split('/').pop();
2165 - var urlCount = sitemap.url_count || 0;
2166 - var paddingLeft = isSubItem ? '40px' : '16px';
2167 -
2168 - var html = '<div class="mxchat-sitemap-row" style="padding-left: ' + paddingLeft + ';">';
2169 - html += '<div style="display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0;">';
2170 - html += '<span class="dashicons dashicons-media-text" style="color: #666; flex-shrink: 0;"></span>';
2171 - html += '<div style="min-width: 0; flex: 1;">';
2172 - html += '<div style="font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="' + sitemap.url + '">';
2173 - html += displayName;
2174 - html += '</div>';
2175 - html += '<div style="font-size: 11px; color: #666;">';
2176 - html += '<span style="background: #f0f0f0; padding: 1px 6px; border-radius: 3px; margin-right: 8px;">' + typeLabel + '</span>';
2177 - if (urlCount > 0) {
2178 - html += urlCount + ' URLs';
2179 - }
2180 - html += '</div>';
2181 - html += '</div>';
2182 - html += '</div>';
2183 - html += '<button type="button" class="mxchat-process-sitemap-btn mxch-btn mxch-btn-primary mxch-btn-sm" data-url="' + sitemap.url + '" data-sitemap-type="' + sitemap.type + '">';
2184 - html += '<span class="dashicons dashicons-download" style="font-size: 14px; margin-top: 3px;"></span> Process';
2185 - html += '</button>';
2186 - html += '</div>';
2187 -
2188 - return html;
2189 - }
2190 -
2191 - function processSitemap(url, type, buttonEl) {
2192 - var $button = $(buttonEl);
2193 - var originalHtml = $button.html();
2194 -
2195 - // Update button to show loading
2196 - $button.prop('disabled', true);
2197 - $button.html('<span class="dashicons dashicons-update spin" style="font-size: 14px; margin-top: 3px;"></span> Processing...');
2198 -
2199 - // Fill in the sitemap URL form and submit
2200 - var $form = $('#mxchat-url-form');
2201 - var $urlInput = $('#sitemap_url');
2202 - var $importType = $('#import_type');
2203 -
2204 - if ($urlInput.length) {
2205 - $urlInput.val(url);
2206 - }
2207 -
2208 - if ($importType.length) {
2209 - $importType.val('sitemap');
2210 - }
2211 -
2212 - // Add a hidden submit field if not present (required by the PHP handler)
2213 - if ($form.find('input[name="submit_sitemap"]').length === 0) {
2214 - $form.append('<input type="hidden" name="submit_sitemap" value="1">');
2215 - }
2216 -
2217 - // Submit the form
2218 - $form.submit();
2219 - }
2220 -
2221 -
2222 - // Refresh button handler
2223 - if (refreshBtn) {
2224 - $(refreshBtn).on('click', detectSitemaps);
2225 - }
2226 -
2227 - // Start detection
2228 - detectSitemaps();
2229 - }
2230 -
2231 - // Expose initSitemapDetection globally so it can be called from the import options handler
2232 - window.mxchatInitSitemapDetection = initSitemapDetection;
2233 -
2234 - // ========================================
2235 - // ADMIN NOTICE DISMISS FUNCTIONALITY
2236 - // ========================================
2237 -
2238 - // Initialize dismissible notices - add dismiss button if missing
2239 - function initDismissibleNotices() {
2240 - $('.notice.is-dismissible').each(function() {
2241 - var $notice = $(this);
2242 -
2243 - // Skip if already has a dismiss button
2244 - if ($notice.find('.notice-dismiss').length > 0) {
2245 - return;
2246 - }
2247 -
2248 - // Add dismiss button
2249 - var $dismissButton = $('<button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss this notice.</span></button>');
2250 - $notice.append($dismissButton);
2251 - });
2252 - }
2253 -
2254 - // Initialize on page load
2255 - initDismissibleNotices();
2256 -
2257 - // Use event delegation for dismiss button clicks - works for existing and dynamically added notices
2258 - $(document).on('click', '.notice.is-dismissible .notice-dismiss', function(e) {
2259 - e.preventDefault();
2260 - e.stopPropagation();
2261 -
2262 - var $notice = $(this).closest('.notice');
2263 - $notice.fadeTo(100, 0, function() {
2264 - $notice.slideUp(100, function() {
2265 - $notice.remove();
2266 - });
2267 - });
2268 - });
2269 -
2270 - // Re-initialize when new notices are added dynamically (e.g., via AJAX)
2271 - $(document).on('DOMNodeInserted', function(e) {
2272 - if ($(e.target).hasClass('notice') && $(e.target).hasClass('is-dismissible')) {
2273 - setTimeout(initDismissibleNotices, 10);
2274 - }
2275 1030 });
2276 1031 });