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

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

2,263 lines 95.0 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 data: {
1305 action: 'mxchat_bulk_delete_knowledge',
1306 entries: entries,
1307 bot_id: botId,
1308 nonce: nonce
1309 },
1310 success: function(response) {
1311 if (response.success) {
1312 var data = response.data;
1313
1314 // Remove successful rows
1315 if (data.success_ids && data.success_ids.length > 0) {
1316 data.success_ids.forEach(function(id) {
1317 var $row = $('#prompt-' + id);
1318 // Also remove child chunk rows if it's a group
1319 var groupId = $row.data('group-id');
1320 if (groupId) {
1321 $('.mxchat-chunk-row.' + groupId).fadeOut(300, function() {
1322 $(this).remove();
1323 });
1324 }
1325 $row.fadeOut(300, function() {
1326 $(this).remove();
1327 });
1328 });
1329 }
1330
1331 // Handle failed entries
1332 if (data.failed_ids && data.failed_ids.length > 0) {
1333 data.failed_ids.forEach(function(id) {
1334 $('#prompt-' + id).removeClass('mxchat-row-deleting').addClass('mxchat-row-error');
1335 });
1336 }
1337
1338 // Show result message
1339 var successCount = data.success_ids ? data.success_ids.length : 0;
1340 var failedCount = data.failed_ids ? data.failed_ids.length : 0;
1341 var message = 'Deleted ' + successCount + ' entries.';
1342 if (failedCount > 0) {
1343 message += ' ' + failedCount + ' entries failed to delete.';
1344 }
1345
1346 $('<div class="notice notice-success is-dismissible"><p>' + message + '</p></div>')
1347 .insertAfter('.mxchat-hero')
1348 .delay(5000)
1349 .fadeOut();
1350
1351 // Clear selection
1352 selectedKnowledgeEntries.clear();
1353 updateKnowledgeSelectionUI();
1354
1355 // Update entry count displays (header and sidebar)
1356 if (successCount > 0) {
1357 var $countSpan = $('#mxchat-entry-count');
1358 if ($countSpan.length) {
1359 var currentText = $countSpan.text();
1360 var match = currentText.match(/\((\d+)\)/);
1361 if (match) {
1362 var newCount = Math.max(0, parseInt(match[1]) - successCount);
1363 updateEntryCount(newCount);
1364 }
1365 }
1366 }
1367
1368 } else {
1369 alert('Error: ' + (response.data || 'Unknown error'));
1370 $('tr.mxchat-row-deleting').removeClass('mxchat-row-deleting');
1371 }
1372 },
1373 error: function() {
1374 alert('Network error occurred');
1375 $('tr.mxchat-row-deleting').removeClass('mxchat-row-deleting');
1376 },
1377 complete: function() {
1378 $button.prop('disabled', selectedKnowledgeEntries.size === 0);
1379 $button.find('.mxchat-bulk-delete-text').text('Delete Selected');
1380 $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash');
1381 }
1382 });
1383 });
1384
1385 // Clear selection when page changes
1386 $(document).on('click', '.mxchat-page-link', function() {
1387 selectedKnowledgeEntries.clear();
1388 updateKnowledgeSelectionUI();
1389 });
1390
1391 // ========================================
1392 // AJAX PAGINATION FOR KNOWLEDGE BASE ENTRIES
1393 // ========================================
1394
1395 // Handle pagination link clicks
1396 $(document).on('click', '.mxchat-page-link', function(e) {
1397 e.preventDefault();
1398
1399 var $link = $(this);
1400 var page = $link.data('page');
1401 var $paginationWrapper = $('#mxchat-kb-pagination');
1402 var $tbody = $('#mxchat-entries-tbody');
1403
1404 if (!page || $link.hasClass('loading')) {
1405 return;
1406 }
1407
1408 // Show loading state
1409 $link.addClass('loading');
1410 $tbody.css('opacity', '0.5');
1411
1412 // Add loading indicator to pagination
1413 var $loadingIndicator = $('<span class="mxchat-pagination-loading"><span class="dashicons dashicons-update spin"></span></span>');
1414 $paginationWrapper.find('.mxchat-ajax-pagination').append($loadingIndicator);
1415
1416 // Get search and filter values from pagination wrapper
1417 var searchQuery = $paginationWrapper.data('search') || '';
1418 var contentType = $paginationWrapper.data('content-type') || '';
1419
1420 $.ajax({
1421 url: ajaxurl,
1422 type: 'POST',
1423 data: {
1424 action: 'mxchat_paginate_entries',
1425 nonce: mxchatAdmin.entries_nonce,
1426 bot_id: mxchatAdmin.bot_id || 'default',
1427 page: page,
1428 search: searchQuery,
1429 content_type: contentType
1430 },
1431 success: function(response) {
1432 $link.removeClass('loading');
1433 $tbody.css('opacity', '1');
1434 $loadingIndicator.remove();
1435
1436 if (response.success && response.data) {
1437 // Update the table body with new HTML
1438 $tbody.html(response.data.html);
1439
1440 // Update the pagination - find the inner container
1441 if (response.data.pagination_html) {
1442 // Replace the inner pagination div content
1443 $paginationWrapper.html(response.data.pagination_html);
1444 }
1445
1446 // Update data attributes on wrapper (preserve search/filter for next pagination)
1447 $paginationWrapper.attr('data-current-page', response.data.page);
1448 if (response.data.total_pages) {
1449 $paginationWrapper.attr('data-total-pages', response.data.total_pages);
1450 }
1451 // Preserve search and content_type on the wrapper from the inner pagination div
1452 var $innerPagination = $paginationWrapper.find('.mxchat-ajax-pagination');
1453 if ($innerPagination.length) {
1454 $paginationWrapper.attr('data-search', $innerPagination.data('search') || '');
1455 $paginationWrapper.attr('data-content-type', $innerPagination.data('content-type') || '');
1456 }
1457
1458 // Scroll to top of the table smoothly
1459 $('html, body').animate({
1460 scrollTop: $('#knowledge-base').offset().top - 50
1461 }, 300);
1462
1463 // Update URL hash to stay on knowledge-base tab
1464 if (window.history && window.history.replaceState) {
1465 window.history.replaceState(null, '', window.location.pathname + window.location.search + '#knowledge-base');
1466 }
1467
1468 // Show success feedback
1469 showNotification('success', 'Page ' + response.data.page + ' loaded');
1470 } else {
1471 showNotification('error', 'Failed to load page: ' + (response.data?.message || 'Unknown error'));
1472 }
1473 },
1474 error: function(xhr, status, error) {
1475 $link.removeClass('loading');
1476 $tbody.css('opacity', '1');
1477 $loadingIndicator.remove();
1478 showNotification('error', 'Network error while loading page');
1479 }
1480 });
1481 });
1482
1483 // ========================================
1484 // PINECONE REFRESH ENTRIES BUTTON
1485 // ========================================
1486
1487 $('#mxchat-refresh-pinecone-entries').on('click', function() {
1488 var $button = $(this);
1489 var $icon = $button.find('.dashicons');
1490 var $tbody = $('#mxchat-entries-tbody');
1491 var $paginationWrapper = $('#mxchat-kb-pagination');
1492
1493 // Show loading state
1494 $button.prop('disabled', true);
1495 $icon.addClass('spin');
1496 $tbody.css('opacity', '0.5');
1497
1498 $.ajax({
1499 url: ajaxurl,
1500 type: 'POST',
1501 data: {
1502 action: 'mxchat_refresh_pinecone_entries',
1503 nonce: mxchatAdmin.entries_nonce,
1504 bot_id: mxchatAdmin.bot_id || 'default',
1505 page: 1
1506 },
1507 success: function(response) {
1508 $button.prop('disabled', false);
1509 $icon.removeClass('spin');
1510 $tbody.css('opacity', '1');
1511
1512 if (response.success && response.data) {
1513 // Update the table body with new HTML
1514 $tbody.html(response.data.html);
1515
1516 // Update the pagination (same pattern as refreshKnowledgeBaseTable)
1517 if ($paginationWrapper.length) {
1518 if (response.data.pagination_html) {
1519 $paginationWrapper.html(response.data.pagination_html);
1520 $paginationWrapper.attr('style', 'padding: 16px; border-top: 1px solid var(--mxch-card-border); text-align: center;');
1521 } else {
1522 $paginationWrapper.html('');
1523 $paginationWrapper.attr('style', '');
1524 }
1525 }
1526
1527 // Update the count display
1528 if (response.data.total_count !== undefined) {
1529 updateEntryCount(response.data.total_count);
1530 }
1531
1532 // Show success feedback
1533 showNotification('success', 'Entries refreshed successfully!');
1534 } else {
1535 showNotification('error', 'Failed to refresh entries: ' + (response.data?.message || 'Unknown error'));
1536 }
1537 },
1538 error: function(xhr, status, error) {
1539 $button.prop('disabled', false);
1540 $icon.removeClass('spin');
1541 $tbody.css('opacity', '1');
1542 showNotification('error', 'Network error while refreshing entries');
1543 }
1544 });
1545 });
1546
1547 // ========================================
1548 // ACCORDION FUNCTIONALITY
1549 // ========================================
1550
1551 // Handle expand/collapse toggle
1552 $(document).on('click', '.mxchat-expand-toggle', function(e) {
1553 e.preventDefault();
1554 e.stopPropagation();
1555
1556 const $button = $(this);
1557 const $wrapper = $button.closest('.mxchat-accordion-wrapper');
1558 const $preview = $wrapper.find('.mxchat-content-preview');
1559 const $fullContent = $wrapper.find('.mxchat-content-full');
1560
1561 // Toggle expanded state
1562 if ($fullContent.is(':visible')) {
1563 // Collapse
1564 $fullContent.slideUp(300);
1565 $button.removeClass('expanded');
1566 } else {
1567 // Expand
1568 $fullContent.slideDown(300);
1569 $button.addClass('expanded');
1570 }
1571 });
1572
1573 // Click anywhere on preview to toggle (expand or collapse)
1574 $(document).on('click', '.mxchat-content-preview', function(e) {
1575 // Only trigger if not clicking the button directly
1576 if (!$(e.target).closest('.mxchat-expand-toggle').length) {
1577 const $preview = $(this);
1578 const $wrapper = $preview.closest('.mxchat-accordion-wrapper');
1579 const $button = $preview.find('.mxchat-expand-toggle');
1580
1581 // Only trigger if there's a button (meaning content is long enough to expand)
1582 if ($button.length) {
1583 $button.trigger('click');
1584 }
1585 }
1586 });
1587
1588 // ========================================
1589 // CHUNK GROUP TOGGLE FUNCTIONALITY
1590 // ========================================
1591
1592 // Handle chunk group expand/collapse toggle
1593 $(document).on('click', '.mxchat-chunk-toggle', function(e) {
1594 e.preventDefault();
1595 e.stopPropagation();
1596
1597 const $button = $(this);
1598 const groupId = $button.data('group-id');
1599 const $chunkRows = $('.mxchat-chunk-row.' + groupId);
1600
1601 // Toggle expanded state
1602 if ($button.hasClass('expanded')) {
1603 // Collapse
1604 $chunkRows.slideUp(300);
1605 $button.removeClass('expanded');
1606 } else {
1607 // Expand
1608 $chunkRows.slideDown(300);
1609 $button.addClass('expanded');
1610 }
1611 });
1612
1613 // Handle delete button for chunk groups
1614 $(document).on('click', '.delete-button-group', function(e) {
1615 e.preventDefault();
1616 e.stopPropagation();
1617
1618 const $button = $(this);
1619 const sourceUrl = $button.data('source-url');
1620 const chunkCount = $button.data('chunk-count');
1621 const dataSource = $button.data('data-source');
1622 const botId = $button.data('bot-id');
1623 const nonce = $button.data('nonce');
1624
1625 // Confirm deletion
1626 if (!confirm('Are you sure you want to delete all ' + chunkCount + ' chunks for this URL? This action cannot be undone.')) {
1627 return;
1628 }
1629
1630 // Show loading state
1631 $button.prop('disabled', true);
1632 $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update spin');
1633
1634 // Make AJAX request to delete all chunks for this URL
1635 $.ajax({
1636 url: ajaxurl,
1637 type: 'POST',
1638 data: {
1639 action: 'mxchat_delete_chunks_by_url',
1640 source_url: sourceUrl,
1641 data_source: dataSource,
1642 bot_id: botId,
1643 nonce: nonce
1644 },
1645 success: function(response) {
1646 if (response.success) {
1647 // Remove the group header row and all chunk rows
1648 const $headerRow = $button.closest('tr');
1649 const groupId = $headerRow.data('group-id');
1650 $('.mxchat-chunk-row.' + groupId).fadeOut(300, function() {
1651 $(this).remove();
1652 });
1653 $headerRow.fadeOut(300, function() {
1654 $(this).remove();
1655 });
1656 } else {
1657 var errorMsg = response.data || 'Unknown error';
1658 if (typeof response.data === 'object' && response.data.message) {
1659 errorMsg = response.data.message;
1660 }
1661 alert('Error deleting chunks: ' + errorMsg);
1662 $button.prop('disabled', false);
1663 $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash');
1664 }
1665 },
1666 error: function(xhr, status, error) {
1667 console.error('AJAX error:', xhr.responseText);
1668 alert('Error deleting chunks: ' + (error || 'Server error'));
1669 $button.prop('disabled', false);
1670 $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash');
1671 }
1672 });
1673 });
1674
1675 // ========================================
1676 // REAL-TIME KNOWLEDGE ENTRIES UPDATE
1677 // ========================================
1678
1679 let lastEntryId = 0;
1680 let entriesPollingInterval = null;
1681 let isEntriesPolling = false;
1682
1683 // Initialize: get the highest ID from the current table
1684 function initializeLastEntryId() {
1685 const $tbody = $('#mxchat-entries-tbody');
1686 if ($tbody.length === 0) return;
1687
1688 // Get the highest ID from the table
1689 $tbody.find('tr').each(function() {
1690 const idText = $(this).find('td:first').text().trim();
1691 const id = parseInt(idText);
1692 if (!isNaN(id) && id > lastEntryId) {
1693 lastEntryId = id;
1694 }
1695 });
1696 }
1697
1698 // Poll for new entries during processing
1699 function startEntriesPolling() {
1700 if (entriesPollingInterval) return; // Already polling
1701
1702 isEntriesPolling = true;
1703
1704 // Fetch immediately, then start interval
1705 fetchNewEntries();
1706
1707 entriesPollingInterval = setInterval(function() {
1708 fetchNewEntries();
1709 }, 3000); // Poll every 3 seconds
1710 }
1711
1712 function stopEntriesPolling() {
1713 if (entriesPollingInterval) {
1714 clearInterval(entriesPollingInterval);
1715 entriesPollingInterval = null;
1716 }
1717 isEntriesPolling = false;
1718 }
1719
1720 // Track Pinecone count for change detection
1721 var lastPineconeCount = 0;
1722 var pineconeRefreshPending = false;
1723
1724 // Fetch new entries from server
1725 function fetchNewEntries() {
1726 if (!mxchatAdmin.entries_nonce) {
1727 return;
1728 }
1729
1730 $.ajax({
1731 url: ajaxurl,
1732 type: 'POST',
1733 data: {
1734 action: 'mxchat_get_recent_entries',
1735 nonce: mxchatAdmin.entries_nonce,
1736 last_id: lastEntryId,
1737 bot_id: mxchatAdmin.bot_id || 'default',
1738 limit: 20
1739 },
1740 success: function(response) {
1741 if (response.success && response.data) {
1742 // Update count
1743 if (response.data.total_count !== undefined) {
1744 updateEntryCount(response.data.total_count);
1745 }
1746
1747 // Handle Pinecone data source differently
1748 if (response.data.data_source === 'pinecone') {
1749 var newCount = response.data.total_count || 0;
1750
1751 // If count changed and we haven't scheduled a refresh yet
1752 if (newCount !== lastPineconeCount && !pineconeRefreshPending) {
1753 lastPineconeCount = newCount;
1754
1755 // Schedule a table refresh after processing completes
1756 // Show a "refresh to see entries" message
1757 var $tbody = $('#mxchat-entries-tbody');
1758 var $refreshNotice = $tbody.find('.mxchat-pinecone-refresh-notice');
1759
1760 if ($refreshNotice.length === 0 && newCount > 0) {
1761 var noticeHtml = '<tr class="mxchat-pinecone-refresh-notice">' +
1762 '<td colspan="4" style="padding: 20px; text-align: center; background: #f0f7ff; border-bottom: 1px solid var(--mxch-card-border);">' +
1763 '<span class="dashicons dashicons-update" style="color: #7873f5; margin-right: 8px;"></span>' +
1764 '<strong>' + newCount + ' entries in Pinecone.</strong> ' +
1765 '<a href="#" class="mxchat-refresh-table-link" style="color: #7873f5; text-decoration: underline;">Refresh to see new entries</a>' +
1766 '</td></tr>';
1767 $tbody.prepend(noticeHtml);
1768
1769 // Handle refresh link click
1770 $tbody.find('.mxchat-refresh-table-link').on('click', function(e) {
1771 e.preventDefault();
1772 location.reload();
1773 });
1774 } else if ($refreshNotice.length > 0) {
1775 // Update the count in existing notice
1776 $refreshNotice.find('strong').text(newCount + ' entries in Pinecone.');
1777 }
1778 }
1779 } else {
1780 // WordPress DB - Track new entries and show refresh notice
1781 // Don't add rows individually during processing as they need to be grouped by source_url
1782 if (response.data.entries && response.data.entries.length > 0) {
1783 // Update last ID to track progress
1784 if (response.data.max_id > lastEntryId) {
1785 lastEntryId = response.data.max_id;
1786 }
1787
1788 // Show/update refresh notice (similar to Pinecone handling)
1789 var $tbody = $('#mxchat-entries-tbody');
1790 var $refreshNotice = $tbody.find('.mxchat-wordpress-refresh-notice');
1791 var newCount = response.data.total_count || 0;
1792
1793 if ($refreshNotice.length === 0 && newCount > 0) {
1794 var noticeHtml = '<tr class="mxchat-wordpress-refresh-notice mxchat-new-entry">' +
1795 '<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);">' +
1796 '<span class="dashicons dashicons-update spin" style="color: #7873f5; margin-right: 8px;"></span>' +
1797 '<strong style="color: var(--mxch-text-primary);">Processing... <span class="mxchat-processing-count">' + newCount + '</span> entries</strong>' +
1798 '</td></tr>';
1799 $tbody.prepend(noticeHtml);
1800 } else if ($refreshNotice.length > 0) {
1801 // Update the count in existing notice
1802 $refreshNotice.find('.mxchat-processing-count').text(newCount);
1803 }
1804 } else {
1805 // Update last ID even if no new entries
1806 if (response.data.max_id > lastEntryId) {
1807 lastEntryId = response.data.max_id;
1808 }
1809 }
1810 }
1811 }
1812 },
1813 error: function(xhr, status, error) {
1814 console.error('MxChat: Error fetching new entries:', error, xhr.responseText);
1815 }
1816 });
1817 }
1818
1819 // Update the entry count display
1820 function updateEntryCount(count) {
1821 // Update main table count
1822 const $countSpan = $('#mxchat-entry-count');
1823 if ($countSpan.length) {
1824 $countSpan.text('(' + count + ')');
1825
1826 // Flash animation to indicate update
1827 $countSpan.addClass('mxchat-count-updated');
1828 setTimeout(function() {
1829 $countSpan.removeClass('mxchat-count-updated');
1830 }, 1000);
1831 }
1832
1833 // Update sidebar badge count
1834 const $sidebarCount = $('#mxchat-sidebar-count');
1835 if ($sidebarCount.length) {
1836 $sidebarCount.text(count);
1837
1838 // Flash animation for sidebar too
1839 $sidebarCount.addClass('mxchat-count-updated');
1840 setTimeout(function() {
1841 $sidebarCount.removeClass('mxchat-count-updated');
1842 }, 1000);
1843 }
1844 }
1845
1846 // Refresh the knowledge base table via AJAX pagination
1847 // This ensures entries are properly grouped by source_url
1848 function refreshKnowledgeBaseTable() {
1849 var $paginationWrapper = $('#mxchat-kb-pagination');
1850 var $tbody = $('#mxchat-entries-tbody');
1851
1852 if ($tbody.length === 0) {
1853 return;
1854 }
1855
1856 // Remove any processing notice
1857 $tbody.find('.mxchat-wordpress-refresh-notice, .mxchat-pinecone-refresh-notice').remove();
1858
1859 // Show loading state
1860 $tbody.css('opacity', '0.5');
1861
1862 $.ajax({
1863 url: ajaxurl,
1864 type: 'POST',
1865 data: {
1866 action: 'mxchat_paginate_entries',
1867 nonce: mxchatAdmin.entries_nonce,
1868 bot_id: mxchatAdmin.bot_id || 'default',
1869 page: 1 // Always go to first page to see newest entries
1870 },
1871 success: function(response) {
1872 $tbody.css('opacity', '1');
1873
1874 if (response.success && response.data) {
1875 // Update the table body with properly grouped HTML
1876 $tbody.html(response.data.html);
1877
1878 // Update the pagination
1879 if ($paginationWrapper.length) {
1880 if (response.data.pagination_html) {
1881 // Add pagination content and styling
1882 $paginationWrapper.html(response.data.pagination_html);
1883 $paginationWrapper.attr('style', 'padding: 16px; border-top: 1px solid var(--mxch-card-border); text-align: center;');
1884 } else {
1885 // No pagination needed - clear and hide
1886 $paginationWrapper.html('');
1887 $paginationWrapper.attr('style', '');
1888 }
1889 }
1890
1891 // Update count display
1892 if (response.data.total_count !== undefined) {
1893 updateEntryCount(response.data.total_count);
1894 }
1895 }
1896 },
1897 error: function(xhr, status, error) {
1898 $tbody.css('opacity', '1');
1899 console.error('MxChat: Error refreshing table:', error);
1900 }
1901 });
1902 }
1903
1904 // Expose refreshKnowledgeBaseTable for external access (content-selector.js)
1905 window.refreshKnowledgeBaseTable = refreshKnowledgeBaseTable;
1906
1907 // Add new entries to the table (kept for backwards compatibility but not used during processing)
1908 function addNewEntriesToTable(entries) {
1909 const $tbody = $('#mxchat-entries-tbody');
1910 if ($tbody.length === 0) return;
1911
1912 // Remove "no entries" message if present
1913 const $noEntries = $tbody.find('td[colspan="4"]').closest('tr');
1914 if ($noEntries.length) {
1915 $noEntries.remove();
1916 }
1917
1918 // Add entries in reverse order (oldest first, so newest ends up at top)
1919 entries.reverse().forEach(function(entry) {
1920 // Check if entry already exists
1921 if ($tbody.find('tr[data-entry-id="' + entry.id + '"]').length > 0) {
1922 return;
1923 }
1924
1925 const sourceHtml = entry.has_link
1926 ? '<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>'
1927 : '<span style="color: var(--mxch-text-muted);">Manual</span>';
1928
1929 const deleteUrl = mxchatAdmin.admin_url + 'admin-post.php?action=mxchat_delete_prompt&id=' + entry.id + '&_wpnonce=' + entry.delete_nonce;
1930
1931 // Check if content needs expand button (content longer than preview)
1932 const needsExpand = entry.content_length > entry.preview_length;
1933
1934 // Build accordion-style content cell (matching initial page load structure)
1935 let contentHtml = '<div class="mxchat-accordion-wrapper">' +
1936 '<div class="mxchat-content-preview">' +
1937 '<span class="preview-text">' + entry.preview + '</span>';
1938
1939 if (needsExpand) {
1940 contentHtml += '<button class="mxchat-expand-toggle" type="button">' +
1941 '<span class="dashicons dashicons-arrow-down-alt2"></span>' +
1942 '</button>';
1943 }
1944
1945 contentHtml += '</div>' +
1946 '<div class="mxchat-content-full" style="display: none;">' +
1947 '<div class="content-view">' + entry.full_content + '</div>' +
1948 '</div>' +
1949 '</div>';
1950
1951 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;">' +
1952 '<td style="padding: 12px 16px; font-size: 13px;">' + entry.id + '</td>' +
1953 '<td class="mxchat-content-cell" style="padding: 12px 16px; font-size: 13px;">' + contentHtml + '</td>' +
1954 '<td class="mxchat-url-cell" style="padding: 12px 16px; font-size: 13px;">' + sourceHtml + '</td>' +
1955 '<td style="padding: 12px 16px;">' +
1956 '<a href="' + deleteUrl + '" class="mxch-btn mxch-btn-ghost mxch-btn-sm" style="color: var(--mxch-error);" onclick="return confirm(\'Delete this entry?\');">' +
1957 '<span class="dashicons dashicons-trash" style="font-size: 14px;"></span>' +
1958 '</a>' +
1959 '</td>' +
1960 '</tr>');
1961
1962 // Add highlight class and prepend to tbody
1963 $row.addClass('mxchat-new-entry');
1964 $tbody.prepend($row);
1965 $row.slideDown(300);
1966
1967 // Remove highlight after animation
1968 setTimeout(function() {
1969 $row.removeClass('mxchat-new-entry');
1970 }, 2000);
1971 });
1972 }
1973
1974 // Initialize entry ID tracking
1975 initializeLastEntryId();
1976
1977 // Check on page load if there's already active processing (e.g., page was refreshed during processing)
1978 if ($('.mxchat-status-card').length > 0) {
1979 // Check if processing is active via AJAX
1980 $.ajax({
1981 url: ajaxurl,
1982 type: 'POST',
1983 data: {
1984 action: 'mxchat_get_status_updates',
1985 nonce: mxchatAdmin.status_nonce
1986 },
1987 success: function(response) {
1988 if (response.is_processing) {
1989 startEntriesPolling();
1990 }
1991 }
1992 });
1993 }
1994
1995 // Expose functions for external access
1996 window.mxchatEntriesPolling = {
1997 start: startEntriesPolling,
1998 stop: stopEntriesPolling,
1999 fetch: fetchNewEntries
2000 };
2001
2002 // ========================================
2003 // SITEMAP DETECTION FUNCTIONALITY
2004 // ========================================
2005
2006 let sitemapDetectionInitialized = false;
2007
2008 /**
2009 * Initialize sitemap detection when Sitemap Import is clicked
2010 */
2011 function initSitemapDetection() {
2012 if (sitemapDetectionInitialized) return;
2013
2014 const loadingEl = document.getElementById('mxchat-sitemaps-loading');
2015 const detectedEl = document.getElementById('mxchat-detected-sitemaps');
2016 const noSitemapsEl = document.getElementById('mxchat-no-sitemaps');
2017 const listEl = document.getElementById('mxchat-sitemaps-list');
2018 const refreshBtn = document.getElementById('mxchat-refresh-sitemaps');
2019 const nonceEl = document.getElementById('mxchat-detect-sitemaps-nonce');
2020
2021 if (!loadingEl || !nonceEl) return;
2022
2023 sitemapDetectionInitialized = true;
2024
2025 function detectSitemaps() {
2026 // Show loading
2027 loadingEl.style.display = 'block';
2028 if (detectedEl) detectedEl.style.display = 'none';
2029 if (noSitemapsEl) noSitemapsEl.style.display = 'none';
2030
2031 // Disable refresh button
2032 if (refreshBtn) {
2033 refreshBtn.disabled = true;
2034 var refreshIcon = refreshBtn.querySelector('.dashicons');
2035 if (refreshIcon) refreshIcon.classList.add('spin');
2036 }
2037
2038 $.ajax({
2039 url: ajaxurl,
2040 type: 'POST',
2041 data: {
2042 action: 'mxchat_detect_sitemaps',
2043 nonce: nonceEl.value
2044 },
2045 timeout: 60000, // 60 second timeout for slow servers
2046 success: function(data) {
2047 loadingEl.style.display = 'none';
2048
2049 // Re-enable refresh button
2050 if (refreshBtn) {
2051 refreshBtn.disabled = false;
2052 var refreshIcon = refreshBtn.querySelector('.dashicons');
2053 if (refreshIcon) refreshIcon.classList.remove('spin');
2054 }
2055
2056 if (data.success && data.data && data.data.sitemaps && data.data.sitemaps.length > 0) {
2057 renderSitemaps(data.data.sitemaps);
2058 if (detectedEl) detectedEl.style.display = 'block';
2059 } else {
2060 if (noSitemapsEl) {
2061 noSitemapsEl.style.display = 'block';
2062 $(noSitemapsEl).data('was-shown', true);
2063 }
2064 }
2065 },
2066 error: function(xhr, status, error) {
2067 loadingEl.style.display = 'none';
2068 if (noSitemapsEl) {
2069 noSitemapsEl.style.display = 'block';
2070 $(noSitemapsEl).data('was-shown', true);
2071 }
2072 if (refreshBtn) {
2073 refreshBtn.disabled = false;
2074 var refreshIcon = refreshBtn.querySelector('.dashicons');
2075 if (refreshIcon) refreshIcon.classList.remove('spin');
2076 }
2077 }
2078 });
2079 }
2080
2081 function renderSitemaps(sitemaps) {
2082 if (!listEl) return;
2083
2084 var html = '';
2085 var botIdEl = document.getElementById('mxchat-sitemap-bot-id');
2086 var botId = botIdEl ? botIdEl.value : '';
2087
2088 sitemaps.forEach(function(sitemap) {
2089 if (sitemap.type === 'index' && sitemap.sub_sitemaps && sitemap.sub_sitemaps.length > 0) {
2090 // Render sitemap index with sub-sitemaps
2091 html += '<div class="mxchat-sitemap-group">';
2092 html += '<div class="mxchat-sitemap-group-header">';
2093 html += '<div style="display: flex; align-items: center; gap: 10px;">';
2094 html += '<span class="dashicons dashicons-arrow-right-alt2" style="transition: transform 0.2s;"></span>';
2095 html += '<span class="dashicons dashicons-list-view" style="color: #7873f5;"></span>';
2096 html += '<div>';
2097 html += '<strong style="font-size: 13px;">Sitemap Index</strong>';
2098 html += '<span style="color: #666; font-size: 12px; margin-left: 8px;">' + sitemap.source + '</span>';
2099 html += '</div>';
2100 html += '</div>';
2101 html += '<span style="background: rgba(120, 115, 245, 0.1); color: #7873f5; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600;">';
2102 html += sitemap.sub_sitemaps.length + ' sitemaps';
2103 html += '</span>';
2104 html += '</div>';
2105 html += '<div class="mxchat-sitemap-sub-list">';
2106 sitemap.sub_sitemaps.forEach(function(sub) {
2107 html += renderSitemapRow(sub, botId, true);
2108 });
2109 html += '</div>';
2110 html += '</div>';
2111 } else if (sitemap.type !== 'index') {
2112 // Render standalone sitemap
2113 html += renderSitemapRow(sitemap, botId, false);
2114 }
2115 });
2116
2117 listEl.innerHTML = html;
2118
2119 // Add click handlers for group toggles
2120 $(listEl).find('.mxchat-sitemap-group-header').on('click', function() {
2121 var $group = $(this).parent();
2122 var $subList = $group.find('.mxchat-sitemap-sub-list');
2123 var $arrow = $(this).find('.dashicons-arrow-right-alt2');
2124
2125 $group.toggleClass('expanded');
2126
2127 if ($group.hasClass('expanded')) {
2128 $subList.slideDown(200);
2129 $arrow.css('transform', 'rotate(90deg)');
2130 } else {
2131 $subList.slideUp(200);
2132 $arrow.css('transform', 'rotate(0deg)');
2133 }
2134 });
2135
2136 // Add click handlers for process buttons
2137 $(listEl).find('.mxchat-process-sitemap-btn').on('click', function() {
2138 var url = $(this).data('url');
2139 var type = $(this).data('sitemap-type');
2140 processSitemap(url, type, this);
2141 });
2142 }
2143
2144 function renderSitemapRow(sitemap, botId, isSubItem) {
2145 var typeLabels = {
2146 'content': 'Content',
2147 'taxonomy': 'Taxonomy',
2148 'author': 'Authors'
2149 };
2150 var typeLabel = typeLabels[sitemap.type] || sitemap.type;
2151 var displayName = sitemap.name || sitemap.url.split('/').pop();
2152 var urlCount = sitemap.url_count || 0;
2153 var paddingLeft = isSubItem ? '40px' : '16px';
2154
2155 var html = '<div class="mxchat-sitemap-row" style="padding-left: ' + paddingLeft + ';">';
2156 html += '<div style="display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0;">';
2157 html += '<span class="dashicons dashicons-media-text" style="color: #666; flex-shrink: 0;"></span>';
2158 html += '<div style="min-width: 0; flex: 1;">';
2159 html += '<div style="font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="' + sitemap.url + '">';
2160 html += displayName;
2161 html += '</div>';
2162 html += '<div style="font-size: 11px; color: #666;">';
2163 html += '<span style="background: #f0f0f0; padding: 1px 6px; border-radius: 3px; margin-right: 8px;">' + typeLabel + '</span>';
2164 if (urlCount > 0) {
2165 html += urlCount + ' URLs';
2166 }
2167 html += '</div>';
2168 html += '</div>';
2169 html += '</div>';
2170 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 + '">';
2171 html += '<span class="dashicons dashicons-download" style="font-size: 14px; margin-top: 3px;"></span> Process';
2172 html += '</button>';
2173 html += '</div>';
2174
2175 return html;
2176 }
2177
2178 function processSitemap(url, type, buttonEl) {
2179 var $button = $(buttonEl);
2180 var originalHtml = $button.html();
2181
2182 // Update button to show loading
2183 $button.prop('disabled', true);
2184 $button.html('<span class="dashicons dashicons-update spin" style="font-size: 14px; margin-top: 3px;"></span> Processing...');
2185
2186 // Fill in the sitemap URL form and submit
2187 var $form = $('#mxchat-url-form');
2188 var $urlInput = $('#sitemap_url');
2189 var $importType = $('#import_type');
2190
2191 if ($urlInput.length) {
2192 $urlInput.val(url);
2193 }
2194
2195 if ($importType.length) {
2196 $importType.val('sitemap');
2197 }
2198
2199 // Add a hidden submit field if not present (required by the PHP handler)
2200 if ($form.find('input[name="submit_sitemap"]').length === 0) {
2201 $form.append('<input type="hidden" name="submit_sitemap" value="1">');
2202 }
2203
2204 // Submit the form
2205 $form.submit();
2206 }
2207
2208
2209 // Refresh button handler
2210 if (refreshBtn) {
2211 $(refreshBtn).on('click', detectSitemaps);
2212 }
2213
2214 // Start detection
2215 detectSitemaps();
2216 }
2217
2218 // Expose initSitemapDetection globally so it can be called from the import options handler
2219 window.mxchatInitSitemapDetection = initSitemapDetection;
2220
2221 // ========================================
2222 // ADMIN NOTICE DISMISS FUNCTIONALITY
2223 // ========================================
2224
2225 // Initialize dismissible notices - add dismiss button if missing
2226 function initDismissibleNotices() {
2227 $('.notice.is-dismissible').each(function() {
2228 var $notice = $(this);
2229
2230 // Skip if already has a dismiss button
2231 if ($notice.find('.notice-dismiss').length > 0) {
2232 return;
2233 }
2234
2235 // Add dismiss button
2236 var $dismissButton = $('<button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss this notice.</span></button>');
2237 $notice.append($dismissButton);
2238 });
2239 }
2240
2241 // Initialize on page load
2242 initDismissibleNotices();
2243
2244 // Use event delegation for dismiss button clicks - works for existing and dynamically added notices
2245 $(document).on('click', '.notice.is-dismissible .notice-dismiss', function(e) {
2246 e.preventDefault();
2247 e.stopPropagation();
2248
2249 var $notice = $(this).closest('.notice');
2250 $notice.fadeTo(100, 0, function() {
2251 $notice.slideUp(100, function() {
2252 $notice.remove();
2253 });
2254 });
2255 });
2256
2257 // Re-initialize when new notices are added dynamically (e.g., via AJAX)
2258 $(document).on('DOMNodeInserted', function(e) {
2259 if ($(e.target).hasClass('notice') && $(e.target).hasClass('is-dismissible')) {
2260 setTimeout(initDismissibleNotices, 10);
2261 }
2262 });
2263 });