PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.1.3
MxChat – AI Chatbot & Content Generation for WordPress v3.1.3
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
mxchat-basic / js / knowledge-processing.js

knowledge-processing.js in MxChat – AI Chatbot & Content Generation for WordPress 3.1.3, at js/knowledge-processing.js

2,272 lines 95.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 jQuery(document).ready(function($) {
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 // QUEUE PROCESSING SYSTEM
74 // ========================================
75
76 let isProcessingQueue = false;
77 let currentQueueId = null;
78 let currentQueueType = null;
79
80 // Process 5 items at a time
81 const BATCH_SIZE = 5;
82
83 // Check if we should start queue processing on page load
84 checkForActiveQueues();
85
86 // Form submission handler - triggers queue processing
87 $('#mxchat-url-form').on('submit', function(e) {
88 //console.log('MxChat: Form submitted, queue will be created');
89
90 // Don't prevent default - let form submit normally
91 // But schedule a check after redirect
92 localStorage.setItem('mxchat_check_queue_after_submit', Date.now().toString());
93 });
94
95 // Check if we just submitted a form and need to start processing
96 const justSubmitted = localStorage.getItem('mxchat_check_queue_after_submit');
97 if (justSubmitted) {
98 const submitTime = parseInt(justSubmitted);
99 const now = Date.now();
100
101 // If submitted within last 10 seconds, wait for queue to be created
102 if (now - submitTime < 10000) {
103 //console.log('MxChat: Form was just submitted, waiting for queue creation...');
104 localStorage.removeItem('mxchat_check_queue_after_submit');
105
106 // Show processing message
107 if ($('.mxchat-processing-message').length === 0) {
108 const message = $('<div class="mxchat-processing-message" style="text-align: center; padding: 15px; background: #f0f7ff; border-radius: 8px; margin-top: 15px;">⏳ Queue created! Processing will start in a moment...</div>');
109 $('.mxchat-import-section').after(message);
110 }
111
112 // Check for queue multiple times with increasing delays
113 setTimeout(function() { checkForActiveQueues(); }, 1000);
114 setTimeout(function() { checkForActiveQueues(); }, 2000);
115 setTimeout(function() { checkForActiveQueues(); }, 3000);
116 setTimeout(function() { checkForActiveQueues(); }, 5000);
117 }
118 }
119
120 function checkForActiveQueues() {
121 $.ajax({
122 url: ajaxurl,
123 type: 'POST',
124 data: {
125 action: 'mxchat_get_status_updates',
126 nonce: mxchatAdmin.status_nonce
127 },
128 success: function(response) {
129 //console.log('MxChat: Checking for active queues...', response);
130
131 if (response.sitemap_queue_id && response.sitemap_status) {
132 if (response.sitemap_status.status === 'processing') {
133 //console.log('MxChat: Found active sitemap queue:', response.sitemap_queue_id);
134 startQueueProcessing(response.sitemap_queue_id, 'sitemap');
135 } else if (response.sitemap_status.status === 'complete') {
136 //console.log('MxChat: Sitemap queue already complete');
137 // ADD THIS LINE:
138 $('.mxchat-processing-message').remove();
139 // Show completed status card (no auto-refresh)
140 updateSitemapStatus(response.sitemap_status);
141 }
142 }
143
144 if (response.pdf_queue_id && response.pdf_status) {
145 if (response.pdf_status.status === 'processing') {
146 //console.log('MxChat: Found active PDF queue:', response.pdf_queue_id);
147 startQueueProcessing(response.pdf_queue_id, 'pdf');
148 } else if (response.pdf_status.status === 'complete') {
149 //console.log('MxChat: PDF queue already complete');
150 // ADD THIS LINE:
151 $('.mxchat-processing-message').remove();
152 // Show completed status card (no auto-refresh)
153 updatePdfStatus(response.pdf_status);
154 }
155 }
156
157 // ADD THIS: If no queues found at all, remove the message
158 if (!response.sitemap_queue_id && !response.pdf_queue_id) {
159 $('.mxchat-processing-message').remove();
160 }
161 },
162 error: function(xhr, status, error) {
163 console.error('MxChat: Error checking for active queues:', error);
164 // ADD THIS: Remove message on error too
165 $('.mxchat-processing-message').remove();
166 }
167 });
168 }
169
170 /**
171 * Start processing a queue
172 */
173 function startQueueProcessing(queueId, queueType) {
174 if (isProcessingQueue) {
175 //console.log('MxChat: Already processing a queue, skipping');
176 return;
177 }
178
179 isProcessingQueue = true;
180 currentQueueId = queueId;
181 currentQueueType = queueType;
182
183 //console.log('MxChat: Starting queue processing:', queueId, queueType);
184
185 // Remove any "waiting" messages
186 $('.mxchat-processing-message').remove();
187
188 // Create or update status card
189 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();
198 }
199
200 /**
201 * Process the next batch of items (5 at a time)
202 */
203 function processNextBatch() {
204 if (!isProcessingQueue) {
205 //console.log('MxChat: Processing stopped');
206 return;
207 }
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
220 }
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;
236 }
237 }
238
239 // If no items to process, queue is done
240 if (items.length === 0) {
241 if (queueComplete) {
242 handleQueueComplete();
243 } else {
244 verifyQueueCompletion();
245 }
246 return;
247 }
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 });
280 }
281
282 /**
283 * Verify if queue is actually complete
284 * Prevents infinite loops when last item fails
285 */
286 function verifyQueueCompletion() {
287 //console.log('MxChat: Verifying queue completion status...');
288
289 $.ajax({
290 url: ajaxurl,
291 type: 'POST',
292 data: {
293 action: 'mxchat_get_queue_status',
294 nonce: mxchatAdmin.queue_nonce,
295 queue_id: currentQueueId
296 },
297 success: function(response) {
298 if (response.success) {
299 const status = response.data;
300
301 // If no pending or processing items, queue is done
302 if (status.pending === 0 && status.processing === 0) {
303 //console.log('MxChat: Queue verified as complete');
304 handleQueueComplete();
305 } else {
306 // Still has items, try to continue
307 //console.log('MxChat: Queue still has pending items, continuing...');
308 processNextBatch();
309 }
310 } else {
311 // Can't verify, assume complete to prevent infinite loop
312 //console.log('MxChat: Could not verify queue status, assuming complete');
313 handleQueueComplete();
314 }
315 },
316 error: function() {
317 // Can't verify, assume complete to prevent infinite loop
318 //console.log('MxChat: Network error verifying queue, assuming complete');
319 handleQueueComplete();
320 }
321 });
322 }
323
324 /**
325 * Process a single queue item
326 * Returns a Promise that resolves when processing is complete
327 */
328 function processQueueItem(item) {
329 return $.ajax({
330 url: ajaxurl,
331 type: 'POST',
332 data: {
333 action: 'mxchat_process_queue_item',
334 nonce: mxchatAdmin.queue_nonce,
335 item_id: item.id,
336 item_type: item.type,
337 item_data: item.data,
338 bot_id: item.bot_id
339 }
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 });
356 }
357
358 /**
359 * Update queue progress (fetches latest stats)
360 */
361 function updateQueueProgress() {
362 $.ajax({
363 url: ajaxurl,
364 type: 'POST',
365 data: {
366 action: 'mxchat_get_queue_status',
367 nonce: mxchatAdmin.queue_nonce,
368 queue_id: currentQueueId
369 },
370 success: function(response) {
371 if (response.success) {
372 const status = response.data;
373
374 // Update the appropriate status card
375 if (currentQueueType === 'pdf') {
376 updatePdfStatusFromQueue(status);
377 } else {
378 updateSitemapStatusFromQueue(status);
379 }
380 }
381 }
382 });
383 }
384
385 /**
386 * Handle queue completion
387 * NO AUTO-REFRESH - Show completed card with errors until dismissed
388 */
389 function handleQueueComplete() {
390 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
400 // Get final status with error details
401 $.ajax({
402 url: ajaxurl,
403 type: 'POST',
404 data: {
405 action: 'mxchat_get_queue_status',
406 nonce: mxchatAdmin.queue_nonce,
407 queue_id: currentQueueId
408 },
409 success: function(response) {
410 if (response.success) {
411 const status = response.data;
412
413 // Show completed status card (NO REFRESH)
414 if (currentQueueType === 'pdf') {
415 showCompletedPdfCard(status);
416 } else {
417 showCompletedSitemapCard(status);
418 }
419
420 // Mark the queue as complete on server
421 markQueueAsComplete(currentQueueId);
422
423 // Show notification based on results
424 if (status.failed > 0) {
425 showNotification('warning',
426 `Processing completed: ${status.completed} succeeded, ${status.failed} failed. ` +
427 `Review errors below and dismiss when ready.`
428 );
429 } else {
430 showNotification('success',
431 `Processing completed successfully! All ${status.completed} items processed. ` +
432 `Dismiss the status card when ready.`
433 );
434 }
435
436 // NO AUTO-REFRESH - User must manually dismiss
437 } else {
438 // Couldn't get final status, just show generic completion
439 showNotification('success', 'Processing completed! Refresh page to see final results.');
440 }
441 },
442 error: function() {
443 // Error getting final status
444 showNotification('success', 'Processing completed! Refresh page to see final results.');
445 }
446 });
447 }
448
449 /**
450 * Show completed PDF card with full error details (NO RETRY BUTTON)
451 */
452 function showCompletedPdfCard(status) {
453 let $card = $('.mxchat-status-card:contains("PDF Processing")');
454
455 if ($card.length === 0) {
456 return;
457 }
458
459 // Remove processing UI elements
460 $card.find('.mxchat-stop-form').remove();
461 $card.find('.mxchat-status-warning').remove();
462
463 // Update header with completion badge
464 $card.find('.mxchat-status-badge').remove();
465 if (status.failed > 0) {
466 $card.find('.mxchat-status-header h4').after(
467 '<span class="mxchat-status-badge mxchat-status-warning" style="margin-left: 10px; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: normal;">⚠️ Completed with ' +
468 status.failed + ' failures - Refresh to view entries</span>'
469 );
470 } else {
471 $card.find('.mxchat-status-header h4').after(
472 '<span class="mxchat-status-badge mxchat-status-success" style="margin-left: 10px; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: normal;">✓ Complete - Refresh to view entries</span>'
473 );
474 }
475
476 // Add dismiss button
477 addDismissButton($card);
478
479 // Update progress bar to 100%
480 $card.find('.mxchat-progress-fill').css('width', '100%');
481
482 // Update details with final stats
483 let detailsHtml = '<div style="background: #f0f7ff; padding: 15px; border-radius: 4px; margin-bottom: 15px;">';
484 detailsHtml += '<h4 style="margin: 0 0 10px 0;">📊 Final Results</h4>';
485 detailsHtml += '<p style="margin: 5px 0;"><strong>Total Pages:</strong> ' + status.total + '</p>';
486 detailsHtml += '<p style="margin: 5px 0; color: #2ea44f;"><strong>✓ Successfully Processed:</strong> ' + status.completed + '</p>';
487
488 if (status.failed > 0) {
489 detailsHtml += '<p style="margin: 5px 0; color: #cf222e;"><strong>✗ Failed:</strong> ' + status.failed + '</p>';
490 }
491
492 detailsHtml += '</div>';
493
494 // Add error details if there are failures (NO RETRY BUTTON)
495 if (status.failed > 0 && status.failed_items && status.failed_items.length > 0) {
496 detailsHtml += '<div class="mxchat-error-summary" style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-top: 15px;">';
497 detailsHtml += '<h4 style="margin: 0 0 10px 0; color: #856404;">⚠️ Failed Pages</h4>';
498
499 detailsHtml += '<details style="cursor: pointer;">';
500 detailsHtml += '<summary style="font-weight: bold; color: #856404; padding: 5px 0;">Click to view ' + status.failed_items.length + ' failed pages</summary>';
501
502 detailsHtml += '<div style="margin-top: 10px; max-height: 400px; overflow-y: auto;">';
503 detailsHtml += '<table style="width: 100%; border-collapse: collapse;">';
504 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>';
505 detailsHtml += '<tbody>';
506
507 status.failed_items.forEach(function(item) {
508 const data = JSON.parse(item.item_data);
509 const pageNum = data.page_number || 'Unknown';
510
511 detailsHtml += '<tr style="border-bottom: 1px solid #ddd;">';
512 detailsHtml += '<td style="padding: 8px;">Page ' + pageNum + '</td>';
513 detailsHtml += '<td style="padding: 8px; word-break: break-word;">' + (item.error_message || 'Unknown error') + '</td>';
514 detailsHtml += '<td style="padding: 8px;">' + item.attempts + '</td>';
515 detailsHtml += '</tr>';
516 });
517
518 detailsHtml += '</tbody></table>';
519 detailsHtml += '</div>';
520 detailsHtml += '</details>';
521
522 detailsHtml += '</div>';
523 }
524
525 $card.find('.mxchat-status-details').html(detailsHtml);
526 }
527
528 /**
529 * Show completed sitemap card with full error details (NO RETRY BUTTON)
530 */
531 function showCompletedSitemapCard(status) {
532 let $card = $('.mxchat-status-card:contains("Sitemap Processing")');
533
534 if ($card.length === 0) {
535 return;
536 }
537
538 // Remove processing UI elements
539 $card.find('.mxchat-stop-form').remove();
540 $card.find('.mxchat-status-warning').remove();
541
542 // Update header with completion badge
543 $card.find('.mxchat-status-badge').remove();
544 if (status.failed > 0) {
545 $card.find('.mxchat-status-header h4').after(
546 '<span class="mxchat-status-badge mxchat-status-warning" style="margin-left: 10px; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: normal;">⚠️ Completed with ' +
547 status.failed + ' failures - Refresh to view entries</span>'
548 );
549 } else {
550 $card.find('.mxchat-status-header h4').after(
551 '<span class="mxchat-status-badge mxchat-status-success" style="margin-left: 10px; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: normal;">✓ Complete - Refresh to view entries</span>'
552 );
553 }
554
555 // Add dismiss button
556 addDismissButton($card);
557
558 // Update progress bar to 100%
559 $card.find('.mxchat-progress-fill').css('width', '100%');
560
561 // Update details with final stats
562 let detailsHtml = '<div style="background: #f0f7ff; padding: 15px; border-radius: 4px; margin-bottom: 15px;">';
563 detailsHtml += '<h4 style="margin: 0 0 10px 0;">📊 Final Results</h4>';
564 detailsHtml += '<p style="margin: 5px 0;"><strong>Total URLs:</strong> ' + status.total + '</p>';
565 detailsHtml += '<p style="margin: 5px 0; color: #2ea44f;"><strong>✓ Successfully Processed:</strong> ' + status.completed + '</p>';
566
567 if (status.failed > 0) {
568 detailsHtml += '<p style="margin: 5px 0; color: #cf222e;"><strong>✗ Failed:</strong> ' + status.failed + '</p>';
569 }
570
571 detailsHtml += '</div>';
572
573 // Add error details if there are failures (NO RETRY BUTTON)
574 if (status.failed > 0 && status.failed_items && status.failed_items.length > 0) {
575 detailsHtml += '<div class="mxchat-error-summary" style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-top: 15px;">';
576 detailsHtml += '<h4 style="margin: 0 0 10px 0; color: #856404;">⚠️ Failed URLs</h4>';
577
578 detailsHtml += '<details style="cursor: pointer;">';
579 detailsHtml += '<summary style="font-weight: bold; color: #856404; padding: 5px 0;">Click to view ' + status.failed_items.length + ' failed URLs</summary>';
580
581 detailsHtml += '<div style="margin-top: 10px; max-height: 400px; overflow-y: auto;">';
582 detailsHtml += '<table style="width: 100%; border-collapse: collapse;">';
583 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>';
584 detailsHtml += '<tbody>';
585
586 status.failed_items.forEach(function(item) {
587 const data = JSON.parse(item.item_data);
588 const url = data.url || 'Unknown URL';
589 const displayUrl = url.length > 60 ? url.substring(0, 57) + '...' : url;
590
591 detailsHtml += '<tr style="border-bottom: 1px solid #ddd;">';
592 detailsHtml += '<td style="padding: 8px;"><a href="' + url + '" target="_blank" style="color: #0073aa; text-decoration: none;">' + displayUrl + '</a></td>';
593 detailsHtml += '<td style="padding: 8px; word-break: break-word;">' + (item.error_message || 'Unknown error') + '</td>';
594 detailsHtml += '<td style="padding: 8px;">' + item.attempts + '</td>';
595 detailsHtml += '</tr>';
596 });
597
598 detailsHtml += '</tbody></table>';
599 detailsHtml += '</div>';
600 detailsHtml += '</details>';
601
602 detailsHtml += '</div>';
603 }
604
605 $card.find('.mxchat-status-details').html(detailsHtml);
606 }
607
608 /**
609 * Mark queue as complete on server side
610 * This prevents it from auto-starting on page refresh
611 */
612 function markQueueAsComplete(queueId) {
613 // This is a fire-and-forget call to update queue status
614 $.ajax({
615 url: ajaxurl,
616 type: 'POST',
617 data: {
618 action: 'mxchat_mark_queue_complete',
619 nonce: mxchatAdmin.queue_nonce,
620 queue_id: queueId
621 },
622 success: function(response) {
623 //console.log('MxChat: Queue marked as complete on server');
624 },
625 error: function() {
626 //console.log('MxChat: Could not mark queue as complete, but continuing');
627 }
628 });
629 }
630
631 /**
632 * Stop processing button handler
633 */
634 $(document).on('submit', '.mxchat-stop-form', function() {
635 //console.log('MxChat: Stop processing requested');
636 isProcessingQueue = false;
637 currentQueueId = null;
638 currentQueueType = null;
639 });
640
641 /**
642 * Create or update status card
643 */
644 function createOrUpdateStatusCard(queueType) {
645 const cardTitle = queueType === 'pdf' ? 'PDF Processing Status' : 'Sitemap Processing Status';
646 let $card = $('.mxchat-status-card:contains("' + cardTitle + '")');
647
648 if ($card.length === 0) {
649 // Create new card
650 let html = '<div class="mxchat-status-card">';
651 html += '<div class="mxchat-status-header">';
652 html += '<h4>' + cardTitle + '</h4>';
653 html += '<div class="mxchat-status-warning" style="background: #fff3cd; color: #856404; padding: 8px 12px; border-radius: 4px; font-size: 13px; margin: 10px 0;">';
654 html += '⚠️ <strong>Keep this tab open</strong> - Processing runs in your browser';
655 html += '</div>';
656 html += '<form method="post" class="mxchat-stop-form" action="' +
657 mxchatAdmin.admin_url + 'admin-post.php?action=mxchat_stop_processing">';
658 html += '<input type="hidden" name="mxchat_stop_processing_nonce" value="' +
659 mxchatAdmin.stop_nonce + '">';
660 html += '<button type="submit" name="stop_processing" class="mxchat-button-secondary">';
661 html += 'Stop Processing</button></form>';
662 html += '</div>';
663 html += '<div class="mxchat-progress-bar">';
664 html += '<div class="mxchat-progress-fill" style="width: 0%"></div>';
665 html += '</div>';
666 html += '<div class="mxchat-status-details">';
667 html += '<p>Initializing...</p>';
668 html += '</div>';
669 html += '</div>';
670
671 // Insert card
672 let $importSection = $('.mxchat-import-section');
673 if ($importSection.length > 0) {
674 $importSection.after($(html));
675 }
676 }
677 }
678
679 /**
680 * Update PDF status from queue data (DURING PROCESSING)
681 */
682 function updatePdfStatusFromQueue(status) {
683 let $card = $('.mxchat-status-card:contains("PDF Processing")');
684
685 if ($card.length === 0) {
686 return;
687 }
688
689 // Update progress bar
690 $card.find('.mxchat-progress-fill').css('width', status.percentage + '%');
691
692 // Update details
693 let detailsHtml = '<p>Progress: ' + (status.completed + status.failed) + ' of ' +
694 status.total + ' pages (' + status.percentage + '%)</p>';
695
696 if (status.completed > 0) {
697 detailsHtml += '<p><strong>✓ Processed successfully:</strong> ' + status.completed + '</p>';
698 }
699
700 if (status.failed > 0) {
701 detailsHtml += '<p class="error-count"><strong>✗ Failed pages:</strong> ' + status.failed + '</p>';
702 }
703
704 detailsHtml += '<p><strong>Status:</strong> Processing</p>';
705
706 $card.find('.mxchat-status-details').html(detailsHtml);
707 }
708
709 /**
710 * Update sitemap status from queue data (DURING PROCESSING)
711 */
712 function updateSitemapStatusFromQueue(status) {
713 let $card = $('.mxchat-status-card:contains("Sitemap Processing")');
714
715 if ($card.length === 0) {
716 return;
717 }
718
719 // Update progress bar
720 $card.find('.mxchat-progress-fill').css('width', status.percentage + '%');
721
722 // Update details
723 let detailsHtml = '<p>Progress: ' + (status.completed + status.failed) + ' of ' +
724 status.total + ' URLs (' + status.percentage + '%)</p>';
725
726 if (status.completed > 0) {
727 detailsHtml += '<p><strong>✓ Processed successfully:</strong> ' + status.completed + '</p>';
728 }
729
730 if (status.failed > 0) {
731 detailsHtml += '<p class="error-count"><strong>✗ Failed URLs:</strong> ' + status.failed + '</p>';
732 }
733
734 detailsHtml += '<p><strong>Status:</strong> Processing</p>';
735
736 $card.find('.mxchat-status-details').html(detailsHtml);
737 }
738
739 // ========================================
740 // STATUS UPDATES FOR COMPLETED QUEUES (FROM SERVER)
741 // ========================================
742
743 /**
744 * Dismiss completed status button handler
745 */
746 $(document).on('click', '.mxchat-dismiss-button', function() {
747 const $button = $(this);
748 const $card = $button.closest('.mxchat-status-card');
749
750 let cardType = $card.data('card-type');
751 if (!cardType) {
752 cardType = $card.find('h4').text().includes('PDF') ? 'pdf' : 'sitemap';
753 }
754
755 $card.fadeOut(300, function() {
756 $(this).remove();
757 });
758
759 $.ajax({
760 url: ajaxurl,
761 type: 'POST',
762 data: {
763 action: 'mxchat_clear_queue',
764 nonce: mxchatAdmin.queue_nonce,
765 queue_id: $card.data('queue-id') || ''
766 },
767 success: function(response) {
768 //console.log('MxChat: Queue cleared');
769 }
770 });
771 });
772
773 /**
774 * Update PDF status card (for already completed queues on page load)
775 */
776 function updatePdfStatus(status) {
777 let $pdfCard = $('.mxchat-status-card:contains("PDF Processing")');
778
779 if ($pdfCard.length === 0 && status) {
780 createPdfStatusCard(status);
781 $pdfCard = $('.mxchat-status-card:contains("PDF Processing")');
782 }
783
784 if ($pdfCard.length > 0 && status.status === 'complete') {
785 // Show as completed (same as showCompletedPdfCard but from server data)
786 showCompletedPdfCard(status);
787 }
788 }
789
790 /**
791 * Update sitemap status card (for already completed queues on page load)
792 */
793 function updateSitemapStatus(status) {
794 let $sitemapCard = $('.mxchat-status-card:contains("Sitemap Processing")');
795
796 if ($sitemapCard.length === 0 && status) {
797 createSitemapStatusCard(status);
798 $sitemapCard = $('.mxchat-status-card:contains("Sitemap Processing")');
799 }
800
801 if ($sitemapCard.length > 0 && status.status === 'complete') {
802 // Show as completed (same as showCompletedSitemapCard but from server data)
803 showCompletedSitemapCard(status);
804 }
805 }
806
807 /**
808 * Create PDF status card
809 */
810 function createPdfStatusCard(status) {
811 let html = '<div class="mxchat-status-card" data-queue-id="' + (status.queue_id || '') + '">';
812 html += '<div class="mxchat-status-header">';
813 html += '<h4>PDF Processing Status</h4>';
814 html += '</div>';
815 html += '<div class="mxchat-progress-bar">';
816 html += '<div class="mxchat-progress-fill" style="width: ' + status.percentage + '%"></div>';
817 html += '</div>';
818 html += '<div class="mxchat-status-details">';
819 html += '<p>Progress: ' + status.processed_pages + ' of ' + status.total_pages + ' pages</p>';
820 html += '</div>';
821 html += '</div>';
822
823 $('.mxchat-import-section').after($(html));
824 }
825
826 /**
827 * Create sitemap status card
828 */
829 function createSitemapStatusCard(status) {
830 let html = '<div class="mxchat-status-card" data-queue-id="' + (status.queue_id || '') + '">';
831 html += '<div class="mxchat-status-header">';
832 html += '<h4>Sitemap Processing Status</h4>';
833 html += '</div>';
834 html += '<div class="mxchat-progress-bar">';
835 html += '<div class="mxchat-progress-fill" style="width: ' + status.percentage + '%"></div>';
836 html += '</div>';
837 html += '<div class="mxchat-status-details">';
838 html += '<p>Progress: ' + status.processed_urls + ' of ' + status.total_urls + ' URLs</p>';
839 html += '</div>';
840 html += '</div>';
841
842 $('.mxchat-import-section').after($(html));
843 }
844
845 /**
846 * Add dismiss button to completed cards
847 */
848 function addDismissButton($card) {
849 if ($card.find('.mxchat-dismiss-button').length === 0) {
850 const dismissButton = $('<button type="button" class="mxchat-dismiss-button" style="padding: 6px 12px; background: #666; color: white; border: none; border-radius: 3px; cursor: pointer; margin-left: 10px;">Dismiss</button>');
851 $card.find('.mxchat-status-header').append(dismissButton);
852 }
853 }
854
855 /**
856 * Show notification helper
857 */
858 function showNotification(type, message) {
859 const $notification = $('<div class="mxchat-kb-notification ' + type + '">' + message + '</div>');
860 $('.mxchat-content, body').first().prepend($notification);
861
862 setTimeout(function() {
863 $notification.fadeOut(300, function() {
864 $(this).remove();
865 });
866 }, 5000);
867 }
868
869 // ========================================
870 // ROLE-BASED CONTENT RESTRICTIONS (Keep existing code)
871 // ========================================
872
873 if ($('#mxchat-mappings-container').length > 0) {
874 loadTagRoleMappings();
875 }
876
877 $('#mxchat-add-tag-role').on('click', function() {
878 const tagSlug = $('#mxchat-tag-input').val().trim();
879 const roleRestriction = $('#mxchat-role-select').val();
880
881 if (!tagSlug) {
882 alert('Please enter a tag name');
883 return;
884 }
885
886 const $btn = $(this);
887 $btn.prop('disabled', true).html('<span class="dashicons dashicons-update-alt"></span> Adding...');
888
889 $.ajax({
890 url: ajaxurl,
891 type: 'POST',
892 data: {
893 action: 'mxchat_add_tag_role_mapping',
894 nonce: mxchatAdmin.settings_nonce,
895 tag_slug: tagSlug,
896 role_restriction: roleRestriction
897 },
898 success: function(response) {
899 if (response.success) {
900 $('#mxchat-tag-input').val('');
901 $('#mxchat-role-select').val('public');
902 loadTagRoleMappings();
903 showNotification('success', 'Tag-role mapping added successfully!');
904 } else {
905 alert('Error: ' + response.data);
906 }
907 $btn.prop('disabled', false).html('<span class="dashicons dashicons-plus-alt"></span> Add Mapping');
908 },
909 error: function() {
910 alert('Network error occurred');
911 $btn.prop('disabled', false).html('<span class="dashicons dashicons-plus-alt"></span> Add Mapping');
912 }
913 });
914 });
915
916 $(document).on('click', '.mxchat-delete-mapping', function() {
917 if (!confirm('Are you sure you want to delete this mapping?')) {
918 return;
919 }
920
921 const $btn = $(this);
922 const $row = $btn.closest('tr');
923 const tagSlug = $btn.data('tag-slug');
924
925 $btn.html('<span class="dashicons dashicons-update-alt"></span> Deleting...');
926 $row.addClass('mxchat-row-deleting');
927
928 $.ajax({
929 url: ajaxurl,
930 type: 'POST',
931 data: {
932 action: 'mxchat_delete_tag_role_mapping',
933 nonce: mxchatAdmin.settings_nonce,
934 tag_slug: tagSlug
935 },
936 success: function(response) {
937 if (response.success) {
938 $row.fadeOut(300, function() {
939 $(this).remove();
940 if ($('.mxchat-mappings-table tbody tr').length === 0) {
941 $('.mxchat-mappings-table').hide();
942 $('#mxchat-no-mappings').show();
943 }
944 });
945 showNotification('success', 'Mapping deleted successfully!');
946 } else {
947 alert('Error: ' + response.data);
948 $btn.html('<span class="dashicons dashicons-trash"></span> Delete');
949 $row.removeClass('mxchat-row-deleting');
950 }
951 },
952 error: function() {
953 alert('Network error occurred');
954 $btn.html('<span class="dashicons dashicons-trash"></span> Delete');
955 $row.removeClass('mxchat-row-deleting');
956 }
957 });
958 });
959
960 $('#mxchat-bulk-update-roles').on('click', function() {
961 if (!confirm('This will update role restrictions for all existing content with mapped tags. Continue?')) {
962 return;
963 }
964
965 const $btn = $(this);
966 const $progress = $('#mxchat-bulk-update-progress');
967 const $result = $('#mxchat-bulk-update-result');
968
969 $progress.show();
970 $result.hide();
971 $btn.prop('disabled', true);
972
973 $progress.find('.mxchat-progress-text').text('Starting bulk update...');
974 $progress.find('.mxchat-progress-fill').css('width', '0%');
975
976 $.ajax({
977 url: ajaxurl,
978 type: 'POST',
979 data: {
980 action: 'mxchat_bulk_update_tag_roles',
981 nonce: mxchatAdmin.settings_nonce
982 },
983 success: function(response) {
984 $progress.hide();
985 $btn.prop('disabled', false);
986
987 if (response.success) {
988 $result.removeClass('error').addClass('success');
989
990 let resultHtml = '<h5>Bulk Update Complete</h5>';
991 resultHtml += '<p><strong>Total Updated:</strong> ' + response.data.updated_count + '</p>';
992 resultHtml += '<p><strong>Tags Processed:</strong> ' + response.data.tags_processed + '</p>';
993
994 if (response.data.details && response.data.details.length > 0) {
995 resultHtml += '<ul>';
996 response.data.details.forEach(function(detail) {
997 resultHtml += '<li>' + detail + '</li>';
998 });
999 resultHtml += '</ul>';
1000 }
1001
1002 $result.html(resultHtml).show();
1003 showNotification('success', 'Bulk update completed successfully!');
1004 } else {
1005 $result.removeClass('success').addClass('error');
1006 $result.html('<h5>Update Failed</h5><p>' + response.data + '</p>').show();
1007 }
1008 },
1009 error: function() {
1010 $progress.hide();
1011 $btn.prop('disabled', false);
1012 $result.removeClass('success').addClass('error');
1013 $result.html('<h5>Network Error</h5><p>Please try again.</p>').show();
1014 }
1015 });
1016 });
1017
1018 function loadTagRoleMappings() {
1019 const $container = $('#mxchat-mappings-container');
1020 $container.html('<div class="mxchat-loading-mappings"><span class="mxchat-role-spinner is-active"></span> Loading mappings...</div>');
1021
1022 $.ajax({
1023 url: ajaxurl,
1024 type: 'POST',
1025 data: {
1026 action: 'mxchat_get_tag_role_mappings',
1027 nonce: mxchatAdmin.settings_nonce
1028 },
1029 success: function(response) {
1030 if (response.success && response.data.mappings.length > 0) {
1031 $('#mxchat-no-mappings').hide();
1032
1033 let html = '<table class="mxchat-mappings-table">';
1034 html += '<thead><tr><th>Tag</th><th>Role Restriction</th><th>Posts with Tag</th><th>Actions</th></tr></thead><tbody>';
1035
1036 response.data.mappings.forEach(function(mapping) {
1037 html += '<tr>';
1038 html += '<td><span class="mxchat-tag-badge"><span class="dashicons dashicons-tag"></span>' + mapping.tag_slug + '</span></td>';
1039 html += '<td><span class="mxchat-role-badge ' + mapping.role_restriction + '">' + mapping.role_label + '</span></td>';
1040 html += '<td><span class="mxchat-post-count"><span class="dashicons dashicons-admin-post"></span>' + mapping.post_count + '</span></td>';
1041 html += '<td><div class="mxchat-mapping-actions"><button class="mxchat-delete-mapping" data-tag-slug="' + mapping.tag_slug + '"><span class="dashicons dashicons-trash"></span> Delete</button></div></td>';
1042 html += '</tr>';
1043 });
1044
1045 html += '</tbody></table>';
1046 $container.html(html);
1047 } else {
1048 $container.html('');
1049 $('#mxchat-no-mappings').show();
1050 }
1051 },
1052 error: function() {
1053 $container.html('<div class="mxchat-error">Failed to load mappings. Please refresh the page.</div>');
1054 }
1055 });
1056 }
1057
1058 // ========================================
1059 // PINECONE DELETE HANDLER (Keep existing code)
1060 // ========================================
1061
1062 $(document).on('click', '.delete-button-ajax', function(e) {
1063 e.preventDefault();
1064
1065 if (!confirm('Are you sure you want to delete this entry?')) {
1066 return;
1067 }
1068
1069 var $button = $(this);
1070 var $row = $button.closest('tr');
1071 var vectorId = $button.data('vector-id');
1072 var botId = $button.data('bot-id') || 'default';
1073 var nonce = $button.data('nonce');
1074
1075 $button.prop('disabled', true);
1076 $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update-alt');
1077 $row.addClass('mxchat-row-deleting');
1078
1079 $.ajax({
1080 url: ajaxurl,
1081 type: 'POST',
1082 data: {
1083 action: 'mxchat_delete_pinecone_prompt',
1084 nonce: nonce,
1085 vector_id: vectorId,
1086 bot_id: botId
1087 },
1088 success: function(response) {
1089 if (response.success) {
1090 $row.fadeOut(500, function() {
1091 $(this).remove();
1092 // Update entry count displays (header and sidebar)
1093 var $countSpan = $('#mxchat-entry-count');
1094 if ($countSpan.length) {
1095 var currentText = $countSpan.text();
1096 var match = currentText.match(/\((\d+)\)/);
1097 if (match) {
1098 var newCount = Math.max(0, parseInt(match[1]) - 1);
1099 updateEntryCount(newCount);
1100 }
1101 }
1102 });
1103
1104 $('<div class="notice notice-success is-dismissible"><p>Entry deleted successfully from Pinecone.</p></div>')
1105 .insertAfter('.mxchat-hero')
1106 .delay(3000)
1107 .fadeOut();
1108 } else {
1109 $button.prop('disabled', false);
1110 $button.find('.dashicons').removeClass('dashicons-update-alt').addClass('dashicons-trash');
1111 $row.removeClass('mxchat-row-deleting');
1112 alert('Error: ' + response.data);
1113 }
1114 },
1115 error: function() {
1116 $button.prop('disabled', false);
1117 $button.find('.dashicons').removeClass('dashicons-update-alt').addClass('dashicons-trash');
1118 $row.removeClass('mxchat-row-deleting');
1119 alert('Network error occurred');
1120 }
1121 });
1122 });
1123
1124 // ========================================
1125 // WORDPRESS DATABASE DELETE HANDLER (AJAX)
1126 // ========================================
1127
1128 $(document).on('click', '.delete-button-wordpress', function(e) {
1129 e.preventDefault();
1130
1131 if (!confirm('Are you sure you want to delete this entry?')) {
1132 return;
1133 }
1134
1135 var $button = $(this);
1136 var $row = $button.closest('tr');
1137 var entryId = $button.data('entry-id');
1138 var nonce = $button.data('nonce');
1139
1140 $button.prop('disabled', true);
1141 $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update-alt spin');
1142 $row.addClass('mxchat-row-deleting');
1143
1144 $.ajax({
1145 url: ajaxurl,
1146 type: 'POST',
1147 data: {
1148 action: 'mxchat_delete_wordpress_prompt',
1149 nonce: nonce,
1150 entry_id: entryId
1151 },
1152 success: function(response) {
1153 if (response.success) {
1154 $row.fadeOut(500, function() {
1155 $(this).remove();
1156 // Update entry count displays (header and sidebar)
1157 var $countSpan = $('#mxchat-entry-count');
1158 if ($countSpan.length) {
1159 var currentText = $countSpan.text();
1160 var match = currentText.match(/\((\d+)\)/);
1161 if (match) {
1162 var newCount = Math.max(0, parseInt(match[1]) - 1);
1163 updateEntryCount(newCount);
1164 }
1165 }
1166 });
1167
1168 $('<div class="notice notice-success is-dismissible"><p>Entry deleted successfully.</p></div>')
1169 .insertAfter('.mxchat-hero')
1170 .delay(3000)
1171 .fadeOut();
1172 } else {
1173 $button.prop('disabled', false);
1174 $button.find('.dashicons').removeClass('dashicons-update-alt spin').addClass('dashicons-trash');
1175 $row.removeClass('mxchat-row-deleting');
1176 alert('Error: ' + response.data);
1177 }
1178 },
1179 error: function() {
1180 $button.prop('disabled', false);
1181 $button.find('.dashicons').removeClass('dashicons-update-alt spin').addClass('dashicons-trash');
1182 $row.removeClass('mxchat-row-deleting');
1183 alert('Network error occurred');
1184 }
1185 });
1186 });
1187
1188 // ========================================
1189 // BULK SELECTION FOR KNOWLEDGE ENTRIES
1190 // ========================================
1191
1192 var selectedKnowledgeEntries = new Set();
1193
1194 // Update selection UI
1195 function updateKnowledgeSelectionUI() {
1196 var count = selectedKnowledgeEntries.size;
1197 var $countEl = $('#mxchat-selected-entry-count');
1198 var $deleteBtn = $('#mxchat-delete-selected-entries');
1199 var $deleteAllForm = $('#mxchat-delete-all-form');
1200
1201 if (count > 0) {
1202 // Show Delete Selected button, hide Delete All form
1203 $deleteAllForm.hide();
1204 $deleteBtn.show();
1205 $countEl.text('(' + count + ')');
1206 } else {
1207 // Show Delete All form, hide Delete Selected button
1208 $deleteAllForm.show();
1209 $deleteBtn.hide();
1210 $countEl.text('');
1211 }
1212
1213 // Update select all checkbox state
1214 var totalItems = $('.mxchat-entry-checkbox').length;
1215 var checkedItems = $('.mxchat-entry-checkbox:checked').length;
1216 $('.mxchat-entry-checkbox-all').prop('checked', totalItems > 0 && checkedItems === totalItems);
1217 $('.mxchat-entry-checkbox-all').prop('indeterminate', checkedItems > 0 && checkedItems < totalItems);
1218 }
1219
1220 // Select all checkbox handler
1221 $(document).on('change', '.mxchat-entry-checkbox-all', function() {
1222 var isChecked = $(this).is(':checked');
1223
1224 // Sync all select-all checkboxes
1225 $('.mxchat-entry-checkbox-all').prop('checked', isChecked);
1226
1227 $('.mxchat-entry-checkbox').prop('checked', isChecked);
1228
1229 if (isChecked) {
1230 $('.mxchat-entry-checkbox').each(function() {
1231 var entryData = {
1232 id: $(this).data('entry-id'),
1233 source: $(this).data('source'),
1234 sourceUrl: $(this).data('source-url'),
1235 isGroup: $(this).data('is-group'),
1236 chunkCount: $(this).data('chunk-count') || 1
1237 };
1238 selectedKnowledgeEntries.add(JSON.stringify(entryData));
1239 $(this).closest('tr').addClass('selected');
1240 });
1241 } else {
1242 selectedKnowledgeEntries.clear();
1243 $('tr.selected').removeClass('selected');
1244 }
1245
1246 updateKnowledgeSelectionUI();
1247 });
1248
1249 // Individual checkbox handler
1250 $(document).on('change', '.mxchat-entry-checkbox', function() {
1251 var $checkbox = $(this);
1252 var $row = $checkbox.closest('tr');
1253 var entryData = {
1254 id: $checkbox.data('entry-id'),
1255 source: $checkbox.data('source'),
1256 sourceUrl: $checkbox.data('source-url'),
1257 isGroup: $checkbox.data('is-group'),
1258 chunkCount: $checkbox.data('chunk-count') || 1
1259 };
1260 var entryKey = JSON.stringify(entryData);
1261
1262 if ($checkbox.is(':checked')) {
1263 selectedKnowledgeEntries.add(entryKey);
1264 $row.addClass('selected');
1265 } else {
1266 selectedKnowledgeEntries.delete(entryKey);
1267 $row.removeClass('selected');
1268 }
1269
1270 updateKnowledgeSelectionUI();
1271 });
1272
1273 // Bulk delete button handler
1274 $(document).on('click', '#mxchat-delete-selected-entries', function() {
1275 var count = selectedKnowledgeEntries.size;
1276 if (count === 0) return;
1277
1278 if (!confirm('Are you sure you want to delete ' + count + ' selected entries? This action cannot be undone.')) {
1279 return;
1280 }
1281
1282 var $button = $(this);
1283 var nonce = $button.data('nonce');
1284 var botId = $button.data('bot-id');
1285
1286 // Parse selected entries
1287 var entries = Array.from(selectedKnowledgeEntries).map(function(entryStr) {
1288 return JSON.parse(entryStr);
1289 });
1290
1291 // Show loading state
1292 $button.prop('disabled', true);
1293 $button.find('.mxchat-bulk-delete-text').text('Deleting...');
1294 $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update spin');
1295
1296 // Mark rows as deleting
1297 entries.forEach(function(entry) {
1298 $('#prompt-' + entry.id).addClass('mxchat-row-deleting');
1299 });
1300
1301 $.ajax({
1302 url: ajaxurl,
1303 type: 'POST',
1304 timeout: 120000, // 120 seconds — matches server-side set_time_limit
1305 data: {
1306 action: 'mxchat_bulk_delete_knowledge',
1307 entries: entries,
1308 bot_id: botId,
1309 nonce: nonce
1310 },
1311 success: function(response) {
1312 if (response.success) {
1313 var data = response.data;
1314
1315 // Remove successful rows
1316 if (data.success_ids && data.success_ids.length > 0) {
1317 data.success_ids.forEach(function(id) {
1318 var $row = $('#prompt-' + id);
1319 // Also remove child chunk rows if it's a group
1320 var groupId = $row.data('group-id');
1321 if (groupId) {
1322 $('.mxchat-chunk-row.' + groupId).fadeOut(300, function() {
1323 $(this).remove();
1324 });
1325 }
1326 $row.fadeOut(300, function() {
1327 $(this).remove();
1328 });
1329 });
1330 }
1331
1332 // Handle failed entries
1333 if (data.failed_ids && data.failed_ids.length > 0) {
1334 data.failed_ids.forEach(function(id) {
1335 $('#prompt-' + id).removeClass('mxchat-row-deleting').addClass('mxchat-row-error');
1336 });
1337 }
1338
1339 // Show result message
1340 var successCount = data.success_ids ? data.success_ids.length : 0;
1341 var failedCount = data.failed_ids ? data.failed_ids.length : 0;
1342 var message = 'Deleted ' + successCount + ' entries.';
1343 if (failedCount > 0) {
1344 message += ' ' + failedCount + ' entries failed to delete.';
1345 }
1346
1347 $('<div class="notice notice-success is-dismissible"><p>' + message + '</p></div>')
1348 .insertAfter('.mxchat-hero')
1349 .delay(5000)
1350 .fadeOut();
1351
1352 // Clear selection
1353 selectedKnowledgeEntries.clear();
1354 updateKnowledgeSelectionUI();
1355
1356 // Update entry count displays (header and sidebar)
1357 if (successCount > 0) {
1358 var $countSpan = $('#mxchat-entry-count');
1359 if ($countSpan.length) {
1360 var currentText = $countSpan.text();
1361 var match = currentText.match(/\((\d+)\)/);
1362 if (match) {
1363 var newCount = Math.max(0, parseInt(match[1]) - successCount);
1364 updateEntryCount(newCount);
1365 }
1366 }
1367 }
1368
1369 } else {
1370 alert('Error: ' + (response.data || 'Unknown error'));
1371 $('tr.mxchat-row-deleting').removeClass('mxchat-row-deleting');
1372 }
1373 },
1374 error: function(jqXHR, textStatus) {
1375 var message = 'An error occurred while deleting entries.';
1376 if (textStatus === 'timeout') {
1377 message = 'The deletion request timed out. Please refresh the page to check which entries were deleted, then try again for any remaining.';
1378 } else if (textStatus === 'error' && jqXHR.status === 0) {
1379 message = 'Network error: The server took too long to respond. Please refresh and try deleting fewer entries at a time.';
1380 } else if (jqXHR.responseJSON && jqXHR.responseJSON.data) {
1381 message = 'Error: ' + jqXHR.responseJSON.data;
1382 }
1383 alert(message);
1384 $('tr.mxchat-row-deleting').removeClass('mxchat-row-deleting');
1385 },
1386 complete: function() {
1387 $button.prop('disabled', selectedKnowledgeEntries.size === 0);
1388 $button.find('.mxchat-bulk-delete-text').text('Delete Selected');
1389 $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash');
1390 }
1391 });
1392 });
1393
1394 // Clear selection when page changes
1395 $(document).on('click', '.mxchat-page-link', function() {
1396 selectedKnowledgeEntries.clear();
1397 updateKnowledgeSelectionUI();
1398 });
1399
1400 // ========================================
1401 // AJAX PAGINATION FOR KNOWLEDGE BASE ENTRIES
1402 // ========================================
1403
1404 // Handle pagination link clicks
1405 $(document).on('click', '.mxchat-page-link', function(e) {
1406 e.preventDefault();
1407
1408 var $link = $(this);
1409 var page = $link.data('page');
1410 var $paginationWrapper = $('#mxchat-kb-pagination');
1411 var $tbody = $('#mxchat-entries-tbody');
1412
1413 if (!page || $link.hasClass('loading')) {
1414 return;
1415 }
1416
1417 // Show loading state
1418 $link.addClass('loading');
1419 $tbody.css('opacity', '0.5');
1420
1421 // Add loading indicator to pagination
1422 var $loadingIndicator = $('<span class="mxchat-pagination-loading"><span class="dashicons dashicons-update spin"></span></span>');
1423 $paginationWrapper.find('.mxchat-ajax-pagination').append($loadingIndicator);
1424
1425 // Get search and filter values from pagination wrapper
1426 var searchQuery = $paginationWrapper.data('search') || '';
1427 var contentType = $paginationWrapper.data('content-type') || '';
1428
1429 $.ajax({
1430 url: ajaxurl,
1431 type: 'POST',
1432 data: {
1433 action: 'mxchat_paginate_entries',
1434 nonce: mxchatAdmin.entries_nonce,
1435 bot_id: mxchatAdmin.bot_id || 'default',
1436 page: page,
1437 search: searchQuery,
1438 content_type: contentType
1439 },
1440 success: function(response) {
1441 $link.removeClass('loading');
1442 $tbody.css('opacity', '1');
1443 $loadingIndicator.remove();
1444
1445 if (response.success && response.data) {
1446 // Update the table body with new HTML
1447 $tbody.html(response.data.html);
1448
1449 // Update the pagination - find the inner container
1450 if (response.data.pagination_html) {
1451 // Replace the inner pagination div content
1452 $paginationWrapper.html(response.data.pagination_html);
1453 }
1454
1455 // Update data attributes on wrapper (preserve search/filter for next pagination)
1456 $paginationWrapper.attr('data-current-page', response.data.page);
1457 if (response.data.total_pages) {
1458 $paginationWrapper.attr('data-total-pages', response.data.total_pages);
1459 }
1460 // Preserve search and content_type on the wrapper from the inner pagination div
1461 var $innerPagination = $paginationWrapper.find('.mxchat-ajax-pagination');
1462 if ($innerPagination.length) {
1463 $paginationWrapper.attr('data-search', $innerPagination.data('search') || '');
1464 $paginationWrapper.attr('data-content-type', $innerPagination.data('content-type') || '');
1465 }
1466
1467 // Scroll to top of the table smoothly
1468 $('html, body').animate({
1469 scrollTop: $('#knowledge-base').offset().top - 50
1470 }, 300);
1471
1472 // Update URL hash to stay on knowledge-base tab
1473 if (window.history && window.history.replaceState) {
1474 window.history.replaceState(null, '', window.location.pathname + window.location.search + '#knowledge-base');
1475 }
1476
1477 // Show success feedback
1478 showNotification('success', 'Page ' + response.data.page + ' loaded');
1479 } else {
1480 showNotification('error', 'Failed to load page: ' + (response.data?.message || 'Unknown error'));
1481 }
1482 },
1483 error: function(xhr, status, error) {
1484 $link.removeClass('loading');
1485 $tbody.css('opacity', '1');
1486 $loadingIndicator.remove();
1487 showNotification('error', 'Network error while loading page');
1488 }
1489 });
1490 });
1491
1492 // ========================================
1493 // PINECONE REFRESH ENTRIES BUTTON
1494 // ========================================
1495
1496 $('#mxchat-refresh-pinecone-entries').on('click', function() {
1497 var $button = $(this);
1498 var $icon = $button.find('.dashicons');
1499 var $tbody = $('#mxchat-entries-tbody');
1500 var $paginationWrapper = $('#mxchat-kb-pagination');
1501
1502 // Show loading state
1503 $button.prop('disabled', true);
1504 $icon.addClass('spin');
1505 $tbody.css('opacity', '0.5');
1506
1507 $.ajax({
1508 url: ajaxurl,
1509 type: 'POST',
1510 data: {
1511 action: 'mxchat_refresh_pinecone_entries',
1512 nonce: mxchatAdmin.entries_nonce,
1513 bot_id: mxchatAdmin.bot_id || 'default',
1514 page: 1
1515 },
1516 success: function(response) {
1517 $button.prop('disabled', false);
1518 $icon.removeClass('spin');
1519 $tbody.css('opacity', '1');
1520
1521 if (response.success && response.data) {
1522 // Update the table body with new HTML
1523 $tbody.html(response.data.html);
1524
1525 // Update the pagination (same pattern as refreshKnowledgeBaseTable)
1526 if ($paginationWrapper.length) {
1527 if (response.data.pagination_html) {
1528 $paginationWrapper.html(response.data.pagination_html);
1529 $paginationWrapper.attr('style', 'padding: 16px; border-top: 1px solid var(--mxch-card-border); text-align: center;');
1530 } else {
1531 $paginationWrapper.html('');
1532 $paginationWrapper.attr('style', '');
1533 }
1534 }
1535
1536 // Update the count display
1537 if (response.data.total_count !== undefined) {
1538 updateEntryCount(response.data.total_count);
1539 }
1540
1541 // Show success feedback
1542 showNotification('success', 'Entries refreshed successfully!');
1543 } else {
1544 showNotification('error', 'Failed to refresh entries: ' + (response.data?.message || 'Unknown error'));
1545 }
1546 },
1547 error: function(xhr, status, error) {
1548 $button.prop('disabled', false);
1549 $icon.removeClass('spin');
1550 $tbody.css('opacity', '1');
1551 showNotification('error', 'Network error while refreshing entries');
1552 }
1553 });
1554 });
1555
1556 // ========================================
1557 // ACCORDION FUNCTIONALITY
1558 // ========================================
1559
1560 // Handle expand/collapse toggle
1561 $(document).on('click', '.mxchat-expand-toggle', function(e) {
1562 e.preventDefault();
1563 e.stopPropagation();
1564
1565 const $button = $(this);
1566 const $wrapper = $button.closest('.mxchat-accordion-wrapper');
1567 const $preview = $wrapper.find('.mxchat-content-preview');
1568 const $fullContent = $wrapper.find('.mxchat-content-full');
1569
1570 // Toggle expanded state
1571 if ($fullContent.is(':visible')) {
1572 // Collapse
1573 $fullContent.slideUp(300);
1574 $button.removeClass('expanded');
1575 } else {
1576 // Expand
1577 $fullContent.slideDown(300);
1578 $button.addClass('expanded');
1579 }
1580 });
1581
1582 // Click anywhere on preview to toggle (expand or collapse)
1583 $(document).on('click', '.mxchat-content-preview', function(e) {
1584 // Only trigger if not clicking the button directly
1585 if (!$(e.target).closest('.mxchat-expand-toggle').length) {
1586 const $preview = $(this);
1587 const $wrapper = $preview.closest('.mxchat-accordion-wrapper');
1588 const $button = $preview.find('.mxchat-expand-toggle');
1589
1590 // Only trigger if there's a button (meaning content is long enough to expand)
1591 if ($button.length) {
1592 $button.trigger('click');
1593 }
1594 }
1595 });
1596
1597 // ========================================
1598 // CHUNK GROUP TOGGLE FUNCTIONALITY
1599 // ========================================
1600
1601 // Handle chunk group expand/collapse toggle
1602 $(document).on('click', '.mxchat-chunk-toggle', function(e) {
1603 e.preventDefault();
1604 e.stopPropagation();
1605
1606 const $button = $(this);
1607 const groupId = $button.data('group-id');
1608 const $chunkRows = $('.mxchat-chunk-row.' + groupId);
1609
1610 // Toggle expanded state
1611 if ($button.hasClass('expanded')) {
1612 // Collapse
1613 $chunkRows.slideUp(300);
1614 $button.removeClass('expanded');
1615 } else {
1616 // Expand
1617 $chunkRows.slideDown(300);
1618 $button.addClass('expanded');
1619 }
1620 });
1621
1622 // Handle delete button for chunk groups
1623 $(document).on('click', '.delete-button-group', function(e) {
1624 e.preventDefault();
1625 e.stopPropagation();
1626
1627 const $button = $(this);
1628 const sourceUrl = $button.data('source-url');
1629 const chunkCount = $button.data('chunk-count');
1630 const dataSource = $button.data('data-source');
1631 const botId = $button.data('bot-id');
1632 const nonce = $button.data('nonce');
1633
1634 // Confirm deletion
1635 if (!confirm('Are you sure you want to delete all ' + chunkCount + ' chunks for this URL? This action cannot be undone.')) {
1636 return;
1637 }
1638
1639 // Show loading state
1640 $button.prop('disabled', true);
1641 $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update spin');
1642
1643 // Make AJAX request to delete all chunks for this URL
1644 $.ajax({
1645 url: ajaxurl,
1646 type: 'POST',
1647 data: {
1648 action: 'mxchat_delete_chunks_by_url',
1649 source_url: sourceUrl,
1650 data_source: dataSource,
1651 bot_id: botId,
1652 nonce: nonce
1653 },
1654 success: function(response) {
1655 if (response.success) {
1656 // Remove the group header row and all chunk rows
1657 const $headerRow = $button.closest('tr');
1658 const groupId = $headerRow.data('group-id');
1659 $('.mxchat-chunk-row.' + groupId).fadeOut(300, function() {
1660 $(this).remove();
1661 });
1662 $headerRow.fadeOut(300, function() {
1663 $(this).remove();
1664 });
1665 } else {
1666 var errorMsg = response.data || 'Unknown error';
1667 if (typeof response.data === 'object' && response.data.message) {
1668 errorMsg = response.data.message;
1669 }
1670 alert('Error deleting chunks: ' + errorMsg);
1671 $button.prop('disabled', false);
1672 $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash');
1673 }
1674 },
1675 error: function(xhr, status, error) {
1676 console.error('AJAX error:', xhr.responseText);
1677 alert('Error deleting chunks: ' + (error || 'Server error'));
1678 $button.prop('disabled', false);
1679 $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash');
1680 }
1681 });
1682 });
1683
1684 // ========================================
1685 // REAL-TIME KNOWLEDGE ENTRIES UPDATE
1686 // ========================================
1687
1688 let lastEntryId = 0;
1689 let entriesPollingInterval = null;
1690 let isEntriesPolling = false;
1691
1692 // Initialize: get the highest ID from the current table
1693 function initializeLastEntryId() {
1694 const $tbody = $('#mxchat-entries-tbody');
1695 if ($tbody.length === 0) return;
1696
1697 // Get the highest ID from the table
1698 $tbody.find('tr').each(function() {
1699 const idText = $(this).find('td:first').text().trim();
1700 const id = parseInt(idText);
1701 if (!isNaN(id) && id > lastEntryId) {
1702 lastEntryId = id;
1703 }
1704 });
1705 }
1706
1707 // Poll for new entries during processing
1708 function startEntriesPolling() {
1709 if (entriesPollingInterval) return; // Already polling
1710
1711 isEntriesPolling = true;
1712
1713 // Fetch immediately, then start interval
1714 fetchNewEntries();
1715
1716 entriesPollingInterval = setInterval(function() {
1717 fetchNewEntries();
1718 }, 3000); // Poll every 3 seconds
1719 }
1720
1721 function stopEntriesPolling() {
1722 if (entriesPollingInterval) {
1723 clearInterval(entriesPollingInterval);
1724 entriesPollingInterval = null;
1725 }
1726 isEntriesPolling = false;
1727 }
1728
1729 // Track Pinecone count for change detection
1730 var lastPineconeCount = 0;
1731 var pineconeRefreshPending = false;
1732
1733 // Fetch new entries from server
1734 function fetchNewEntries() {
1735 if (!mxchatAdmin.entries_nonce) {
1736 return;
1737 }
1738
1739 $.ajax({
1740 url: ajaxurl,
1741 type: 'POST',
1742 data: {
1743 action: 'mxchat_get_recent_entries',
1744 nonce: mxchatAdmin.entries_nonce,
1745 last_id: lastEntryId,
1746 bot_id: mxchatAdmin.bot_id || 'default',
1747 limit: 20
1748 },
1749 success: function(response) {
1750 if (response.success && response.data) {
1751 // Update count
1752 if (response.data.total_count !== undefined) {
1753 updateEntryCount(response.data.total_count);
1754 }
1755
1756 // Handle Pinecone data source differently
1757 if (response.data.data_source === 'pinecone') {
1758 var newCount = response.data.total_count || 0;
1759
1760 // If count changed and we haven't scheduled a refresh yet
1761 if (newCount !== lastPineconeCount && !pineconeRefreshPending) {
1762 lastPineconeCount = newCount;
1763
1764 // Schedule a table refresh after processing completes
1765 // Show a "refresh to see entries" message
1766 var $tbody = $('#mxchat-entries-tbody');
1767 var $refreshNotice = $tbody.find('.mxchat-pinecone-refresh-notice');
1768
1769 if ($refreshNotice.length === 0 && newCount > 0) {
1770 var noticeHtml = '<tr class="mxchat-pinecone-refresh-notice">' +
1771 '<td colspan="4" style="padding: 20px; text-align: center; background: #f0f7ff; border-bottom: 1px solid var(--mxch-card-border);">' +
1772 '<span class="dashicons dashicons-update" style="color: #7873f5; margin-right: 8px;"></span>' +
1773 '<strong>' + newCount + ' entries in Pinecone.</strong> ' +
1774 '<a href="#" class="mxchat-refresh-table-link" style="color: #7873f5; text-decoration: underline;">Refresh to see new entries</a>' +
1775 '</td></tr>';
1776 $tbody.prepend(noticeHtml);
1777
1778 // Handle refresh link click
1779 $tbody.find('.mxchat-refresh-table-link').on('click', function(e) {
1780 e.preventDefault();
1781 location.reload();
1782 });
1783 } else if ($refreshNotice.length > 0) {
1784 // Update the count in existing notice
1785 $refreshNotice.find('strong').text(newCount + ' entries in Pinecone.');
1786 }
1787 }
1788 } else {
1789 // WordPress DB - Track new entries and show refresh notice
1790 // Don't add rows individually during processing as they need to be grouped by source_url
1791 if (response.data.entries && response.data.entries.length > 0) {
1792 // Update last ID to track progress
1793 if (response.data.max_id > lastEntryId) {
1794 lastEntryId = response.data.max_id;
1795 }
1796
1797 // Show/update refresh notice (similar to Pinecone handling)
1798 var $tbody = $('#mxchat-entries-tbody');
1799 var $refreshNotice = $tbody.find('.mxchat-wordpress-refresh-notice');
1800 var newCount = response.data.total_count || 0;
1801
1802 if ($refreshNotice.length === 0 && newCount > 0) {
1803 var noticeHtml = '<tr class="mxchat-wordpress-refresh-notice mxchat-new-entry">' +
1804 '<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);">' +
1805 '<span class="dashicons dashicons-update spin" style="color: #7873f5; margin-right: 8px;"></span>' +
1806 '<strong style="color: var(--mxch-text-primary);">Processing... <span class="mxchat-processing-count">' + newCount + '</span> entries</strong>' +
1807 '</td></tr>';
1808 $tbody.prepend(noticeHtml);
1809 } else if ($refreshNotice.length > 0) {
1810 // Update the count in existing notice
1811 $refreshNotice.find('.mxchat-processing-count').text(newCount);
1812 }
1813 } else {
1814 // Update last ID even if no new entries
1815 if (response.data.max_id > lastEntryId) {
1816 lastEntryId = response.data.max_id;
1817 }
1818 }
1819 }
1820 }
1821 },
1822 error: function(xhr, status, error) {
1823 console.error('MxChat: Error fetching new entries:', error, xhr.responseText);
1824 }
1825 });
1826 }
1827
1828 // Update the entry count display
1829 function updateEntryCount(count) {
1830 // Update main table count
1831 const $countSpan = $('#mxchat-entry-count');
1832 if ($countSpan.length) {
1833 $countSpan.text('(' + count + ')');
1834
1835 // Flash animation to indicate update
1836 $countSpan.addClass('mxchat-count-updated');
1837 setTimeout(function() {
1838 $countSpan.removeClass('mxchat-count-updated');
1839 }, 1000);
1840 }
1841
1842 // Update sidebar badge count
1843 const $sidebarCount = $('#mxchat-sidebar-count');
1844 if ($sidebarCount.length) {
1845 $sidebarCount.text(count);
1846
1847 // Flash animation for sidebar too
1848 $sidebarCount.addClass('mxchat-count-updated');
1849 setTimeout(function() {
1850 $sidebarCount.removeClass('mxchat-count-updated');
1851 }, 1000);
1852 }
1853 }
1854
1855 // Refresh the knowledge base table via AJAX pagination
1856 // This ensures entries are properly grouped by source_url
1857 function refreshKnowledgeBaseTable() {
1858 var $paginationWrapper = $('#mxchat-kb-pagination');
1859 var $tbody = $('#mxchat-entries-tbody');
1860
1861 if ($tbody.length === 0) {
1862 return;
1863 }
1864
1865 // Remove any processing notice
1866 $tbody.find('.mxchat-wordpress-refresh-notice, .mxchat-pinecone-refresh-notice').remove();
1867
1868 // Show loading state
1869 $tbody.css('opacity', '0.5');
1870
1871 $.ajax({
1872 url: ajaxurl,
1873 type: 'POST',
1874 data: {
1875 action: 'mxchat_paginate_entries',
1876 nonce: mxchatAdmin.entries_nonce,
1877 bot_id: mxchatAdmin.bot_id || 'default',
1878 page: 1 // Always go to first page to see newest entries
1879 },
1880 success: function(response) {
1881 $tbody.css('opacity', '1');
1882
1883 if (response.success && response.data) {
1884 // Update the table body with properly grouped HTML
1885 $tbody.html(response.data.html);
1886
1887 // Update the pagination
1888 if ($paginationWrapper.length) {
1889 if (response.data.pagination_html) {
1890 // Add pagination content and styling
1891 $paginationWrapper.html(response.data.pagination_html);
1892 $paginationWrapper.attr('style', 'padding: 16px; border-top: 1px solid var(--mxch-card-border); text-align: center;');
1893 } else {
1894 // No pagination needed - clear and hide
1895 $paginationWrapper.html('');
1896 $paginationWrapper.attr('style', '');
1897 }
1898 }
1899
1900 // Update count display
1901 if (response.data.total_count !== undefined) {
1902 updateEntryCount(response.data.total_count);
1903 }
1904 }
1905 },
1906 error: function(xhr, status, error) {
1907 $tbody.css('opacity', '1');
1908 console.error('MxChat: Error refreshing table:', error);
1909 }
1910 });
1911 }
1912
1913 // Expose refreshKnowledgeBaseTable for external access (content-selector.js)
1914 window.refreshKnowledgeBaseTable = refreshKnowledgeBaseTable;
1915
1916 // Add new entries to the table (kept for backwards compatibility but not used during processing)
1917 function addNewEntriesToTable(entries) {
1918 const $tbody = $('#mxchat-entries-tbody');
1919 if ($tbody.length === 0) return;
1920
1921 // Remove "no entries" message if present
1922 const $noEntries = $tbody.find('td[colspan="4"]').closest('tr');
1923 if ($noEntries.length) {
1924 $noEntries.remove();
1925 }
1926
1927 // Add entries in reverse order (oldest first, so newest ends up at top)
1928 entries.reverse().forEach(function(entry) {
1929 // Check if entry already exists
1930 if ($tbody.find('tr[data-entry-id="' + entry.id + '"]').length > 0) {
1931 return;
1932 }
1933
1934 const sourceHtml = entry.has_link
1935 ? '<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>'
1936 : '<span style="color: var(--mxch-text-muted);">Manual</span>';
1937
1938 const deleteUrl = mxchatAdmin.admin_url + 'admin-post.php?action=mxchat_delete_prompt&id=' + entry.id + '&_wpnonce=' + entry.delete_nonce;
1939
1940 // Check if content needs expand button (content longer than preview)
1941 const needsExpand = entry.content_length > entry.preview_length;
1942
1943 // Build accordion-style content cell (matching initial page load structure)
1944 let contentHtml = '<div class="mxchat-accordion-wrapper">' +
1945 '<div class="mxchat-content-preview">' +
1946 '<span class="preview-text">' + entry.preview + '</span>';
1947
1948 if (needsExpand) {
1949 contentHtml += '<button class="mxchat-expand-toggle" type="button">' +
1950 '<span class="dashicons dashicons-arrow-down-alt2"></span>' +
1951 '</button>';
1952 }
1953
1954 contentHtml += '</div>' +
1955 '<div class="mxchat-content-full" style="display: none;">' +
1956 '<div class="content-view">' + entry.full_content + '</div>' +
1957 '</div>' +
1958 '</div>';
1959
1960 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;">' +
1961 '<td style="padding: 12px 16px; font-size: 13px;">' + entry.id + '</td>' +
1962 '<td class="mxchat-content-cell" style="padding: 12px 16px; font-size: 13px;">' + contentHtml + '</td>' +
1963 '<td class="mxchat-url-cell" style="padding: 12px 16px; font-size: 13px;">' + sourceHtml + '</td>' +
1964 '<td style="padding: 12px 16px;">' +
1965 '<a href="' + deleteUrl + '" class="mxch-btn mxch-btn-ghost mxch-btn-sm" style="color: var(--mxch-error);" onclick="return confirm(\'Delete this entry?\');">' +
1966 '<span class="dashicons dashicons-trash" style="font-size: 14px;"></span>' +
1967 '</a>' +
1968 '</td>' +
1969 '</tr>');
1970
1971 // Add highlight class and prepend to tbody
1972 $row.addClass('mxchat-new-entry');
1973 $tbody.prepend($row);
1974 $row.slideDown(300);
1975
1976 // Remove highlight after animation
1977 setTimeout(function() {
1978 $row.removeClass('mxchat-new-entry');
1979 }, 2000);
1980 });
1981 }
1982
1983 // Initialize entry ID tracking
1984 initializeLastEntryId();
1985
1986 // Check on page load if there's already active processing (e.g., page was refreshed during processing)
1987 if ($('.mxchat-status-card').length > 0) {
1988 // Check if processing is active via AJAX
1989 $.ajax({
1990 url: ajaxurl,
1991 type: 'POST',
1992 data: {
1993 action: 'mxchat_get_status_updates',
1994 nonce: mxchatAdmin.status_nonce
1995 },
1996 success: function(response) {
1997 if (response.is_processing) {
1998 startEntriesPolling();
1999 }
2000 }
2001 });
2002 }
2003
2004 // Expose functions for external access
2005 window.mxchatEntriesPolling = {
2006 start: startEntriesPolling,
2007 stop: stopEntriesPolling,
2008 fetch: fetchNewEntries
2009 };
2010
2011 // ========================================
2012 // SITEMAP DETECTION FUNCTIONALITY
2013 // ========================================
2014
2015 let sitemapDetectionInitialized = false;
2016
2017 /**
2018 * Initialize sitemap detection when Sitemap Import is clicked
2019 */
2020 function initSitemapDetection() {
2021 if (sitemapDetectionInitialized) return;
2022
2023 const loadingEl = document.getElementById('mxchat-sitemaps-loading');
2024 const detectedEl = document.getElementById('mxchat-detected-sitemaps');
2025 const noSitemapsEl = document.getElementById('mxchat-no-sitemaps');
2026 const listEl = document.getElementById('mxchat-sitemaps-list');
2027 const refreshBtn = document.getElementById('mxchat-refresh-sitemaps');
2028 const nonceEl = document.getElementById('mxchat-detect-sitemaps-nonce');
2029
2030 if (!loadingEl || !nonceEl) return;
2031
2032 sitemapDetectionInitialized = true;
2033
2034 function detectSitemaps() {
2035 // Show loading
2036 loadingEl.style.display = 'block';
2037 if (detectedEl) detectedEl.style.display = 'none';
2038 if (noSitemapsEl) noSitemapsEl.style.display = 'none';
2039
2040 // Disable refresh button
2041 if (refreshBtn) {
2042 refreshBtn.disabled = true;
2043 var refreshIcon = refreshBtn.querySelector('.dashicons');
2044 if (refreshIcon) refreshIcon.classList.add('spin');
2045 }
2046
2047 $.ajax({
2048 url: ajaxurl,
2049 type: 'POST',
2050 data: {
2051 action: 'mxchat_detect_sitemaps',
2052 nonce: nonceEl.value
2053 },
2054 timeout: 60000, // 60 second timeout for slow servers
2055 success: function(data) {
2056 loadingEl.style.display = 'none';
2057
2058 // Re-enable refresh button
2059 if (refreshBtn) {
2060 refreshBtn.disabled = false;
2061 var refreshIcon = refreshBtn.querySelector('.dashicons');
2062 if (refreshIcon) refreshIcon.classList.remove('spin');
2063 }
2064
2065 if (data.success && data.data && data.data.sitemaps && data.data.sitemaps.length > 0) {
2066 renderSitemaps(data.data.sitemaps);
2067 if (detectedEl) detectedEl.style.display = 'block';
2068 } else {
2069 if (noSitemapsEl) {
2070 noSitemapsEl.style.display = 'block';
2071 $(noSitemapsEl).data('was-shown', true);
2072 }
2073 }
2074 },
2075 error: function(xhr, status, error) {
2076 loadingEl.style.display = 'none';
2077 if (noSitemapsEl) {
2078 noSitemapsEl.style.display = 'block';
2079 $(noSitemapsEl).data('was-shown', true);
2080 }
2081 if (refreshBtn) {
2082 refreshBtn.disabled = false;
2083 var refreshIcon = refreshBtn.querySelector('.dashicons');
2084 if (refreshIcon) refreshIcon.classList.remove('spin');
2085 }
2086 }
2087 });
2088 }
2089
2090 function renderSitemaps(sitemaps) {
2091 if (!listEl) return;
2092
2093 var html = '';
2094 var botIdEl = document.getElementById('mxchat-sitemap-bot-id');
2095 var botId = botIdEl ? botIdEl.value : '';
2096
2097 sitemaps.forEach(function(sitemap) {
2098 if (sitemap.type === 'index' && sitemap.sub_sitemaps && sitemap.sub_sitemaps.length > 0) {
2099 // Render sitemap index with sub-sitemaps
2100 html += '<div class="mxchat-sitemap-group">';
2101 html += '<div class="mxchat-sitemap-group-header">';
2102 html += '<div style="display: flex; align-items: center; gap: 10px;">';
2103 html += '<span class="dashicons dashicons-arrow-right-alt2" style="transition: transform 0.2s;"></span>';
2104 html += '<span class="dashicons dashicons-list-view" style="color: #7873f5;"></span>';
2105 html += '<div>';
2106 html += '<strong style="font-size: 13px;">Sitemap Index</strong>';
2107 html += '<span style="color: #666; font-size: 12px; margin-left: 8px;">' + sitemap.source + '</span>';
2108 html += '</div>';
2109 html += '</div>';
2110 html += '<span style="background: rgba(120, 115, 245, 0.1); color: #7873f5; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600;">';
2111 html += sitemap.sub_sitemaps.length + ' sitemaps';
2112 html += '</span>';
2113 html += '</div>';
2114 html += '<div class="mxchat-sitemap-sub-list">';
2115 sitemap.sub_sitemaps.forEach(function(sub) {
2116 html += renderSitemapRow(sub, botId, true);
2117 });
2118 html += '</div>';
2119 html += '</div>';
2120 } else if (sitemap.type !== 'index') {
2121 // Render standalone sitemap
2122 html += renderSitemapRow(sitemap, botId, false);
2123 }
2124 });
2125
2126 listEl.innerHTML = html;
2127
2128 // Add click handlers for group toggles
2129 $(listEl).find('.mxchat-sitemap-group-header').on('click', function() {
2130 var $group = $(this).parent();
2131 var $subList = $group.find('.mxchat-sitemap-sub-list');
2132 var $arrow = $(this).find('.dashicons-arrow-right-alt2');
2133
2134 $group.toggleClass('expanded');
2135
2136 if ($group.hasClass('expanded')) {
2137 $subList.slideDown(200);
2138 $arrow.css('transform', 'rotate(90deg)');
2139 } else {
2140 $subList.slideUp(200);
2141 $arrow.css('transform', 'rotate(0deg)');
2142 }
2143 });
2144
2145 // Add click handlers for process buttons
2146 $(listEl).find('.mxchat-process-sitemap-btn').on('click', function() {
2147 var url = $(this).data('url');
2148 var type = $(this).data('sitemap-type');
2149 processSitemap(url, type, this);
2150 });
2151 }
2152
2153 function renderSitemapRow(sitemap, botId, isSubItem) {
2154 var typeLabels = {
2155 'content': 'Content',
2156 'taxonomy': 'Taxonomy',
2157 'author': 'Authors'
2158 };
2159 var typeLabel = typeLabels[sitemap.type] || sitemap.type;
2160 var displayName = sitemap.name || sitemap.url.split('/').pop();
2161 var urlCount = sitemap.url_count || 0;
2162 var paddingLeft = isSubItem ? '40px' : '16px';
2163
2164 var html = '<div class="mxchat-sitemap-row" style="padding-left: ' + paddingLeft + ';">';
2165 html += '<div style="display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0;">';
2166 html += '<span class="dashicons dashicons-media-text" style="color: #666; flex-shrink: 0;"></span>';
2167 html += '<div style="min-width: 0; flex: 1;">';
2168 html += '<div style="font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="' + sitemap.url + '">';
2169 html += displayName;
2170 html += '</div>';
2171 html += '<div style="font-size: 11px; color: #666;">';
2172 html += '<span style="background: #f0f0f0; padding: 1px 6px; border-radius: 3px; margin-right: 8px;">' + typeLabel + '</span>';
2173 if (urlCount > 0) {
2174 html += urlCount + ' URLs';
2175 }
2176 html += '</div>';
2177 html += '</div>';
2178 html += '</div>';
2179 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 + '">';
2180 html += '<span class="dashicons dashicons-download" style="font-size: 14px; margin-top: 3px;"></span> Process';
2181 html += '</button>';
2182 html += '</div>';
2183
2184 return html;
2185 }
2186
2187 function processSitemap(url, type, buttonEl) {
2188 var $button = $(buttonEl);
2189 var originalHtml = $button.html();
2190
2191 // Update button to show loading
2192 $button.prop('disabled', true);
2193 $button.html('<span class="dashicons dashicons-update spin" style="font-size: 14px; margin-top: 3px;"></span> Processing...');
2194
2195 // Fill in the sitemap URL form and submit
2196 var $form = $('#mxchat-url-form');
2197 var $urlInput = $('#sitemap_url');
2198 var $importType = $('#import_type');
2199
2200 if ($urlInput.length) {
2201 $urlInput.val(url);
2202 }
2203
2204 if ($importType.length) {
2205 $importType.val('sitemap');
2206 }
2207
2208 // Add a hidden submit field if not present (required by the PHP handler)
2209 if ($form.find('input[name="submit_sitemap"]').length === 0) {
2210 $form.append('<input type="hidden" name="submit_sitemap" value="1">');
2211 }
2212
2213 // Submit the form
2214 $form.submit();
2215 }
2216
2217
2218 // Refresh button handler
2219 if (refreshBtn) {
2220 $(refreshBtn).on('click', detectSitemaps);
2221 }
2222
2223 // Start detection
2224 detectSitemaps();
2225 }
2226
2227 // Expose initSitemapDetection globally so it can be called from the import options handler
2228 window.mxchatInitSitemapDetection = initSitemapDetection;
2229
2230 // ========================================
2231 // ADMIN NOTICE DISMISS FUNCTIONALITY
2232 // ========================================
2233
2234 // Initialize dismissible notices - add dismiss button if missing
2235 function initDismissibleNotices() {
2236 $('.notice.is-dismissible').each(function() {
2237 var $notice = $(this);
2238
2239 // Skip if already has a dismiss button
2240 if ($notice.find('.notice-dismiss').length > 0) {
2241 return;
2242 }
2243
2244 // Add dismiss button
2245 var $dismissButton = $('<button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss this notice.</span></button>');
2246 $notice.append($dismissButton);
2247 });
2248 }
2249
2250 // Initialize on page load
2251 initDismissibleNotices();
2252
2253 // Use event delegation for dismiss button clicks - works for existing and dynamically added notices
2254 $(document).on('click', '.notice.is-dismissible .notice-dismiss', function(e) {
2255 e.preventDefault();
2256 e.stopPropagation();
2257
2258 var $notice = $(this).closest('.notice');
2259 $notice.fadeTo(100, 0, function() {
2260 $notice.slideUp(100, function() {
2261 $notice.remove();
2262 });
2263 });
2264 });
2265
2266 // Re-initialize when new notices are added dynamically (e.g., via AJAX)
2267 $(document).on('DOMNodeInserted', function(e) {
2268 if ($(e.target).hasClass('notice') && $(e.target).hasClass('is-dismissible')) {
2269 setTimeout(initDismissibleNotices, 10);
2270 }
2271 });
2272 });