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
← All changes | js/knowledge-processing.js +38 -1271 3.2.152.5.5 View file →
@@ -85,14 +85,14 @@
85 85
86 86 // Form submission handler - triggers queue processing
87 87 $('#mxchat-url-form').on('submit', function(e) {
88 88 //console.log('MxChat: Form submitted, queue will be created');
89 -
89 +
90 90 // Don't prevent default - let form submit normally
91 91 // But schedule a check after redirect
92 92 localStorage.setItem('mxchat_check_queue_after_submit', Date.now().toString());
93 93 });
94 -
94 +
95 95 // Check if we just submitted a form and need to start processing
96 96 const justSubmitted = localStorage.getItem('mxchat_check_queue_after_submit');
97 97 if (justSubmitted) {
98 98 const submitTime = parseInt(justSubmitted);
@@ -174,26 +174,21 @@
174 174 if (isProcessingQueue) {
175 175 //console.log('MxChat: Already processing a queue, skipping');
176 176 return;
177 177 }
178 -
178 +
179 179 isProcessingQueue = true;
180 180 currentQueueId = queueId;
181 181 currentQueueType = queueType;
182 -
182 +
183 183 //console.log('MxChat: Starting queue processing:', queueId, queueType);
184 -
184 +
185 185 // Remove any "waiting" messages
186 186 $('.mxchat-processing-message').remove();
187 -
187 +
188 188 // Create or update status card
189 189 createOrUpdateStatusCard(queueType);
190 190
191 - // Start real-time entries polling if function exists
192 - if (typeof startEntriesPolling === 'function') {
193 - startEntriesPolling();
194 - }
195 -
196 191 // Start the batch processing loop
197 192 processNextBatch();
198 193 }
199 194
@@ -387,17 +382,11 @@
387 382 * NO AUTO-REFRESH - Show completed card with errors until dismissed
388 383 */
389 384 function handleQueueComplete() {
390 385 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 -
386 +
387 + //console.log('MxChat: Queue processing completed - showing final results');
388 +
400 389 // Get final status with error details
401 390 $.ajax({
402 391 url: ajaxurl,
403 392 type: 'POST',
@@ -495,18 +484,20 @@
495 484 if (status.failed > 0 && status.failed_items && status.failed_items.length > 0) {
496 485 detailsHtml += '<div class="mxchat-error-summary" style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-top: 15px;">';
497 486 detailsHtml += '<h4 style="margin: 0 0 10px 0; color: #856404;">⚠️ Failed Pages</h4>';
498 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 +
499 491 detailsHtml += '<div style="margin-top: 10px; max-height: 400px; overflow-y: auto;">';
500 492 detailsHtml += '<table style="width: 100%; border-collapse: collapse;">';
501 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>';
502 494 detailsHtml += '<tbody>';
503 -
495 +
504 496 status.failed_items.forEach(function(item) {
505 - let data;
506 - try { data = JSON.parse(item.item_data); } catch(e) { data = {}; }
497 + const data = JSON.parse(item.item_data);
507 498 const pageNum = data.page_number || 'Unknown';
508 -
499 +
509 500 detailsHtml += '<tr style="border-bottom: 1px solid #ddd;">';
510 501 detailsHtml += '<td style="padding: 8px;">Page ' + pageNum + '</td>';
511 502 detailsHtml += '<td style="padding: 8px; word-break: break-word;">' + (item.error_message || 'Unknown error') + '</td>';
512 503 detailsHtml += '<td style="padding: 8px;">' + item.attempts + '</td>';
@@ -511,12 +502,13 @@
511 502 detailsHtml += '<td style="padding: 8px; word-break: break-word;">' + (item.error_message || 'Unknown error') + '</td>';
512 503 detailsHtml += '<td style="padding: 8px;">' + item.attempts + '</td>';
513 504 detailsHtml += '</tr>';
514 505 });
515 -
506 +
516 507 detailsHtml += '</tbody></table>';
517 508 detailsHtml += '</div>';
518 -
509 + detailsHtml += '</details>';
510 +
519 511 detailsHtml += '</div>';
520 512 }
521 513
522 514 $card.find('.mxchat-status-details').html(detailsHtml);
@@ -571,20 +563,21 @@
571 563 if (status.failed > 0 && status.failed_items && status.failed_items.length > 0) {
572 564 detailsHtml += '<div class="mxchat-error-summary" style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-top: 15px;">';
573 565 detailsHtml += '<h4 style="margin: 0 0 10px 0; color: #856404;">⚠️ Failed URLs</h4>';
574 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 +
575 570 detailsHtml += '<div style="margin-top: 10px; max-height: 400px; overflow-y: auto;">';
576 571 detailsHtml += '<table style="width: 100%; border-collapse: collapse;">';
577 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>';
578 573 detailsHtml += '<tbody>';
579 -
574 +
580 575 status.failed_items.forEach(function(item) {
581 - let data;
582 - try { data = JSON.parse(item.item_data); } catch(e) { data = {}; }
583 - const url = data.url || data.pdf_url || 'Unknown URL';
584 - const pageInfo = data.page_number ? ' (page ' + data.page_number + ')' : '';
585 - const displayUrl = (url.length > 60 ? url.substring(0, 57) + '...' : url) + pageInfo;
586 -
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 +
587 580 detailsHtml += '<tr style="border-bottom: 1px solid #ddd;">';
588 581 detailsHtml += '<td style="padding: 8px;"><a href="' + url + '" target="_blank" style="color: #0073aa; text-decoration: none;">' + displayUrl + '</a></td>';
589 582 detailsHtml += '<td style="padding: 8px; word-break: break-word;">' + (item.error_message || 'Unknown error') + '</td>';
590 583 detailsHtml += '<td style="padding: 8px;">' + item.attempts + '</td>';
@@ -589,15 +582,16 @@
589 582 detailsHtml += '<td style="padding: 8px; word-break: break-word;">' + (item.error_message || 'Unknown error') + '</td>';
590 583 detailsHtml += '<td style="padding: 8px;">' + item.attempts + '</td>';
591 584 detailsHtml += '</tr>';
592 585 });
593 -
586 +
594 587 detailsHtml += '</tbody></table>';
595 588 detailsHtml += '</div>';
596 -
589 + detailsHtml += '</details>';
590 +
597 591 detailsHtml += '</div>';
598 592 }
599 -
593 +
600 594 $card.find('.mxchat-status-details').html(detailsHtml);
601 595 }
602 596
603 597 /**
@@ -1083,20 +1077,21 @@
1083 1077 success: function(response) {
1084 1078 if (response.success) {
1085 1079 $row.fadeOut(500, function() {
1086 1080 $(this).remove();
1087 - // Update entry count displays (header and sidebar)
1088 - var $countSpan = $('#mxchat-entry-count');
1081 +
1082 + var $countSpan = $('.mxchat-record-count');
1089 1083 if ($countSpan.length) {
1090 1084 var currentText = $countSpan.text();
1091 - var match = currentText.match(/\((\d+)\)/);
1092 - if (match) {
1093 - var newCount = Math.max(0, parseInt(match[1]) - 1);
1094 - updateEntryCount(newCount);
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));
1095 1090 }
1096 1091 }
1097 1092 });
1098 -
1093 +
1099 1094 $('<div class="notice notice-success is-dismissible"><p>Entry deleted successfully from Pinecone.</p></div>')
1100 1095 .insertAfter('.mxchat-hero')
1101 1096 .delay(3000)
1102 1097 .fadeOut();
@@ -1116,440 +1111,8 @@
1116 1111 });
1117 1112 });
1118 1113
1119 1114 // ========================================
1120 - // WORDPRESS DATABASE DELETE HANDLER (AJAX)
1121 - // ========================================
1122 -
1123 - $(document).on('click', '.delete-button-wordpress', function(e) {
1124 - e.preventDefault();
1125 -
1126 - if (!confirm('Are you sure you want to delete this entry?')) {
1127 - return;
1128 - }
1129 -
1130 - var $button = $(this);
1131 - var $row = $button.closest('tr');
1132 - var entryId = $button.data('entry-id');
1133 - var nonce = $button.data('nonce');
1134 -
1135 - $button.prop('disabled', true);
1136 - $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update-alt spin');
1137 - $row.addClass('mxchat-row-deleting');
1138 -
1139 - $.ajax({
1140 - url: ajaxurl,
1141 - type: 'POST',
1142 - data: {
1143 - action: 'mxchat_delete_wordpress_prompt',
1144 - nonce: nonce,
1145 - entry_id: entryId
1146 - },
1147 - success: function(response) {
1148 - if (response.success) {
1149 - $row.fadeOut(500, function() {
1150 - $(this).remove();
1151 - // Update entry count displays (header and sidebar)
1152 - var $countSpan = $('#mxchat-entry-count');
1153 - if ($countSpan.length) {
1154 - var currentText = $countSpan.text();
1155 - var match = currentText.match(/\((\d+)\)/);
1156 - if (match) {
1157 - var newCount = Math.max(0, parseInt(match[1]) - 1);
1158 - updateEntryCount(newCount);
1159 - }
1160 - }
1161 - });
1162 -
1163 - $('<div class="notice notice-success is-dismissible"><p>Entry deleted successfully.</p></div>')
1164 - .insertAfter('.mxchat-hero')
1165 - .delay(3000)
1166 - .fadeOut();
1167 - } else {
1168 - $button.prop('disabled', false);
1169 - $button.find('.dashicons').removeClass('dashicons-update-alt spin').addClass('dashicons-trash');
1170 - $row.removeClass('mxchat-row-deleting');
1171 - alert('Error: ' + response.data);
1172 - }
1173 - },
1174 - error: function() {
1175 - $button.prop('disabled', false);
1176 - $button.find('.dashicons').removeClass('dashicons-update-alt spin').addClass('dashicons-trash');
1177 - $row.removeClass('mxchat-row-deleting');
1178 - alert('Network error occurred');
1179 - }
1180 - });
1181 - });
1182 -
1183 - // ========================================
1184 - // BULK SELECTION FOR KNOWLEDGE ENTRIES
1185 - // ========================================
1186 -
1187 - var selectedKnowledgeEntries = new Set();
1188 -
1189 - // Update selection UI
1190 - function updateKnowledgeSelectionUI() {
1191 - var count = selectedKnowledgeEntries.size;
1192 - var $countEl = $('#mxchat-selected-entry-count');
1193 - var $deleteBtn = $('#mxchat-delete-selected-entries');
1194 - var $deleteAllForm = $('#mxchat-delete-all-form');
1195 -
1196 - if (count > 0) {
1197 - // Show Delete Selected button, hide Delete All form
1198 - $deleteAllForm.hide();
1199 - $deleteBtn.show();
1200 - $countEl.text('(' + count + ')');
1201 - } else {
1202 - // Show Delete All form, hide Delete Selected button
1203 - $deleteAllForm.show();
1204 - $deleteBtn.hide();
1205 - $countEl.text('');
1206 - }
1207 -
1208 - // Update select all checkbox state
1209 - var totalItems = $('.mxchat-entry-checkbox').length;
1210 - var checkedItems = $('.mxchat-entry-checkbox:checked').length;
1211 - $('.mxchat-entry-checkbox-all').prop('checked', totalItems > 0 && checkedItems === totalItems);
1212 - $('.mxchat-entry-checkbox-all').prop('indeterminate', checkedItems > 0 && checkedItems < totalItems);
1213 - }
1214 -
1215 - // Select all checkbox handler
1216 - $(document).on('change', '.mxchat-entry-checkbox-all', function() {
1217 - var isChecked = $(this).is(':checked');
1218 -
1219 - // Sync all select-all checkboxes
1220 - $('.mxchat-entry-checkbox-all').prop('checked', isChecked);
1221 -
1222 - $('.mxchat-entry-checkbox').prop('checked', isChecked);
1223 -
1224 - if (isChecked) {
1225 - $('.mxchat-entry-checkbox').each(function() {
1226 - var entryData = {
1227 - id: $(this).data('entry-id'),
1228 - source: $(this).data('source'),
1229 - sourceUrl: $(this).data('source-url'),
1230 - isGroup: $(this).data('is-group'),
1231 - chunkCount: $(this).data('chunk-count') || 1
1232 - };
1233 - selectedKnowledgeEntries.add(JSON.stringify(entryData));
1234 - $(this).closest('tr').addClass('selected');
1235 - });
1236 - } else {
1237 - selectedKnowledgeEntries.clear();
1238 - $('tr.selected').removeClass('selected');
1239 - }
1240 -
1241 - updateKnowledgeSelectionUI();
1242 - });
1243 -
1244 - // Individual checkbox handler
1245 - $(document).on('change', '.mxchat-entry-checkbox', function() {
1246 - var $checkbox = $(this);
1247 - var $row = $checkbox.closest('tr');
1248 - var entryData = {
1249 - id: $checkbox.data('entry-id'),
1250 - source: $checkbox.data('source'),
1251 - sourceUrl: $checkbox.data('source-url'),
1252 - isGroup: $checkbox.data('is-group'),
1253 - chunkCount: $checkbox.data('chunk-count') || 1
1254 - };
1255 - var entryKey = JSON.stringify(entryData);
1256 -
1257 - if ($checkbox.is(':checked')) {
1258 - selectedKnowledgeEntries.add(entryKey);
1259 - $row.addClass('selected');
1260 - } else {
1261 - selectedKnowledgeEntries.delete(entryKey);
1262 - $row.removeClass('selected');
1263 - }
1264 -
1265 - updateKnowledgeSelectionUI();
1266 - });
1267 -
1268 - // Bulk delete button handler
1269 - $(document).on('click', '#mxchat-delete-selected-entries', function() {
1270 - var count = selectedKnowledgeEntries.size;
1271 - if (count === 0) return;
1272 -
1273 - if (!confirm('Are you sure you want to delete ' + count + ' selected entries? This action cannot be undone.')) {
1274 - return;
1275 - }
1276 -
1277 - var $button = $(this);
1278 - var nonce = $button.data('nonce');
1279 - var botId = $button.data('bot-id');
1280 -
1281 - // Parse selected entries
1282 - var entries = Array.from(selectedKnowledgeEntries).map(function(entryStr) {
1283 - return JSON.parse(entryStr);
1284 - });
1285 -
1286 - // Show loading state
1287 - $button.prop('disabled', true);
1288 - $button.find('.mxchat-bulk-delete-text').text('Deleting...');
1289 - $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update spin');
1290 -
1291 - // Mark rows as deleting
1292 - entries.forEach(function(entry) {
1293 - $('#prompt-' + entry.id).addClass('mxchat-row-deleting');
1294 - });
1295 -
1296 - $.ajax({
1297 - url: ajaxurl,
1298 - type: 'POST',
1299 - timeout: 120000, // 120 seconds — matches server-side set_time_limit
1300 - data: {
1301 - action: 'mxchat_bulk_delete_knowledge',
1302 - entries: entries,
1303 - bot_id: botId,
1304 - nonce: nonce
1305 - },
1306 - success: function(response) {
1307 - if (response.success) {
1308 - var data = response.data;
1309 -
1310 - // Remove successful rows
1311 - if (data.success_ids && data.success_ids.length > 0) {
1312 - data.success_ids.forEach(function(id) {
1313 - var $row = $('#prompt-' + id);
1314 - // Also remove child chunk rows if it's a group
1315 - var groupId = $row.data('group-id');
1316 - if (groupId) {
1317 - $('.mxchat-chunk-row.' + groupId).fadeOut(300, function() {
1318 - $(this).remove();
1319 - });
1320 - }
1321 - $row.fadeOut(300, function() {
1322 - $(this).remove();
1323 - });
1324 - });
1325 - }
1326 -
1327 - // Handle failed entries
1328 - if (data.failed_ids && data.failed_ids.length > 0) {
1329 - data.failed_ids.forEach(function(id) {
1330 - $('#prompt-' + id).removeClass('mxchat-row-deleting').addClass('mxchat-row-error');
1331 - });
1332 - }
1333 -
1334 - // Show result message
1335 - var successCount = data.success_ids ? data.success_ids.length : 0;
1336 - var failedCount = data.failed_ids ? data.failed_ids.length : 0;
1337 - var message = 'Deleted ' + successCount + ' entries.';
1338 - if (failedCount > 0) {
1339 - message += ' ' + failedCount + ' entries failed to delete.';
1340 - }
1341 -
1342 - $('<div class="notice notice-success is-dismissible"><p>' + message + '</p></div>')
1343 - .insertAfter('.mxchat-hero')
1344 - .delay(5000)
1345 - .fadeOut();
1346 -
1347 - // Clear selection
1348 - selectedKnowledgeEntries.clear();
1349 - updateKnowledgeSelectionUI();
1350 -
1351 - // Update entry count displays (header and sidebar)
1352 - if (successCount > 0) {
1353 - var $countSpan = $('#mxchat-entry-count');
1354 - if ($countSpan.length) {
1355 - var currentText = $countSpan.text();
1356 - var match = currentText.match(/\((\d+)\)/);
1357 - if (match) {
1358 - var newCount = Math.max(0, parseInt(match[1]) - successCount);
1359 - updateEntryCount(newCount);
1360 - }
1361 - }
1362 - }
1363 -
1364 - } else {
1365 - alert('Error: ' + (response.data || 'Unknown error'));
1366 - $('tr.mxchat-row-deleting').removeClass('mxchat-row-deleting');
1367 - }
1368 - },
1369 - error: function(jqXHR, textStatus) {
1370 - var message = 'An error occurred while deleting entries.';
1371 - if (textStatus === 'timeout') {
1372 - message = 'The deletion request timed out. Please refresh the page to check which entries were deleted, then try again for any remaining.';
1373 - } else if (textStatus === 'error' && jqXHR.status === 0) {
1374 - message = 'Network error: The server took too long to respond. Please refresh and try deleting fewer entries at a time.';
1375 - } else if (jqXHR.responseJSON && jqXHR.responseJSON.data) {
1376 - message = 'Error: ' + jqXHR.responseJSON.data;
1377 - }
1378 - alert(message);
1379 - $('tr.mxchat-row-deleting').removeClass('mxchat-row-deleting');
1380 - },
1381 - complete: function() {
1382 - $button.prop('disabled', selectedKnowledgeEntries.size === 0);
1383 - $button.find('.mxchat-bulk-delete-text').text('Delete Selected');
1384 - $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash');
1385 - }
1386 - });
1387 - });
1388 -
1389 - // Clear selection when page changes
1390 - $(document).on('click', '.mxchat-page-link', function() {
1391 - selectedKnowledgeEntries.clear();
1392 - updateKnowledgeSelectionUI();
1393 - });
1394 -
1395 - // ========================================
1396 - // AJAX PAGINATION FOR KNOWLEDGE BASE ENTRIES
1397 - // ========================================
1398 -
1399 - // Handle pagination link clicks
1400 - $(document).on('click', '.mxchat-page-link', function(e) {
1401 - e.preventDefault();
1402 -
1403 - var $link = $(this);
1404 - var page = $link.data('page');
1405 - var $paginationWrapper = $('#mxchat-kb-pagination');
1406 - var $tbody = $('#mxchat-entries-tbody');
1407 -
1408 - if (!page || $link.hasClass('loading')) {
1409 - return;
1410 - }
1411 -
1412 - // Show loading state
1413 - $link.addClass('loading');
1414 - $tbody.css('opacity', '0.5');
1415 -
1416 - // Add loading indicator to pagination
1417 - var $loadingIndicator = $('<span class="mxchat-pagination-loading"><span class="dashicons dashicons-update spin"></span></span>');
1418 - $paginationWrapper.find('.mxchat-ajax-pagination').append($loadingIndicator);
1419 -
1420 - // Get search and filter values from pagination wrapper
1421 - var searchQuery = $paginationWrapper.data('search') || '';
1422 - var contentType = $paginationWrapper.data('content-type') || '';
1423 -
1424 - $.ajax({
1425 - url: ajaxurl,
1426 - type: 'POST',
1427 - data: {
1428 - action: 'mxchat_paginate_entries',
1429 - nonce: mxchatAdmin.entries_nonce,
1430 - bot_id: mxchatAdmin.bot_id || 'default',
1431 - page: page,
1432 - search: searchQuery,
1433 - content_type: contentType
1434 - },
1435 - success: function(response) {
1436 - $link.removeClass('loading');
1437 - $tbody.css('opacity', '1');
1438 - $loadingIndicator.remove();
1439 -
1440 - if (response.success && response.data) {
1441 - // Update the table body with new HTML
1442 - $tbody.html(response.data.html);
1443 -
1444 - // Update the pagination - find the inner container
1445 - if (response.data.pagination_html) {
1446 - // Replace the inner pagination div content
1447 - $paginationWrapper.html(response.data.pagination_html);
1448 - }
1449 -
1450 - // Update data attributes on wrapper (preserve search/filter for next pagination)
1451 - $paginationWrapper.attr('data-current-page', response.data.page);
1452 - if (response.data.total_pages) {
1453 - $paginationWrapper.attr('data-total-pages', response.data.total_pages);
1454 - }
1455 - // Preserve search and content_type on the wrapper from the inner pagination div
1456 - var $innerPagination = $paginationWrapper.find('.mxchat-ajax-pagination');
1457 - if ($innerPagination.length) {
1458 - $paginationWrapper.attr('data-search', $innerPagination.data('search') || '');
1459 - $paginationWrapper.attr('data-content-type', $innerPagination.data('content-type') || '');
1460 - }
1461 -
1462 - // Scroll to top of the table smoothly
1463 - $('html, body').animate({
1464 - scrollTop: $('#knowledge-base').offset().top - 50
1465 - }, 300);
1466 -
1467 - // Update URL hash to stay on knowledge-base tab
1468 - if (window.history && window.history.replaceState) {
1469 - window.history.replaceState(null, '', window.location.pathname + window.location.search + '#knowledge-base');
1470 - }
1471 -
1472 - // Show success feedback
1473 - showNotification('success', 'Page ' + response.data.page + ' loaded');
1474 - } else {
1475 - showNotification('error', 'Failed to load page: ' + (response.data?.message || 'Unknown error'));
1476 - }
1477 - },
1478 - error: function(xhr, status, error) {
1479 - $link.removeClass('loading');
1480 - $tbody.css('opacity', '1');
1481 - $loadingIndicator.remove();
1482 - showNotification('error', 'Network error while loading page');
1483 - }
1484 - });
1485 - });
1486 -
1487 - // ========================================
1488 - // PINECONE REFRESH ENTRIES BUTTON
1489 - // ========================================
1490 -
1491 - $('#mxchat-refresh-pinecone-entries').on('click', function() {
1492 - var $button = $(this);
1493 - var $icon = $button.find('.dashicons');
1494 - var $tbody = $('#mxchat-entries-tbody');
1495 - var $paginationWrapper = $('#mxchat-kb-pagination');
1496 -
1497 - // Show loading state
1498 - $button.prop('disabled', true);
1499 - $icon.addClass('spin');
1500 - $tbody.css('opacity', '0.5');
1501 -
1502 - $.ajax({
1503 - url: ajaxurl,
1504 - type: 'POST',
1505 - data: {
1506 - action: 'mxchat_refresh_pinecone_entries',
1507 - nonce: mxchatAdmin.entries_nonce,
1508 - bot_id: mxchatAdmin.bot_id || 'default',
1509 - page: 1
1510 - },
1511 - success: function(response) {
1512 - $button.prop('disabled', false);
1513 - $icon.removeClass('spin');
1514 - $tbody.css('opacity', '1');
1515 -
1516 - if (response.success && response.data) {
1517 - // Update the table body with new HTML
1518 - $tbody.html(response.data.html);
1519 -
1520 - // Update the pagination (same pattern as refreshKnowledgeBaseTable)
1521 - if ($paginationWrapper.length) {
1522 - if (response.data.pagination_html) {
1523 - $paginationWrapper.html(response.data.pagination_html);
1524 - $paginationWrapper.attr('style', 'padding: 16px; border-top: 1px solid var(--mxch-card-border); text-align: center;');
1525 - } else {
1526 - $paginationWrapper.html('');
1527 - $paginationWrapper.attr('style', '');
1528 - }
1529 - }
1530 -
1531 - // Update the count display
1532 - if (response.data.total_count !== undefined) {
1533 - updateEntryCount(response.data.total_count);
1534 - }
1535 -
1536 - // Show success feedback
1537 - showNotification('success', 'Entries refreshed successfully!');
1538 - } else {
1539 - showNotification('error', 'Failed to refresh entries: ' + (response.data?.message || 'Unknown error'));
1540 - }
1541 - },
1542 - error: function(xhr, status, error) {
1543 - $button.prop('disabled', false);
1544 - $icon.removeClass('spin');
1545 - $tbody.css('opacity', '1');
1546 - showNotification('error', 'Network error while refreshing entries');
1547 - }
1548 - });
1549 - });
1550 -
1551 - // ========================================
1552 1115 // ACCORDION FUNCTIONALITY
1553 1116 // ========================================
1554 1117
1555 1118 // Handle expand/collapse toggle
@@ -1587,801 +1150,5 @@
1587 1150 $button.trigger('click');
1588 1151 }
1589 1152 }
1590 1153 });
1591 -
1592 - // ========================================
1593 - // CHUNK GROUP TOGGLE FUNCTIONALITY
1594 - // ========================================
1595 -
1596 - // Handle chunk group expand/collapse toggle
1597 - $(document).on('click', '.mxchat-chunk-toggle', function(e) {
1598 - e.preventDefault();
1599 - e.stopPropagation();
1600 -
1601 - const $button = $(this);
1602 - const groupId = $button.data('group-id');
1603 - const $chunkRows = $('.mxchat-chunk-row.' + groupId);
1604 -
1605 - // Toggle expanded state
1606 - if ($button.hasClass('expanded')) {
1607 - // Collapse
1608 - $chunkRows.slideUp(300);
1609 - $button.removeClass('expanded');
1610 - } else {
1611 - // Expand
1612 - $chunkRows.slideDown(300);
1613 - $button.addClass('expanded');
1614 - }
1615 - });
1616 -
1617 - // Handle delete button for chunk groups
1618 - $(document).on('click', '.delete-button-group', function(e) {
1619 - e.preventDefault();
1620 - e.stopPropagation();
1621 -
1622 - const $button = $(this);
1623 - const sourceUrl = $button.data('source-url');
1624 - const chunkCount = $button.data('chunk-count');
1625 - const dataSource = $button.data('data-source');
1626 - const botId = $button.data('bot-id');
1627 - const nonce = $button.data('nonce');
1628 -
1629 - // Confirm deletion
1630 - if (!confirm('Are you sure you want to delete all ' + chunkCount + ' chunks for this URL? This action cannot be undone.')) {
1631 - return;
1632 - }
1633 -
1634 - // Show loading state
1635 - $button.prop('disabled', true);
1636 - $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update spin');
1637 -
1638 - // Make AJAX request to delete all chunks for this URL
1639 - $.ajax({
1640 - url: ajaxurl,
1641 - type: 'POST',
1642 - data: {
1643 - action: 'mxchat_delete_chunks_by_url',
1644 - source_url: sourceUrl,
1645 - data_source: dataSource,
1646 - bot_id: botId,
1647 - nonce: nonce
1648 - },
1649 - success: function(response) {
1650 - if (response.success) {
1651 - // Remove the group header row and all chunk rows
1652 - const $headerRow = $button.closest('tr');
1653 - const groupId = $headerRow.data('group-id');
1654 - $('.mxchat-chunk-row.' + groupId).fadeOut(300, function() {
1655 - $(this).remove();
1656 - });
1657 - $headerRow.fadeOut(300, function() {
1658 - $(this).remove();
1659 - });
1660 - } else {
1661 - var errorMsg = response.data || 'Unknown error';
1662 - if (typeof response.data === 'object' && response.data.message) {
1663 - errorMsg = response.data.message;
1664 - }
1665 - alert('Error deleting chunks: ' + errorMsg);
1666 - $button.prop('disabled', false);
1667 - $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash');
1668 - }
1669 - },
1670 - error: function(xhr, status, error) {
1671 - console.error('AJAX error:', xhr.responseText);
1672 - alert('Error deleting chunks: ' + (error || 'Server error'));
1673 - $button.prop('disabled', false);
1674 - $button.find('.dashicons').removeClass('dashicons-update spin').addClass('dashicons-trash');
1675 - }
1676 - });
1677 - });
1678 -
1679 - // ========================================
1680 - // REAL-TIME KNOWLEDGE ENTRIES UPDATE
1681 - // ========================================
1682 -
1683 - let lastEntryId = 0;
1684 - let entriesPollingInterval = null;
1685 - let isEntriesPolling = false;
1686 -
1687 - // Initialize: get the highest ID from the current table
1688 - function initializeLastEntryId() {
1689 - const $tbody = $('#mxchat-entries-tbody');
1690 - if ($tbody.length === 0) return;
1691 -
1692 - // Get the highest ID from the table
1693 - $tbody.find('tr').each(function() {
1694 - const idText = $(this).find('td:first').text().trim();
1695 - const id = parseInt(idText);
1696 - if (!isNaN(id) && id > lastEntryId) {
1697 - lastEntryId = id;
1698 - }
1699 - });
1700 - }
1701 -
1702 - // Poll for new entries during processing
1703 - function startEntriesPolling() {
1704 - if (entriesPollingInterval) return; // Already polling
1705 -
1706 - isEntriesPolling = true;
1707 -
1708 - // Fetch immediately, then start interval
1709 - fetchNewEntries();
1710 -
1711 - entriesPollingInterval = setInterval(function() {
1712 - fetchNewEntries();
1713 - }, 3000); // Poll every 3 seconds
1714 - }
1715 -
1716 - function stopEntriesPolling() {
1717 - if (entriesPollingInterval) {
1718 - clearInterval(entriesPollingInterval);
1719 - entriesPollingInterval = null;
1720 - }
1721 - isEntriesPolling = false;
1722 - }
1723 -
1724 - // Track Pinecone count for change detection
1725 - var lastPineconeCount = 0;
1726 - var pineconeRefreshPending = false;
1727 -
1728 - // Fetch new entries from server
1729 - function fetchNewEntries() {
1730 - if (!mxchatAdmin.entries_nonce) {
1731 - return;
1732 - }
1733 -
1734 - $.ajax({
1735 - url: ajaxurl,
1736 - type: 'POST',
1737 - data: {
1738 - action: 'mxchat_get_recent_entries',
1739 - nonce: mxchatAdmin.entries_nonce,
1740 - last_id: lastEntryId,
1741 - bot_id: mxchatAdmin.bot_id || 'default',
1742 - limit: 20
1743 - },
1744 - success: function(response) {
1745 - if (response.success && response.data) {
1746 - // Update count
1747 - if (response.data.total_count !== undefined) {
1748 - updateEntryCount(response.data.total_count);
1749 - }
1750 -
1751 - // Handle Pinecone data source differently
1752 - if (response.data.data_source === 'pinecone') {
1753 - var newCount = response.data.total_count || 0;
1754 -
1755 - // If count changed and we haven't scheduled a refresh yet
1756 - if (newCount !== lastPineconeCount && !pineconeRefreshPending) {
1757 - lastPineconeCount = newCount;
1758 -
1759 - // Schedule a table refresh after processing completes
1760 - // Show a "refresh to see entries" message
1761 - var $tbody = $('#mxchat-entries-tbody');
1762 - var $refreshNotice = $tbody.find('.mxchat-pinecone-refresh-notice');
1763 -
1764 - if ($refreshNotice.length === 0 && newCount > 0) {
1765 - var noticeHtml = '<tr class="mxchat-pinecone-refresh-notice">' +
1766 - '<td colspan="4" style="padding: 20px; text-align: center; background: #f0f7ff; border-bottom: 1px solid var(--mxch-card-border);">' +
1767 - '<span class="dashicons dashicons-update" style="color: #7873f5; margin-right: 8px;"></span>' +
1768 - '<strong>' + newCount + ' entries in Pinecone.</strong> ' +
1769 - '<a href="#" class="mxchat-refresh-table-link" style="color: #7873f5; text-decoration: underline;">Refresh to see new entries</a>' +
1770 - '</td></tr>';
1771 - $tbody.prepend(noticeHtml);
1772 -
1773 - // Handle refresh link click
1774 - $tbody.find('.mxchat-refresh-table-link').on('click', function(e) {
1775 - e.preventDefault();
1776 - location.reload();
1777 - });
1778 - } else if ($refreshNotice.length > 0) {
1779 - // Update the count in existing notice
1780 - $refreshNotice.find('strong').text(newCount + ' entries in Pinecone.');
1781 - }
1782 - }
1783 - } else {
1784 - // WordPress DB - Track new entries and show refresh notice
1785 - // Don't add rows individually during processing as they need to be grouped by source_url
1786 - if (response.data.entries && response.data.entries.length > 0) {
1787 - // Update last ID to track progress
1788 - if (response.data.max_id > lastEntryId) {
1789 - lastEntryId = response.data.max_id;
1790 - }
1791 -
1792 - // Show/update refresh notice (similar to Pinecone handling)
1793 - var $tbody = $('#mxchat-entries-tbody');
1794 - var $refreshNotice = $tbody.find('.mxchat-wordpress-refresh-notice');
1795 - var newCount = response.data.total_count || 0;
1796 -
1797 - if ($refreshNotice.length === 0 && newCount > 0) {
1798 - var noticeHtml = '<tr class="mxchat-wordpress-refresh-notice mxchat-new-entry">' +
1799 - '<td colspan="4" style="padding: 16px 20px; text-align: center; background: linear-gradient(135deg, rgba(120, 115, 245, 0.08) 0%, rgba(167, 139, 250, 0.05) 100%); border-bottom: 1px solid var(--mxch-card-border);">' +
1800 - '<span class="dashicons dashicons-update spin" style="color: #7873f5; margin-right: 8px;"></span>' +
1801 - '<strong style="color: var(--mxch-text-primary);">Processing... <span class="mxchat-processing-count">' + newCount + '</span> entries</strong>' +
1802 - '</td></tr>';
1803 - $tbody.prepend(noticeHtml);
1804 - } else if ($refreshNotice.length > 0) {
1805 - // Update the count in existing notice
1806 - $refreshNotice.find('.mxchat-processing-count').text(newCount);
1807 - }
1808 - } else {
1809 - // Update last ID even if no new entries
1810 - if (response.data.max_id > lastEntryId) {
1811 - lastEntryId = response.data.max_id;
1812 - }
1813 - }
1814 - }
1815 - }
1816 - },
1817 - error: function(xhr, status, error) {
1818 - console.error('MxChat: Error fetching new entries:', error, xhr.responseText);
1819 - }
1820 - });
1821 - }
1822 -
1823 - // Update the entry count display
1824 - function updateEntryCount(count) {
1825 - // Update main table count
1826 - const $countSpan = $('#mxchat-entry-count');
1827 - if ($countSpan.length) {
1828 - $countSpan.text('(' + count + ')');
1829 -
1830 - // Flash animation to indicate update
1831 - $countSpan.addClass('mxchat-count-updated');
1832 - setTimeout(function() {
1833 - $countSpan.removeClass('mxchat-count-updated');
1834 - }, 1000);
1835 - }
1836 -
1837 - // Update sidebar badge count
1838 - const $sidebarCount = $('#mxchat-sidebar-count');
1839 - if ($sidebarCount.length) {
1840 - $sidebarCount.text(count);
1841 -
1842 - // Flash animation for sidebar too
1843 - $sidebarCount.addClass('mxchat-count-updated');
1844 - setTimeout(function() {
1845 - $sidebarCount.removeClass('mxchat-count-updated');
1846 - }, 1000);
1847 - }
1848 - }
1849 -
1850 - // Refresh the knowledge base table via AJAX pagination
1851 - // This ensures entries are properly grouped by source_url
1852 - function refreshKnowledgeBaseTable() {
1853 - var $paginationWrapper = $('#mxchat-kb-pagination');
1854 - var $tbody = $('#mxchat-entries-tbody');
1855 -
1856 - if ($tbody.length === 0) {
1857 - return;
1858 - }
1859 -
1860 - // Remove any processing notice
1861 - $tbody.find('.mxchat-wordpress-refresh-notice, .mxchat-pinecone-refresh-notice').remove();
1862 -
1863 - // Show loading state
1864 - $tbody.css('opacity', '0.5');
1865 -
1866 - $.ajax({
1867 - url: ajaxurl,
1868 - type: 'POST',
1869 - data: {
1870 - action: 'mxchat_paginate_entries',
1871 - nonce: mxchatAdmin.entries_nonce,
1872 - bot_id: mxchatAdmin.bot_id || 'default',
1873 - page: 1 // Always go to first page to see newest entries
1874 - },
1875 - success: function(response) {
1876 - $tbody.css('opacity', '1');
1877 -
1878 - if (response.success && response.data) {
1879 - // Update the table body with properly grouped HTML
1880 - $tbody.html(response.data.html);
1881 -
1882 - // Update the pagination
1883 - if ($paginationWrapper.length) {
1884 - if (response.data.pagination_html) {
1885 - // Add pagination content and styling
1886 - $paginationWrapper.html(response.data.pagination_html);
1887 - $paginationWrapper.attr('style', 'padding: 16px; border-top: 1px solid var(--mxch-card-border); text-align: center;');
1888 - } else {
1889 - // No pagination needed - clear and hide
1890 - $paginationWrapper.html('');
1891 - $paginationWrapper.attr('style', '');
1892 - }
1893 - }
1894 -
1895 - // Update count display
1896 - if (response.data.total_count !== undefined) {
1897 - updateEntryCount(response.data.total_count);
1898 - }
1899 - }
1900 - },
1901 - error: function(xhr, status, error) {
1902 - $tbody.css('opacity', '1');
1903 - console.error('MxChat: Error refreshing table:', error);
1904 - }
1905 - });
1906 - }
1907 -
1908 - // Expose refreshKnowledgeBaseTable for external access (content-selector.js)
1909 - window.refreshKnowledgeBaseTable = refreshKnowledgeBaseTable;
1910 -
1911 - // Add new entries to the table (kept for backwards compatibility but not used during processing)
1912 - function addNewEntriesToTable(entries) {
1913 - const $tbody = $('#mxchat-entries-tbody');
1914 - if ($tbody.length === 0) return;
1915 -
1916 - // Remove "no entries" message if present
1917 - const $noEntries = $tbody.find('td[colspan="4"]').closest('tr');
1918 - if ($noEntries.length) {
1919 - $noEntries.remove();
1920 - }
1921 -
1922 - // Add entries in reverse order (oldest first, so newest ends up at top)
1923 - entries.reverse().forEach(function(entry) {
1924 - // Check if entry already exists
1925 - if ($tbody.find('tr[data-entry-id="' + entry.id + '"]').length > 0) {
1926 - return;
1927 - }
1928 -
1929 - const sourceHtml = entry.has_link
1930 - ? '<a href="' + entry.source_url + '" target="_blank" style="color: var(--mxch-primary); text-decoration: none;"><span class="dashicons dashicons-external" style="font-size: 14px;"></span> View</a>'
1931 - : '<span style="color: var(--mxch-text-muted);">Manual</span>';
1932 -
1933 - const deleteUrl = mxchatAdmin.admin_url + 'admin-post.php?action=mxchat_delete_prompt&id=' + entry.id + '&_wpnonce=' + entry.delete_nonce;
1934 -
1935 - // Check if content needs expand button (content longer than preview)
1936 - const needsExpand = entry.content_length > entry.preview_length;
1937 -
1938 - // Build accordion-style content cell (matching initial page load structure)
1939 - let contentHtml = '<div class="mxchat-accordion-wrapper">' +
1940 - '<div class="mxchat-content-preview">' +
1941 - '<span class="preview-text">' + entry.preview + '</span>';
1942 -
1943 - if (needsExpand) {
1944 - contentHtml += '<button class="mxchat-expand-toggle" type="button">' +
1945 - '<span class="dashicons dashicons-arrow-down-alt2"></span>' +
1946 - '</button>';
1947 - }
1948 -
1949 - contentHtml += '</div>' +
1950 - '<div class="mxchat-content-full" style="display: none;">' +
1951 - '<div class="content-view">' + entry.full_content + '</div>' +
1952 - '</div>' +
1953 - '</div>';
1954 -
1955 - const $row = $('<tr id="prompt-' + entry.id + '" data-entry-id="' + entry.id + '" data-source="wordpress" style="border-bottom: 1px solid var(--mxch-card-border); display: none;">' +
1956 - '<td style="padding: 12px 16px; font-size: 13px;">' + entry.id + '</td>' +
1957 - '<td class="mxchat-content-cell" style="padding: 12px 16px; font-size: 13px;">' + contentHtml + '</td>' +
1958 - '<td class="mxchat-url-cell" style="padding: 12px 16px; font-size: 13px;">' + sourceHtml + '</td>' +
1959 - '<td style="padding: 12px 16px; white-space: nowrap;">' +
1960 - '<button type="button" class="mxch-btn mxch-btn-ghost mxch-btn-sm mxchat-edit-entry-btn"' +
1961 - ' data-source-url="' + (entry.source_url || '') + '"' +
1962 - ' data-entry-id="' + entry.id + '"' +
1963 - ' data-data-source="wordpress"' +
1964 - ' data-bot-id="' + (entry.bot_id || 'default') + '"' +
1965 - ' data-nonce="' + (entry.edit_nonce || '') + '"' +
1966 - ' title="Edit content">' +
1967 - '<span class="dashicons dashicons-edit" style="font-size: 14px;"></span>' +
1968 - '</button>' +
1969 - '<a href="' + deleteUrl + '" class="mxch-btn mxch-btn-ghost mxch-btn-sm" style="color: var(--mxch-error);" onclick="return confirm(\'Delete this entry?\');">' +
1970 - '<span class="dashicons dashicons-trash" style="font-size: 14px;"></span>' +
1971 - '</a>' +
1972 - '</td>' +
1973 - '</tr>');
1974 -
1975 - // Add highlight class and prepend to tbody
1976 - $row.addClass('mxchat-new-entry');
1977 - $tbody.prepend($row);
1978 - $row.slideDown(300);
1979 -
1980 - // Remove highlight after animation
1981 - setTimeout(function() {
1982 - $row.removeClass('mxchat-new-entry');
1983 - }, 2000);
1984 - });
1985 - }
1986 -
1987 - // Initialize entry ID tracking
1988 - initializeLastEntryId();
1989 -
1990 - // Check on page load if there's already active processing (e.g., page was refreshed during processing)
1991 - if ($('.mxchat-status-card').length > 0) {
1992 - // Check if processing is active via AJAX
1993 - $.ajax({
1994 - url: ajaxurl,
1995 - type: 'POST',
1996 - data: {
1997 - action: 'mxchat_get_status_updates',
1998 - nonce: mxchatAdmin.status_nonce
1999 - },
2000 - success: function(response) {
2001 - if (response.is_processing) {
2002 - startEntriesPolling();
2003 - }
2004 - }
2005 - });
2006 - }
2007 -
2008 - // Expose functions for external access
2009 - window.mxchatEntriesPolling = {
2010 - start: startEntriesPolling,
2011 - stop: stopEntriesPolling,
2012 - fetch: fetchNewEntries
2013 - };
2014 -
2015 - // ========================================
2016 - // SITEMAP DETECTION FUNCTIONALITY
2017 - // ========================================
2018 -
2019 - let sitemapDetectionInitialized = false;
2020 -
2021 - /**
2022 - * Initialize sitemap detection when Sitemap Import is clicked
2023 - */
2024 - function initSitemapDetection() {
2025 - if (sitemapDetectionInitialized) return;
2026 -
2027 - const loadingEl = document.getElementById('mxchat-sitemaps-loading');
2028 - const detectedEl = document.getElementById('mxchat-detected-sitemaps');
2029 - const noSitemapsEl = document.getElementById('mxchat-no-sitemaps');
2030 - const listEl = document.getElementById('mxchat-sitemaps-list');
2031 - const refreshBtn = document.getElementById('mxchat-refresh-sitemaps');
2032 - const nonceEl = document.getElementById('mxchat-detect-sitemaps-nonce');
2033 -
2034 - if (!loadingEl || !nonceEl) return;
2035 -
2036 - sitemapDetectionInitialized = true;
2037 -
2038 - function detectSitemaps() {
2039 - // Show loading
2040 - loadingEl.style.display = 'block';
2041 - if (detectedEl) detectedEl.style.display = 'none';
2042 - if (noSitemapsEl) noSitemapsEl.style.display = 'none';
2043 -
2044 - // Disable refresh button
2045 - if (refreshBtn) {
2046 - refreshBtn.disabled = true;
2047 - var refreshIcon = refreshBtn.querySelector('.dashicons');
2048 - if (refreshIcon) refreshIcon.classList.add('spin');
2049 - }
2050 -
2051 - $.ajax({
2052 - url: ajaxurl,
2053 - type: 'POST',
2054 - data: {
2055 - action: 'mxchat_detect_sitemaps',
2056 - nonce: nonceEl.value
2057 - },
2058 - timeout: 60000, // 60 second timeout for slow servers
2059 - success: function(data) {
2060 - loadingEl.style.display = 'none';
2061 -
2062 - // Re-enable refresh button
2063 - if (refreshBtn) {
2064 - refreshBtn.disabled = false;
2065 - var refreshIcon = refreshBtn.querySelector('.dashicons');
2066 - if (refreshIcon) refreshIcon.classList.remove('spin');
2067 - }
2068 -
2069 - if (data.success && data.data && data.data.sitemaps && data.data.sitemaps.length > 0) {
2070 - renderSitemaps(data.data.sitemaps);
2071 - if (detectedEl) detectedEl.style.display = 'block';
2072 - } else {
2073 - if (noSitemapsEl) {
2074 - noSitemapsEl.style.display = 'block';
2075 - $(noSitemapsEl).data('was-shown', true);
2076 - }
2077 - }
2078 - },
2079 - error: function(xhr, status, error) {
2080 - loadingEl.style.display = 'none';
2081 - if (noSitemapsEl) {
2082 - noSitemapsEl.style.display = 'block';
2083 - $(noSitemapsEl).data('was-shown', true);
2084 - }
2085 - if (refreshBtn) {
2086 - refreshBtn.disabled = false;
2087 - var refreshIcon = refreshBtn.querySelector('.dashicons');
2088 - if (refreshIcon) refreshIcon.classList.remove('spin');
2089 - }
2090 - }
2091 - });
2092 - }
2093 -
2094 - function renderSitemaps(sitemaps) {
2095 - if (!listEl) return;
2096 -
2097 - var html = '';
2098 - var botIdEl = document.getElementById('mxchat-sitemap-bot-id');
2099 - var botId = botIdEl ? botIdEl.value : '';
2100 -
2101 - sitemaps.forEach(function(sitemap) {
2102 - if (sitemap.type === 'index' && sitemap.sub_sitemaps && sitemap.sub_sitemaps.length > 0) {
2103 - // Render sitemap index with sub-sitemaps
2104 - html += '<div class="mxchat-sitemap-group">';
2105 - html += '<div class="mxchat-sitemap-group-header">';
2106 - html += '<div style="display: flex; align-items: center; gap: 10px;">';
2107 - html += '<span class="dashicons dashicons-arrow-right-alt2" style="transition: transform 0.2s;"></span>';
2108 - html += '<span class="dashicons dashicons-list-view" style="color: #7873f5;"></span>';
2109 - html += '<div>';
2110 - html += '<strong style="font-size: 13px;">Sitemap Index</strong>';
2111 - html += '<span style="color: #666; font-size: 12px; margin-left: 8px;">' + sitemap.source + '</span>';
2112 - html += '</div>';
2113 - html += '</div>';
2114 - html += '<span style="background: rgba(120, 115, 245, 0.1); color: #7873f5; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600;">';
2115 - html += sitemap.sub_sitemaps.length + ' sitemaps';
2116 - html += '</span>';
2117 - html += '</div>';
2118 - html += '<div class="mxchat-sitemap-sub-list">';
2119 - sitemap.sub_sitemaps.forEach(function(sub) {
2120 - html += renderSitemapRow(sub, botId, true);
2121 - });
2122 - html += '</div>';
2123 - html += '</div>';
2124 - } else if (sitemap.type !== 'index') {
2125 - // Render standalone sitemap
2126 - html += renderSitemapRow(sitemap, botId, false);
2127 - }
2128 - });
2129 -
2130 - listEl.innerHTML = html;
2131 -
2132 - // Add click handlers for group toggles
2133 - $(listEl).find('.mxchat-sitemap-group-header').on('click', function() {
2134 - var $group = $(this).parent();
2135 - var $subList = $group.find('.mxchat-sitemap-sub-list');
2136 - var $arrow = $(this).find('.dashicons-arrow-right-alt2');
2137 -
2138 - $group.toggleClass('expanded');
2139 -
2140 - if ($group.hasClass('expanded')) {
2141 - $subList.slideDown(200);
2142 - $arrow.css('transform', 'rotate(90deg)');
2143 - } else {
2144 - $subList.slideUp(200);
2145 - $arrow.css('transform', 'rotate(0deg)');
2146 - }
2147 - });
2148 -
2149 - // Add click handlers for process buttons
2150 - $(listEl).find('.mxchat-process-sitemap-btn').on('click', function() {
2151 - var url = $(this).data('url');
2152 - var type = $(this).data('sitemap-type');
2153 - processSitemap(url, type, this);
2154 - });
2155 - }
2156 -
2157 - function renderSitemapRow(sitemap, botId, isSubItem) {
2158 - var typeLabels = {
2159 - 'content': 'Content',
2160 - 'taxonomy': 'Taxonomy',
2161 - 'author': 'Authors'
2162 - };
2163 - var typeLabel = typeLabels[sitemap.type] || sitemap.type;
2164 - var displayName = sitemap.name || sitemap.url.split('/').pop();
2165 - var urlCount = sitemap.url_count || 0;
2166 - var paddingLeft = isSubItem ? '40px' : '16px';
2167 -
2168 - var html = '<div class="mxchat-sitemap-row" style="padding-left: ' + paddingLeft + ';">';
2169 - html += '<div style="display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0;">';
2170 - html += '<span class="dashicons dashicons-media-text" style="color: #666; flex-shrink: 0;"></span>';
2171 - html += '<div style="min-width: 0; flex: 1;">';
2172 - html += '<div style="font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="' + sitemap.url + '">';
2173 - html += displayName;
2174 - html += '</div>';
2175 - html += '<div style="font-size: 11px; color: #666;">';
2176 - html += '<span style="background: #f0f0f0; padding: 1px 6px; border-radius: 3px; margin-right: 8px;">' + typeLabel + '</span>';
2177 - if (urlCount > 0) {
2178 - html += urlCount + ' URLs';
2179 - }
2180 - html += '</div>';
2181 - html += '</div>';
2182 - html += '</div>';
2183 - html += '<button type="button" class="mxchat-process-sitemap-btn mxch-btn mxch-btn-primary mxch-btn-sm" data-url="' + sitemap.url + '" data-sitemap-type="' + sitemap.type + '">';
2184 - html += '<span class="dashicons dashicons-download" style="font-size: 14px; margin-top: 3px;"></span> Process';
2185 - html += '</button>';
2186 - html += '</div>';
2187 -
2188 - return html;
2189 - }
2190 -
2191 - function processSitemap(url, type, buttonEl) {
2192 - var $button = $(buttonEl);
2193 - var originalHtml = $button.html();
2194 -
2195 - // Update button to show loading
2196 - $button.prop('disabled', true);
2197 - $button.html('<span class="dashicons dashicons-update spin" style="font-size: 14px; margin-top: 3px;"></span> Processing...');
2198 -
2199 - // Fill in the sitemap URL form and submit
2200 - var $form = $('#mxchat-url-form');
2201 - var $urlInput = $('#sitemap_url');
2202 - var $importType = $('#import_type');
2203 -
2204 - if ($urlInput.length) {
2205 - $urlInput.val(url);
2206 - }
2207 -
2208 - if ($importType.length) {
2209 - $importType.val('sitemap');
2210 - }
2211 -
2212 - // Add a hidden submit field if not present (required by the PHP handler)
2213 - if ($form.find('input[name="submit_sitemap"]').length === 0) {
2214 - $form.append('<input type="hidden" name="submit_sitemap" value="1">');
2215 - }
2216 -
2217 - // Submit the form
2218 - $form.submit();
2219 - }
2220 -
2221 -
2222 - // Refresh button handler
2223 - if (refreshBtn) {
2224 - $(refreshBtn).on('click', detectSitemaps);
2225 - }
2226 -
2227 - // Start detection
2228 - detectSitemaps();
2229 - }
2230 -
2231 - // Expose initSitemapDetection globally so it can be called from the import options handler
2232 - window.mxchatInitSitemapDetection = initSitemapDetection;
2233 -
2234 - // ========================================
2235 - // ADMIN NOTICE DISMISS FUNCTIONALITY
2236 - // ========================================
2237 -
2238 - // Initialize dismissible notices - add dismiss button if missing
2239 - function initDismissibleNotices() {
2240 - $('.notice.is-dismissible').each(function() {
2241 - var $notice = $(this);
2242 -
2243 - // Skip if already has a dismiss button
2244 - if ($notice.find('.notice-dismiss').length > 0) {
2245 - return;
2246 - }
2247 -
2248 - // Add dismiss button
2249 - var $dismissButton = $('<button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss this notice.</span></button>');
2250 - $notice.append($dismissButton);
2251 - });
2252 - }
2253 -
2254 - // Initialize on page load
2255 - initDismissibleNotices();
2256 -
2257 - // Use event delegation for dismiss button clicks - works for existing and dynamically added notices
2258 - $(document).on('click', '.notice.is-dismissible .notice-dismiss', function(e) {
2259 - e.preventDefault();
2260 - e.stopPropagation();
2261 -
2262 - var $notice = $(this).closest('.notice');
2263 - $notice.fadeTo(100, 0, function() {
2264 - $notice.slideUp(100, function() {
2265 - $notice.remove();
2266 - });
2267 - });
2268 - });
2269 -
2270 - // Re-initialize when new notices are added dynamically (e.g., via AJAX)
2271 - $(document).on('DOMNodeInserted', function(e) {
2272 - if ($(e.target).hasClass('notice') && $(e.target).hasClass('is-dismissible')) {
2273 - setTimeout(initDismissibleNotices, 10);
2274 - }
2275 - });
2276 -
2277 - // ========================================
2278 - // CUSTOM META-KEY DISCOVERY PICKER (plan-mxchat-20260709-fe8e4e)
2279 - // Mirror the ACF picker's discover-and-click UX for non-ACF post meta: scan
2280 - // published content for meta keys, render clickable chips, and append chosen
2281 - // keys into the existing whitelist textarea (its autosave then persists them).
2282 - // ========================================
2283 - (function() {
2284 - var $scanBtn = $('#mxchat-scan-meta-btn');
2285 - if (!$scanBtn.length) { return; }
2286 -
2287 - var $results = $('#mxchat-meta-scan-results');
2288 - var $textarea = $('#mxchat_custom_meta_whitelist');
2289 - var $internal = $('#mxchat-scan-internal');
2290 - var $label = $scanBtn.find('.mxchat-scan-label');
2291 - var defaultLabel = $label.text();
2292 -
2293 - function currentKeys() {
2294 - return ($textarea.val() || '')
2295 - .split('\n')
2296 - .map(function(k) { return k.trim(); })
2297 - .filter(function(k) { return k.length; });
2298 - }
2299 -
2300 - function addKey(key) {
2301 - var keys = currentKeys();
2302 - if (keys.indexOf(key) !== -1) { return false; }
2303 - keys.push(key);
2304 - $textarea.val(keys.join('\n'));
2305 - // Fire the existing autosave (bound on 'change' in mxchat-admin.js).
2306 - $textarea.trigger('change');
2307 - return true;
2308 - }
2309 -
2310 - function escapeHtml(s) {
2311 - return String(s).replace(/[&<>"']/g, function(c) {
2312 - return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
2313 - });
2314 - }
2315 -
2316 - function humanize(key) {
2317 - var h = key.replace(/^_+/, '').replace(/[_-]+/g, ' ').trim();
2318 - return h.length ? h.charAt(0).toUpperCase() + h.slice(1) : key;
2319 - }
2320 -
2321 - function renderChips(keys) {
2322 - if (!keys || !keys.length) {
2323 - $results.html('<p class="mxchat-meta-scan-empty">' +
2324 - 'No custom meta keys found on your published content. Try enabling internal keys, or add keys manually below.' +
2325 - '</p>');
2326 - return;
2327 - }
2328 - var existing = currentKeys();
2329 - var html = '<div class="mxchat-meta-chip-grid">';
2330 - keys.forEach(function(item) {
2331 - var already = existing.indexOf(item.key) !== -1;
2332 - html += '<button type="button" class="mxchat-meta-chip' + (already ? ' is-added' : '') +
2333 - '" data-key="' + escapeHtml(item.key) + '"' + (already ? ' aria-pressed="true"' : '') + '>' +
2334 - '<span class="mxchat-meta-chip-main">' +
2335 - '<span class="mxchat-meta-chip-name">' + escapeHtml(humanize(item.key)) + '</span>' +
2336 - '<span class="mxchat-meta-chip-key">' + escapeHtml(item.key) + '</span>' +
2337 - '</span>' +
2338 - '<span class="mxchat-meta-chip-meta">on ' + item.count + ' post' + (item.count === 1 ? '' : 's') +
2339 - (item.sample ? ' · e.g. ' + escapeHtml(item.sample) : '') + '</span>' +
2340 - '<span class="mxchat-meta-chip-tick">' + (already ? '✓ Added' : '+ Add') + '</span>' +
2341 - '</button>';
2342 - });
2343 - html += '</div>';
2344 - $results.html(html);
2345 - }
2346 -
2347 - $scanBtn.on('click', function() {
2348 - var nonce = $scanBtn.data('nonce') ||
2349 - (window.mxchatAdmin && mxchatAdmin.settings_nonce) || '';
2350 - var ajaxUrl = (window.mxchatAdmin && (mxchatAdmin.ajax_url || mxchatAdmin.ajaxurl)) ||
2351 - window.ajaxurl || '/wp-admin/admin-ajax.php';
2352 -
2353 - $scanBtn.prop('disabled', true);
2354 - $label.text('Scanning…');
2355 - $results.html('<p class="mxchat-meta-scan-loading">Scanning your content for meta keys…</p>');
2356 -
2357 - $.post(ajaxUrl, {
2358 - action: 'mxchat_scan_custom_meta_keys',
2359 - _ajax_nonce: nonce,
2360 - include_internal: $internal.is(':checked') ? '1' : '0'
2361 - }).done(function(resp) {
2362 - if (resp && resp.success && resp.data && resp.data.keys) {
2363 - renderChips(resp.data.keys);
2364 - } else {
2365 - var msg = (resp && resp.data && resp.data.message) ? resp.data.message : 'Scan failed. Please try again.';
2366 - $results.html('<p class="mxchat-meta-scan-empty">' + escapeHtml(msg) + '</p>');
2367 - }
2368 - }).fail(function() {
2369 - $results.html('<p class="mxchat-meta-scan-empty">Scan request failed. Please try again.</p>');
2370 - }).always(function() {
2371 - $scanBtn.prop('disabled', false);
2372 - $label.text(defaultLabel);
2373 - });
2374 - });
2375 -
2376 - // Chip click -> add the raw key to the whitelist textarea (dedup) + autosave.
2377 - $results.on('click', '.mxchat-meta-chip', function() {
2378 - var $chip = $(this);
2379 - if ($chip.hasClass('is-added')) { return; }
2380 - var key = String($chip.data('key'));
2381 - if (addKey(key)) {
2382 - $chip.addClass('is-added').attr('aria-pressed', 'true');
2383 - $chip.find('.mxchat-meta-chip-tick').text('✓ Added');
2384 - }
2385 - });
2386 - })();
2387 1154 });