PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.5.5
MxChat – AI Chatbot & Content Generation for WordPress v2.5.5
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 2.5.5, at js/knowledge-processing.js

1,154 lines 47.9 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 the batch processing loop
192 processNextBatch();
193 }
194
195 /**
196 * Process the next batch of items (5 at a time)
197 */
198 function processNextBatch() {
199 if (!isProcessingQueue) {
200 //console.log('MxChat: Processing stopped');
201 return;
202 }
203
204 // Fetch the next batch of items
205 const fetchPromises = [];
206
207 for (let i = 0; i < BATCH_SIZE; i++) {
208 const promise = $.ajax({
209 url: ajaxurl,
210 type: 'POST',
211 data: {
212 action: 'mxchat_get_next_queue_item',
213 nonce: mxchatAdmin.queue_nonce,
214 queue_id: currentQueueId
215 }
216 });
217 fetchPromises.push(promise);
218 }
219
220 // Wait for all fetch requests to complete
221 Promise.all(fetchPromises).then(function(responses) {
222 // Filter out completed/error responses and extract items
223 const items = [];
224 let queueComplete = false;
225
226 for (let response of responses) {
227 if (response.success && response.data && !response.data.complete) {
228 items.push(response.data.item);
229 } else if (response.data && response.data.complete) {
230 queueComplete = true;
231 }
232 }
233
234 // If no items to process, queue is done
235 if (items.length === 0) {
236 if (queueComplete) {
237 handleQueueComplete();
238 } else {
239 verifyQueueCompletion();
240 }
241 return;
242 }
243
244 // Process all items in this batch simultaneously
245 const processPromises = items.map(item => processQueueItem(item));
246
247 // Wait for all items to finish processing
248 Promise.all(processPromises).then(function() {
249 // Update progress after batch completes
250 updateQueueProgress();
251
252 // If we got fewer items than batch size, queue might be done
253 if (items.length < BATCH_SIZE || queueComplete) {
254 verifyQueueCompletion();
255 } else {
256 // Process next batch immediately
257 processNextBatch();
258 }
259 }).catch(function(error) {
260 console.error('MxChat: Error processing batch:', error);
261 // Continue anyway
262 updateQueueProgress();
263 setTimeout(function() {
264 processNextBatch();
265 }, 1000);
266 });
267
268 }).catch(function(error) {
269 console.error('MxChat: Error fetching batch:', error);
270 // Verify queue status before retrying
271 setTimeout(function() {
272 verifyQueueCompletion();
273 }, 2000);
274 });
275 }
276
277 /**
278 * Verify if queue is actually complete
279 * Prevents infinite loops when last item fails
280 */
281 function verifyQueueCompletion() {
282 //console.log('MxChat: Verifying queue completion status...');
283
284 $.ajax({
285 url: ajaxurl,
286 type: 'POST',
287 data: {
288 action: 'mxchat_get_queue_status',
289 nonce: mxchatAdmin.queue_nonce,
290 queue_id: currentQueueId
291 },
292 success: function(response) {
293 if (response.success) {
294 const status = response.data;
295
296 // If no pending or processing items, queue is done
297 if (status.pending === 0 && status.processing === 0) {
298 //console.log('MxChat: Queue verified as complete');
299 handleQueueComplete();
300 } else {
301 // Still has items, try to continue
302 //console.log('MxChat: Queue still has pending items, continuing...');
303 processNextBatch();
304 }
305 } else {
306 // Can't verify, assume complete to prevent infinite loop
307 //console.log('MxChat: Could not verify queue status, assuming complete');
308 handleQueueComplete();
309 }
310 },
311 error: function() {
312 // Can't verify, assume complete to prevent infinite loop
313 //console.log('MxChat: Network error verifying queue, assuming complete');
314 handleQueueComplete();
315 }
316 });
317 }
318
319 /**
320 * Process a single queue item
321 * Returns a Promise that resolves when processing is complete
322 */
323 function processQueueItem(item) {
324 return $.ajax({
325 url: ajaxurl,
326 type: 'POST',
327 data: {
328 action: 'mxchat_process_queue_item',
329 nonce: mxchatAdmin.queue_nonce,
330 item_id: item.id,
331 item_type: item.type,
332 item_data: item.data,
333 bot_id: item.bot_id
334 }
335 }).then(function(response) {
336 if (response.success) {
337 // Item processed successfully
338 //console.log('MxChat: Item processed successfully:', item.id);
339 return true;
340 } else {
341 // Item failed but we KEEP GOING
342 console.warn('MxChat: Item processing failed (will continue):', item.type, item.id);
343 console.warn('MxChat: Error details:', response.data);
344 return false;
345 }
346 }).catch(function(xhr, status, error) {
347 // Network error - log it but KEEP GOING
348 console.error('MxChat: AJAX/Network error processing item:', item.id, error);
349 return false;
350 });
351 }
352
353 /**
354 * Update queue progress (fetches latest stats)
355 */
356 function updateQueueProgress() {
357 $.ajax({
358 url: ajaxurl,
359 type: 'POST',
360 data: {
361 action: 'mxchat_get_queue_status',
362 nonce: mxchatAdmin.queue_nonce,
363 queue_id: currentQueueId
364 },
365 success: function(response) {
366 if (response.success) {
367 const status = response.data;
368
369 // Update the appropriate status card
370 if (currentQueueType === 'pdf') {
371 updatePdfStatusFromQueue(status);
372 } else {
373 updateSitemapStatusFromQueue(status);
374 }
375 }
376 }
377 });
378 }
379
380 /**
381 * Handle queue completion
382 * NO AUTO-REFRESH - Show completed card with errors until dismissed
383 */
384 function handleQueueComplete() {
385 isProcessingQueue = false;
386
387 //console.log('MxChat: Queue processing completed - showing final results');
388
389 // Get final status with error details
390 $.ajax({
391 url: ajaxurl,
392 type: 'POST',
393 data: {
394 action: 'mxchat_get_queue_status',
395 nonce: mxchatAdmin.queue_nonce,
396 queue_id: currentQueueId
397 },
398 success: function(response) {
399 if (response.success) {
400 const status = response.data;
401
402 // Show completed status card (NO REFRESH)
403 if (currentQueueType === 'pdf') {
404 showCompletedPdfCard(status);
405 } else {
406 showCompletedSitemapCard(status);
407 }
408
409 // Mark the queue as complete on server
410 markQueueAsComplete(currentQueueId);
411
412 // Show notification based on results
413 if (status.failed > 0) {
414 showNotification('warning',
415 `Processing completed: ${status.completed} succeeded, ${status.failed} failed. ` +
416 `Review errors below and dismiss when ready.`
417 );
418 } else {
419 showNotification('success',
420 `Processing completed successfully! All ${status.completed} items processed. ` +
421 `Dismiss the status card when ready.`
422 );
423 }
424
425 // NO AUTO-REFRESH - User must manually dismiss
426 } else {
427 // Couldn't get final status, just show generic completion
428 showNotification('success', 'Processing completed! Refresh page to see final results.');
429 }
430 },
431 error: function() {
432 // Error getting final status
433 showNotification('success', 'Processing completed! Refresh page to see final results.');
434 }
435 });
436 }
437
438 /**
439 * Show completed PDF card with full error details (NO RETRY BUTTON)
440 */
441 function showCompletedPdfCard(status) {
442 let $card = $('.mxchat-status-card:contains("PDF Processing")');
443
444 if ($card.length === 0) {
445 return;
446 }
447
448 // Remove processing UI elements
449 $card.find('.mxchat-stop-form').remove();
450 $card.find('.mxchat-status-warning').remove();
451
452 // Update header with completion badge
453 $card.find('.mxchat-status-badge').remove();
454 if (status.failed > 0) {
455 $card.find('.mxchat-status-header h4').after(
456 '<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 ' +
457 status.failed + ' failures - Refresh to view entries</span>'
458 );
459 } else {
460 $card.find('.mxchat-status-header h4').after(
461 '<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>'
462 );
463 }
464
465 // Add dismiss button
466 addDismissButton($card);
467
468 // Update progress bar to 100%
469 $card.find('.mxchat-progress-fill').css('width', '100%');
470
471 // Update details with final stats
472 let detailsHtml = '<div style="background: #f0f7ff; padding: 15px; border-radius: 4px; margin-bottom: 15px;">';
473 detailsHtml += '<h4 style="margin: 0 0 10px 0;">📊 Final Results</h4>';
474 detailsHtml += '<p style="margin: 5px 0;"><strong>Total Pages:</strong> ' + status.total + '</p>';
475 detailsHtml += '<p style="margin: 5px 0; color: #2ea44f;"><strong>✓ Successfully Processed:</strong> ' + status.completed + '</p>';
476
477 if (status.failed > 0) {
478 detailsHtml += '<p style="margin: 5px 0; color: #cf222e;"><strong>✗ Failed:</strong> ' + status.failed + '</p>';
479 }
480
481 detailsHtml += '</div>';
482
483 // Add error details if there are failures (NO RETRY BUTTON)
484 if (status.failed > 0 && status.failed_items && status.failed_items.length > 0) {
485 detailsHtml += '<div class="mxchat-error-summary" style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-top: 15px;">';
486 detailsHtml += '<h4 style="margin: 0 0 10px 0; color: #856404;">⚠️ Failed Pages</h4>';
487
488 detailsHtml += '<details style="cursor: pointer;">';
489 detailsHtml += '<summary style="font-weight: bold; color: #856404; padding: 5px 0;">Click to view ' + status.failed_items.length + ' failed pages</summary>';
490
491 detailsHtml += '<div style="margin-top: 10px; max-height: 400px; overflow-y: auto;">';
492 detailsHtml += '<table style="width: 100%; border-collapse: collapse;">';
493 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>';
494 detailsHtml += '<tbody>';
495
496 status.failed_items.forEach(function(item) {
497 const data = JSON.parse(item.item_data);
498 const pageNum = data.page_number || 'Unknown';
499
500 detailsHtml += '<tr style="border-bottom: 1px solid #ddd;">';
501 detailsHtml += '<td style="padding: 8px;">Page ' + pageNum + '</td>';
502 detailsHtml += '<td style="padding: 8px; word-break: break-word;">' + (item.error_message || 'Unknown error') + '</td>';
503 detailsHtml += '<td style="padding: 8px;">' + item.attempts + '</td>';
504 detailsHtml += '</tr>';
505 });
506
507 detailsHtml += '</tbody></table>';
508 detailsHtml += '</div>';
509 detailsHtml += '</details>';
510
511 detailsHtml += '</div>';
512 }
513
514 $card.find('.mxchat-status-details').html(detailsHtml);
515 }
516
517 /**
518 * Show completed sitemap card with full error details (NO RETRY BUTTON)
519 */
520 function showCompletedSitemapCard(status) {
521 let $card = $('.mxchat-status-card:contains("Sitemap Processing")');
522
523 if ($card.length === 0) {
524 return;
525 }
526
527 // Remove processing UI elements
528 $card.find('.mxchat-stop-form').remove();
529 $card.find('.mxchat-status-warning').remove();
530
531 // Update header with completion badge
532 $card.find('.mxchat-status-badge').remove();
533 if (status.failed > 0) {
534 $card.find('.mxchat-status-header h4').after(
535 '<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 ' +
536 status.failed + ' failures - Refresh to view entries</span>'
537 );
538 } else {
539 $card.find('.mxchat-status-header h4').after(
540 '<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>'
541 );
542 }
543
544 // Add dismiss button
545 addDismissButton($card);
546
547 // Update progress bar to 100%
548 $card.find('.mxchat-progress-fill').css('width', '100%');
549
550 // Update details with final stats
551 let detailsHtml = '<div style="background: #f0f7ff; padding: 15px; border-radius: 4px; margin-bottom: 15px;">';
552 detailsHtml += '<h4 style="margin: 0 0 10px 0;">📊 Final Results</h4>';
553 detailsHtml += '<p style="margin: 5px 0;"><strong>Total URLs:</strong> ' + status.total + '</p>';
554 detailsHtml += '<p style="margin: 5px 0; color: #2ea44f;"><strong>✓ Successfully Processed:</strong> ' + status.completed + '</p>';
555
556 if (status.failed > 0) {
557 detailsHtml += '<p style="margin: 5px 0; color: #cf222e;"><strong>✗ Failed:</strong> ' + status.failed + '</p>';
558 }
559
560 detailsHtml += '</div>';
561
562 // Add error details if there are failures (NO RETRY BUTTON)
563 if (status.failed > 0 && status.failed_items && status.failed_items.length > 0) {
564 detailsHtml += '<div class="mxchat-error-summary" style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-top: 15px;">';
565 detailsHtml += '<h4 style="margin: 0 0 10px 0; color: #856404;">⚠️ Failed URLs</h4>';
566
567 detailsHtml += '<details style="cursor: pointer;">';
568 detailsHtml += '<summary style="font-weight: bold; color: #856404; padding: 5px 0;">Click to view ' + status.failed_items.length + ' failed URLs</summary>';
569
570 detailsHtml += '<div style="margin-top: 10px; max-height: 400px; overflow-y: auto;">';
571 detailsHtml += '<table style="width: 100%; border-collapse: collapse;">';
572 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>';
573 detailsHtml += '<tbody>';
574
575 status.failed_items.forEach(function(item) {
576 const data = JSON.parse(item.item_data);
577 const url = data.url || 'Unknown URL';
578 const displayUrl = url.length > 60 ? url.substring(0, 57) + '...' : url;
579
580 detailsHtml += '<tr style="border-bottom: 1px solid #ddd;">';
581 detailsHtml += '<td style="padding: 8px;"><a href="' + url + '" target="_blank" style="color: #0073aa; text-decoration: none;">' + displayUrl + '</a></td>';
582 detailsHtml += '<td style="padding: 8px; word-break: break-word;">' + (item.error_message || 'Unknown error') + '</td>';
583 detailsHtml += '<td style="padding: 8px;">' + item.attempts + '</td>';
584 detailsHtml += '</tr>';
585 });
586
587 detailsHtml += '</tbody></table>';
588 detailsHtml += '</div>';
589 detailsHtml += '</details>';
590
591 detailsHtml += '</div>';
592 }
593
594 $card.find('.mxchat-status-details').html(detailsHtml);
595 }
596
597 /**
598 * Mark queue as complete on server side
599 * This prevents it from auto-starting on page refresh
600 */
601 function markQueueAsComplete(queueId) {
602 // This is a fire-and-forget call to update queue status
603 $.ajax({
604 url: ajaxurl,
605 type: 'POST',
606 data: {
607 action: 'mxchat_mark_queue_complete',
608 nonce: mxchatAdmin.queue_nonce,
609 queue_id: queueId
610 },
611 success: function(response) {
612 //console.log('MxChat: Queue marked as complete on server');
613 },
614 error: function() {
615 //console.log('MxChat: Could not mark queue as complete, but continuing');
616 }
617 });
618 }
619
620 /**
621 * Stop processing button handler
622 */
623 $(document).on('submit', '.mxchat-stop-form', function() {
624 //console.log('MxChat: Stop processing requested');
625 isProcessingQueue = false;
626 currentQueueId = null;
627 currentQueueType = null;
628 });
629
630 /**
631 * Create or update status card
632 */
633 function createOrUpdateStatusCard(queueType) {
634 const cardTitle = queueType === 'pdf' ? 'PDF Processing Status' : 'Sitemap Processing Status';
635 let $card = $('.mxchat-status-card:contains("' + cardTitle + '")');
636
637 if ($card.length === 0) {
638 // Create new card
639 let html = '<div class="mxchat-status-card">';
640 html += '<div class="mxchat-status-header">';
641 html += '<h4>' + cardTitle + '</h4>';
642 html += '<div class="mxchat-status-warning" style="background: #fff3cd; color: #856404; padding: 8px 12px; border-radius: 4px; font-size: 13px; margin: 10px 0;">';
643 html += '⚠️ <strong>Keep this tab open</strong> - Processing runs in your browser';
644 html += '</div>';
645 html += '<form method="post" class="mxchat-stop-form" action="' +
646 mxchatAdmin.admin_url + 'admin-post.php?action=mxchat_stop_processing">';
647 html += '<input type="hidden" name="mxchat_stop_processing_nonce" value="' +
648 mxchatAdmin.stop_nonce + '">';
649 html += '<button type="submit" name="stop_processing" class="mxchat-button-secondary">';
650 html += 'Stop Processing</button></form>';
651 html += '</div>';
652 html += '<div class="mxchat-progress-bar">';
653 html += '<div class="mxchat-progress-fill" style="width: 0%"></div>';
654 html += '</div>';
655 html += '<div class="mxchat-status-details">';
656 html += '<p>Initializing...</p>';
657 html += '</div>';
658 html += '</div>';
659
660 // Insert card
661 let $importSection = $('.mxchat-import-section');
662 if ($importSection.length > 0) {
663 $importSection.after($(html));
664 }
665 }
666 }
667
668 /**
669 * Update PDF status from queue data (DURING PROCESSING)
670 */
671 function updatePdfStatusFromQueue(status) {
672 let $card = $('.mxchat-status-card:contains("PDF Processing")');
673
674 if ($card.length === 0) {
675 return;
676 }
677
678 // Update progress bar
679 $card.find('.mxchat-progress-fill').css('width', status.percentage + '%');
680
681 // Update details
682 let detailsHtml = '<p>Progress: ' + (status.completed + status.failed) + ' of ' +
683 status.total + ' pages (' + status.percentage + '%)</p>';
684
685 if (status.completed > 0) {
686 detailsHtml += '<p><strong>✓ Processed successfully:</strong> ' + status.completed + '</p>';
687 }
688
689 if (status.failed > 0) {
690 detailsHtml += '<p class="error-count"><strong>✗ Failed pages:</strong> ' + status.failed + '</p>';
691 }
692
693 detailsHtml += '<p><strong>Status:</strong> Processing</p>';
694
695 $card.find('.mxchat-status-details').html(detailsHtml);
696 }
697
698 /**
699 * Update sitemap status from queue data (DURING PROCESSING)
700 */
701 function updateSitemapStatusFromQueue(status) {
702 let $card = $('.mxchat-status-card:contains("Sitemap Processing")');
703
704 if ($card.length === 0) {
705 return;
706 }
707
708 // Update progress bar
709 $card.find('.mxchat-progress-fill').css('width', status.percentage + '%');
710
711 // Update details
712 let detailsHtml = '<p>Progress: ' + (status.completed + status.failed) + ' of ' +
713 status.total + ' URLs (' + status.percentage + '%)</p>';
714
715 if (status.completed > 0) {
716 detailsHtml += '<p><strong>✓ Processed successfully:</strong> ' + status.completed + '</p>';
717 }
718
719 if (status.failed > 0) {
720 detailsHtml += '<p class="error-count"><strong>✗ Failed URLs:</strong> ' + status.failed + '</p>';
721 }
722
723 detailsHtml += '<p><strong>Status:</strong> Processing</p>';
724
725 $card.find('.mxchat-status-details').html(detailsHtml);
726 }
727
728 // ========================================
729 // STATUS UPDATES FOR COMPLETED QUEUES (FROM SERVER)
730 // ========================================
731
732 /**
733 * Dismiss completed status button handler
734 */
735 $(document).on('click', '.mxchat-dismiss-button', function() {
736 const $button = $(this);
737 const $card = $button.closest('.mxchat-status-card');
738
739 let cardType = $card.data('card-type');
740 if (!cardType) {
741 cardType = $card.find('h4').text().includes('PDF') ? 'pdf' : 'sitemap';
742 }
743
744 $card.fadeOut(300, function() {
745 $(this).remove();
746 });
747
748 $.ajax({
749 url: ajaxurl,
750 type: 'POST',
751 data: {
752 action: 'mxchat_clear_queue',
753 nonce: mxchatAdmin.queue_nonce,
754 queue_id: $card.data('queue-id') || ''
755 },
756 success: function(response) {
757 //console.log('MxChat: Queue cleared');
758 }
759 });
760 });
761
762 /**
763 * Update PDF status card (for already completed queues on page load)
764 */
765 function updatePdfStatus(status) {
766 let $pdfCard = $('.mxchat-status-card:contains("PDF Processing")');
767
768 if ($pdfCard.length === 0 && status) {
769 createPdfStatusCard(status);
770 $pdfCard = $('.mxchat-status-card:contains("PDF Processing")');
771 }
772
773 if ($pdfCard.length > 0 && status.status === 'complete') {
774 // Show as completed (same as showCompletedPdfCard but from server data)
775 showCompletedPdfCard(status);
776 }
777 }
778
779 /**
780 * Update sitemap status card (for already completed queues on page load)
781 */
782 function updateSitemapStatus(status) {
783 let $sitemapCard = $('.mxchat-status-card:contains("Sitemap Processing")');
784
785 if ($sitemapCard.length === 0 && status) {
786 createSitemapStatusCard(status);
787 $sitemapCard = $('.mxchat-status-card:contains("Sitemap Processing")');
788 }
789
790 if ($sitemapCard.length > 0 && status.status === 'complete') {
791 // Show as completed (same as showCompletedSitemapCard but from server data)
792 showCompletedSitemapCard(status);
793 }
794 }
795
796 /**
797 * Create PDF status card
798 */
799 function createPdfStatusCard(status) {
800 let html = '<div class="mxchat-status-card" data-queue-id="' + (status.queue_id || '') + '">';
801 html += '<div class="mxchat-status-header">';
802 html += '<h4>PDF Processing Status</h4>';
803 html += '</div>';
804 html += '<div class="mxchat-progress-bar">';
805 html += '<div class="mxchat-progress-fill" style="width: ' + status.percentage + '%"></div>';
806 html += '</div>';
807 html += '<div class="mxchat-status-details">';
808 html += '<p>Progress: ' + status.processed_pages + ' of ' + status.total_pages + ' pages</p>';
809 html += '</div>';
810 html += '</div>';
811
812 $('.mxchat-import-section').after($(html));
813 }
814
815 /**
816 * Create sitemap status card
817 */
818 function createSitemapStatusCard(status) {
819 let html = '<div class="mxchat-status-card" data-queue-id="' + (status.queue_id || '') + '">';
820 html += '<div class="mxchat-status-header">';
821 html += '<h4>Sitemap Processing Status</h4>';
822 html += '</div>';
823 html += '<div class="mxchat-progress-bar">';
824 html += '<div class="mxchat-progress-fill" style="width: ' + status.percentage + '%"></div>';
825 html += '</div>';
826 html += '<div class="mxchat-status-details">';
827 html += '<p>Progress: ' + status.processed_urls + ' of ' + status.total_urls + ' URLs</p>';
828 html += '</div>';
829 html += '</div>';
830
831 $('.mxchat-import-section').after($(html));
832 }
833
834 /**
835 * Add dismiss button to completed cards
836 */
837 function addDismissButton($card) {
838 if ($card.find('.mxchat-dismiss-button').length === 0) {
839 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>');
840 $card.find('.mxchat-status-header').append(dismissButton);
841 }
842 }
843
844 /**
845 * Show notification helper
846 */
847 function showNotification(type, message) {
848 const $notification = $('<div class="mxchat-kb-notification ' + type + '">' + message + '</div>');
849 $('.mxchat-content, body').first().prepend($notification);
850
851 setTimeout(function() {
852 $notification.fadeOut(300, function() {
853 $(this).remove();
854 });
855 }, 5000);
856 }
857
858 // ========================================
859 // ROLE-BASED CONTENT RESTRICTIONS (Keep existing code)
860 // ========================================
861
862 if ($('#mxchat-mappings-container').length > 0) {
863 loadTagRoleMappings();
864 }
865
866 $('#mxchat-add-tag-role').on('click', function() {
867 const tagSlug = $('#mxchat-tag-input').val().trim();
868 const roleRestriction = $('#mxchat-role-select').val();
869
870 if (!tagSlug) {
871 alert('Please enter a tag name');
872 return;
873 }
874
875 const $btn = $(this);
876 $btn.prop('disabled', true).html('<span class="dashicons dashicons-update-alt"></span> Adding...');
877
878 $.ajax({
879 url: ajaxurl,
880 type: 'POST',
881 data: {
882 action: 'mxchat_add_tag_role_mapping',
883 nonce: mxchatAdmin.settings_nonce,
884 tag_slug: tagSlug,
885 role_restriction: roleRestriction
886 },
887 success: function(response) {
888 if (response.success) {
889 $('#mxchat-tag-input').val('');
890 $('#mxchat-role-select').val('public');
891 loadTagRoleMappings();
892 showNotification('success', 'Tag-role mapping added successfully!');
893 } else {
894 alert('Error: ' + response.data);
895 }
896 $btn.prop('disabled', false).html('<span class="dashicons dashicons-plus-alt"></span> Add Mapping');
897 },
898 error: function() {
899 alert('Network error occurred');
900 $btn.prop('disabled', false).html('<span class="dashicons dashicons-plus-alt"></span> Add Mapping');
901 }
902 });
903 });
904
905 $(document).on('click', '.mxchat-delete-mapping', function() {
906 if (!confirm('Are you sure you want to delete this mapping?')) {
907 return;
908 }
909
910 const $btn = $(this);
911 const $row = $btn.closest('tr');
912 const tagSlug = $btn.data('tag-slug');
913
914 $btn.html('<span class="dashicons dashicons-update-alt"></span> Deleting...');
915 $row.addClass('mxchat-row-deleting');
916
917 $.ajax({
918 url: ajaxurl,
919 type: 'POST',
920 data: {
921 action: 'mxchat_delete_tag_role_mapping',
922 nonce: mxchatAdmin.settings_nonce,
923 tag_slug: tagSlug
924 },
925 success: function(response) {
926 if (response.success) {
927 $row.fadeOut(300, function() {
928 $(this).remove();
929 if ($('.mxchat-mappings-table tbody tr').length === 0) {
930 $('.mxchat-mappings-table').hide();
931 $('#mxchat-no-mappings').show();
932 }
933 });
934 showNotification('success', 'Mapping deleted successfully!');
935 } else {
936 alert('Error: ' + response.data);
937 $btn.html('<span class="dashicons dashicons-trash"></span> Delete');
938 $row.removeClass('mxchat-row-deleting');
939 }
940 },
941 error: function() {
942 alert('Network error occurred');
943 $btn.html('<span class="dashicons dashicons-trash"></span> Delete');
944 $row.removeClass('mxchat-row-deleting');
945 }
946 });
947 });
948
949 $('#mxchat-bulk-update-roles').on('click', function() {
950 if (!confirm('This will update role restrictions for all existing content with mapped tags. Continue?')) {
951 return;
952 }
953
954 const $btn = $(this);
955 const $progress = $('#mxchat-bulk-update-progress');
956 const $result = $('#mxchat-bulk-update-result');
957
958 $progress.show();
959 $result.hide();
960 $btn.prop('disabled', true);
961
962 $progress.find('.mxchat-progress-text').text('Starting bulk update...');
963 $progress.find('.mxchat-progress-fill').css('width', '0%');
964
965 $.ajax({
966 url: ajaxurl,
967 type: 'POST',
968 data: {
969 action: 'mxchat_bulk_update_tag_roles',
970 nonce: mxchatAdmin.settings_nonce
971 },
972 success: function(response) {
973 $progress.hide();
974 $btn.prop('disabled', false);
975
976 if (response.success) {
977 $result.removeClass('error').addClass('success');
978
979 let resultHtml = '<h5>Bulk Update Complete</h5>';
980 resultHtml += '<p><strong>Total Updated:</strong> ' + response.data.updated_count + '</p>';
981 resultHtml += '<p><strong>Tags Processed:</strong> ' + response.data.tags_processed + '</p>';
982
983 if (response.data.details && response.data.details.length > 0) {
984 resultHtml += '<ul>';
985 response.data.details.forEach(function(detail) {
986 resultHtml += '<li>' + detail + '</li>';
987 });
988 resultHtml += '</ul>';
989 }
990
991 $result.html(resultHtml).show();
992 showNotification('success', 'Bulk update completed successfully!');
993 } else {
994 $result.removeClass('success').addClass('error');
995 $result.html('<h5>Update Failed</h5><p>' + response.data + '</p>').show();
996 }
997 },
998 error: function() {
999 $progress.hide();
1000 $btn.prop('disabled', false);
1001 $result.removeClass('success').addClass('error');
1002 $result.html('<h5>Network Error</h5><p>Please try again.</p>').show();
1003 }
1004 });
1005 });
1006
1007 function loadTagRoleMappings() {
1008 const $container = $('#mxchat-mappings-container');
1009 $container.html('<div class="mxchat-loading-mappings"><span class="mxchat-role-spinner is-active"></span> Loading mappings...</div>');
1010
1011 $.ajax({
1012 url: ajaxurl,
1013 type: 'POST',
1014 data: {
1015 action: 'mxchat_get_tag_role_mappings',
1016 nonce: mxchatAdmin.settings_nonce
1017 },
1018 success: function(response) {
1019 if (response.success && response.data.mappings.length > 0) {
1020 $('#mxchat-no-mappings').hide();
1021
1022 let html = '<table class="mxchat-mappings-table">';
1023 html += '<thead><tr><th>Tag</th><th>Role Restriction</th><th>Posts with Tag</th><th>Actions</th></tr></thead><tbody>';
1024
1025 response.data.mappings.forEach(function(mapping) {
1026 html += '<tr>';
1027 html += '<td><span class="mxchat-tag-badge"><span class="dashicons dashicons-tag"></span>' + mapping.tag_slug + '</span></td>';
1028 html += '<td><span class="mxchat-role-badge ' + mapping.role_restriction + '">' + mapping.role_label + '</span></td>';
1029 html += '<td><span class="mxchat-post-count"><span class="dashicons dashicons-admin-post"></span>' + mapping.post_count + '</span></td>';
1030 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>';
1031 html += '</tr>';
1032 });
1033
1034 html += '</tbody></table>';
1035 $container.html(html);
1036 } else {
1037 $container.html('');
1038 $('#mxchat-no-mappings').show();
1039 }
1040 },
1041 error: function() {
1042 $container.html('<div class="mxchat-error">Failed to load mappings. Please refresh the page.</div>');
1043 }
1044 });
1045 }
1046
1047 // ========================================
1048 // PINECONE DELETE HANDLER (Keep existing code)
1049 // ========================================
1050
1051 $(document).on('click', '.delete-button-ajax', function(e) {
1052 e.preventDefault();
1053
1054 if (!confirm('Are you sure you want to delete this entry?')) {
1055 return;
1056 }
1057
1058 var $button = $(this);
1059 var $row = $button.closest('tr');
1060 var vectorId = $button.data('vector-id');
1061 var botId = $button.data('bot-id') || 'default';
1062 var nonce = $button.data('nonce');
1063
1064 $button.prop('disabled', true);
1065 $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update-alt');
1066 $row.addClass('mxchat-row-deleting');
1067
1068 $.ajax({
1069 url: ajaxurl,
1070 type: 'POST',
1071 data: {
1072 action: 'mxchat_delete_pinecone_prompt',
1073 nonce: nonce,
1074 vector_id: vectorId,
1075 bot_id: botId
1076 },
1077 success: function(response) {
1078 if (response.success) {
1079 $row.fadeOut(500, function() {
1080 $(this).remove();
1081
1082 var $countSpan = $('.mxchat-record-count');
1083 if ($countSpan.length) {
1084 var currentText = $countSpan.text();
1085 var matches = currentText.match(/\((\d+)/);
1086 if (matches) {
1087 var currentCount = parseInt(matches[1]);
1088 var newCount = Math.max(0, currentCount - 1);
1089 $countSpan.text($countSpan.text().replace(/\(\d+/, '(' + newCount));
1090 }
1091 }
1092 });
1093
1094 $('<div class="notice notice-success is-dismissible"><p>Entry deleted successfully from Pinecone.</p></div>')
1095 .insertAfter('.mxchat-hero')
1096 .delay(3000)
1097 .fadeOut();
1098 } else {
1099 $button.prop('disabled', false);
1100 $button.find('.dashicons').removeClass('dashicons-update-alt').addClass('dashicons-trash');
1101 $row.removeClass('mxchat-row-deleting');
1102 alert('Error: ' + response.data);
1103 }
1104 },
1105 error: function() {
1106 $button.prop('disabled', false);
1107 $button.find('.dashicons').removeClass('dashicons-update-alt').addClass('dashicons-trash');
1108 $row.removeClass('mxchat-row-deleting');
1109 alert('Network error occurred');
1110 }
1111 });
1112 });
1113
1114 // ========================================
1115 // ACCORDION FUNCTIONALITY
1116 // ========================================
1117
1118 // Handle expand/collapse toggle
1119 $(document).on('click', '.mxchat-expand-toggle', function(e) {
1120 e.preventDefault();
1121 e.stopPropagation();
1122
1123 const $button = $(this);
1124 const $wrapper = $button.closest('.mxchat-accordion-wrapper');
1125 const $preview = $wrapper.find('.mxchat-content-preview');
1126 const $fullContent = $wrapper.find('.mxchat-content-full');
1127
1128 // Toggle expanded state
1129 if ($fullContent.is(':visible')) {
1130 // Collapse
1131 $fullContent.slideUp(300);
1132 $button.removeClass('expanded');
1133 } else {
1134 // Expand
1135 $fullContent.slideDown(300);
1136 $button.addClass('expanded');
1137 }
1138 });
1139
1140 // Click anywhere on preview to toggle (expand or collapse)
1141 $(document).on('click', '.mxchat-content-preview', function(e) {
1142 // Only trigger if not clicking the button directly
1143 if (!$(e.target).closest('.mxchat-expand-toggle').length) {
1144 const $preview = $(this);
1145 const $wrapper = $preview.closest('.mxchat-accordion-wrapper');
1146 const $button = $preview.find('.mxchat-expand-toggle');
1147
1148 // Only trigger if there's a button (meaning content is long enough to expand)
1149 if ($button.length) {
1150 $button.trigger('click');
1151 }
1152 }
1153 });
1154 });