PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.4.6
MxChat – AI Chatbot & Content Generation for WordPress v2.4.6
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 +869 -2241 3.2.182.4.6 View file →
@@ -1,2387 +1,1015 @@
1 +
1 2 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();
3 + // Track if a form has been submitted to trigger updates
4 + let formSubmitted = false;
5 +
6 + // Global interval ID to manage the polling
7 + let updateIntervalId = null;
8 +
9 +
10 + $(document).on('click', '.mxchat-dismiss-button', function() {
11 + const $button = $(this);
12 + const $card = $button.closest('.mxchat-status-card');
13 +
14 + // Determine card type from data attribute or content
15 + let cardType = $card.data('card-type');
16 + if (!cardType) {
17 + // Fallback: determine from content
18 + cardType = $card.find('h4').text().includes('PDF') ? 'pdf' : 'sitemap';
19 + }
20 +
21 + // Fade out and remove the card
22 + $card.fadeOut(300, function() {
23 + $(this).remove();
24 + });
25 +
26 + // Clear the completed status on the server
27 + $.ajax({
28 + url: ajaxurl,
29 + type: 'POST',
30 + data: {
31 + action: 'mxchat_dismiss_completed_status',
32 + nonce: mxchatAdmin.status_nonce,
33 + card_type: cardType
34 + },
35 + success: function(response) {
36 + //console.log('MxChat: Completed status dismissed');
37 + },
38 + error: function(xhr, status, error) {
39 + console.error('MxChat: Error dismissing status:', error);
16 40 }
17 - } else {
18 - $button.hide();
41 + });
42 + });
43 +
44 + // Check if we're on the right admin page with status cards or import forms
45 + if ($('.mxchat-status-card').length > 0 || $('.mxchat-import-options').length > 0) {
46 + //console.log('MxChat: Status update script initialized');
47 + // Initialize AJAX status updates
48 + initStatusUpdates();
49 + }
50 +
51 + // Initialize status updates
52 +// Initialize status updates
53 +function initStatusUpdates() {
54 + // Get the refresh interval (default to 2 seconds for more responsive updates)
55 + const refreshInterval = parseInt(mxchatAdmin.status_refresh_interval || 2000);
56 +
57 + // Check if there are active status cards
58 + const hasActiveStatus = $('.mxchat-status-card').length > 0;
59 +
60 + // Set up form submission listeners
61 + $('#mxchat-url-form, #mxchat-content-form').on('submit', function() {
62 + //console.log('MxChat: Form submitted, will start checking for updates');
63 + formSubmitted = true;
64 +
65 + // Store submission info in sessionStorage to persist through redirects
66 + sessionStorage.setItem('mxchat_form_submitted', 'true');
67 + sessionStorage.setItem('mxchat_form_submitted_time', Date.now());
68 +
69 + // Start checking for status updates right away
70 + startPolling(refreshInterval);
71 +
72 + // Create a temporary message
73 + if ($('.mxchat-processing-message').length === 0) {
74 + const message = $('<div class="mxchat-processing-message" style="text-align: center; padding: 15px; background: #f0f7ff; border-radius: 8px; margin-top: 15px;">Processing request... Status will update automatically.</div>');
75 + $('.mxchat-import-section').after(message);
76 +
77 + // Fade out after 5 seconds
78 + setTimeout(function() {
79 + message.fadeOut(500, function() {
80 + $(this).remove();
81 + });
82 + }, 5000);
19 83 }
20 84 });
21 -
22 - // Generate unique URL when button is clicked
23 - $('#mxchat-generate-unique-url').on('click', function() {
24 - generateUniqueUrl();
85 +
86 + // Listen for import option clicks
87 + $('.mxchat-import-box').on('click', function() {
88 + const option = $(this).data('option');
89 + //console.log('MxChat: Import option clicked - ' + option);
25 90 });
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;
91 +
92 + // Check if we recently submitted a form (within last 60 seconds for sitemap processing)
93 + if (sessionStorage.getItem('mxchat_form_submitted') === 'true') {
94 + const submittedTime = parseInt(sessionStorage.getItem('mxchat_form_submitted_time') || '0');
95 + if (Date.now() - submittedTime < 60000) { // 60 seconds
96 + //console.log('MxChat: Detected recent form submission via sessionStorage');
97 + formSubmitted = true;
98 + } else {
99 + // Clear old submission data
100 + sessionStorage.removeItem('mxchat_form_submitted');
101 + sessionStorage.removeItem('mxchat_form_submitted_time');
35 102 }
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 103 }
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 104
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());
105 + // Attach event listener to stop button to clear the interval
106 + $('.mxchat-stop-form').on('submit', function() {
107 + //console.log('MxChat: Stop processing requested, clearing update interval');
108 + stopPolling();
109 + sessionStorage.removeItem('mxchat_form_submitted');
110 + sessionStorage.removeItem('mxchat_form_submitted_time');
93 111 });
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();
112 +
113 + // Start the interval for automatic updates if we have status cards or a form was submitted
114 + if (hasActiveStatus || formSubmitted) {
115 + //console.log('MxChat: Starting automatic status checks');
116 + startPolling(refreshInterval);
117 + }
118 +}
119 +
120 + // Function to start polling
121 + function startPolling(interval) {
122 + // Clear any existing interval first
123 + stopPolling();
100 124
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);
125 + // Do an initial fetch immediately
126 + fetchStatusUpdates();
127 +
128 + // Set up new interval
129 + updateIntervalId = setInterval(function() {
130 + fetchStatusUpdates();
131 + }, interval);
132 +
133 + //console.log('MxChat: Polling started with interval', interval);
134 + }
135 +
136 + // Function to stop polling
137 + function stopPolling() {
138 + if (updateIntervalId !== null) {
139 + clearInterval(updateIntervalId);
140 + updateIntervalId = null;
141 + //console.log('MxChat: Polling stopped');
117 142 }
118 143 }
119 144
120 -function checkForActiveQueues() {
145 +// Fetch status updates from the server
146 +function fetchStatusUpdates() {
147 + // If user is actively viewing the failed URLs or pages, don't refresh as frequently
148 + const $details = $('.mxchat-failed-urls-container details, .mxchat-failed-pages-container details');
149 + const isUserViewing = $details.length > 0 && $details.prop('open');
150 +
151 + // If details are open, we'll refresh at a slower rate
152 + if (isUserViewing) {
153 + // Alternative: Update less frequently when details are open
154 + setTimeout(function() {
155 + performStatusUpdate(false); // Pass false for normal updates
156 + }, 5000); // Slow down updates to every 5 seconds when details are open
157 + } else {
158 + performStatusUpdate(false); // Pass false for normal updates
159 + }
160 +}
161 +
162 +// Perform the actual AJAX request
163 +function performStatusUpdate(clearCompleted = false) {
164 + //console.log('MxChat: Checking for status updates...');
165 +
121 166 $.ajax({
122 167 url: ajaxurl,
123 168 type: 'POST',
124 169 data: {
125 170 action: 'mxchat_get_status_updates',
126 - nonce: mxchatAdmin.status_nonce
171 + nonce: mxchatAdmin.status_nonce,
172 + clear_completed: clearCompleted ? 'true' : 'false'
127 173 },
128 174 success: function(response) {
129 - //console.log('MxChat: Checking for active queues...', response);
175 + //console.log('MxChat: Status update received', response);
130 176
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 - }
177 + // Log specific status details for debugging
178 + if (response.sitemap_status) {
179 + //console.log('Sitemap status:', response.sitemap_status.status, 'Processed:', response.sitemap_status.processed_urls, 'Total:', response.sitemap_status.total_urls);
142 180 }
143 181
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 - }
182 + // Check for completion BEFORE updating UI
183 + let shouldStopPolling = false;
184 +
185 + if (response.sitemap_status && response.sitemap_status.status === 'complete') {
186 + //console.log('MxChat: Sitemap processing complete');
187 + shouldStopPolling = true;
155 188 }
156 189
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();
190 + if (response.pdf_status && response.pdf_status.status === 'complete') {
191 + //console.log('MxChat: PDF processing complete');
192 + shouldStopPolling = true;
160 193 }
194 +
195 + // Always update UI
196 + if ((response && response.is_processing) || formSubmitted || shouldStopPolling) {
197 + updateStatusUI(response);
198 + }
199 +
200 + // Show single URL status if available and no active processing
201 + if (response.single_url_status && !response.is_processing) {
202 + updateSingleUrlStatus(response.single_url_status);
203 + }
204 +
205 + // Handle completion - REMOVE THE AUTOMATIC PAGE RELOAD
206 + if (shouldStopPolling) {
207 + // Clear session storage
208 + sessionStorage.removeItem('mxchat_form_submitted');
209 + sessionStorage.removeItem('mxchat_form_submitted_time');
210 +
211 + // Stop polling
212 + stopPolling();
213 +
214 + // DON'T clear the completed status automatically
215 + // DON'T reload the page automatically
216 +
217 + return; // Exit early
218 + }
219 +
220 + // Reset form submitted flag if no active processing
221 + if (!response.is_processing) {
222 + formSubmitted = false;
223 + sessionStorage.removeItem('mxchat_form_submitted');
224 + sessionStorage.removeItem('mxchat_form_submitted_time');
225 + stopPolling();
226 + }
161 227 },
162 228 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();
229 + console.error('MxChat: Status update failed:', error);
166 230 }
167 231 });
168 232 }
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 233
179 - isProcessingQueue = true;
180 - currentQueueId = queueId;
181 - currentQueueType = queueType;
182 -
183 - //console.log('MxChat: Starting queue processing:', queueId, queueType);
184 -
185 - // Remove any "waiting" messages
186 - $('.mxchat-processing-message').remove();
187 -
188 - // Create or update status card
189 - createOrUpdateStatusCard(queueType);
190 -
191 - // Start real-time entries polling if function exists
192 - if (typeof startEntriesPolling === 'function') {
193 - startEntriesPolling();
194 - }
195 -
196 - // Start the batch processing loop
197 - processNextBatch();
198 - }
199 -
200 - /**
201 - * Process the next batch of items (5 at a time)
202 - */
203 - function processNextBatch() {
204 - if (!isProcessingQueue) {
205 - //console.log('MxChat: Processing stopped');
206 - return;
207 - }
208 -
209 - // Fetch the next batch of items
210 - const fetchPromises = [];
211 -
212 - for (let i = 0; i < BATCH_SIZE; i++) {
213 - const promise = $.ajax({
214 - url: ajaxurl,
215 - type: 'POST',
216 - data: {
217 - action: 'mxchat_get_next_queue_item',
218 - nonce: mxchatAdmin.queue_nonce,
219 - queue_id: currentQueueId
220 - }
234 +function addDismissButtonToCompletedCards() {
235 + // Add dismiss buttons to completed cards that don't have them
236 + $('.mxchat-status-card').each(function() {
237 + const $card = $(this);
238 + const $badge = $card.find('.mxchat-status-badge');
239 +
240 + // Check if this is a completed card and doesn't already have a dismiss button
241 + if (($badge.hasClass('mxchat-status-success') || $badge.hasClass('mxchat-status-warning')) &&
242 + $card.find('.mxchat-dismiss-button').length === 0) {
243 +
244 + // Look for existing action buttons container, or create one
245 + let $actionContainer = $card.find('.mxchat-action-buttons');
246 +
247 + if ($actionContainer.length === 0) {
248 + // Create the action buttons container if it doesn't exist
249 + $actionContainer = $('<div class="mxchat-action-buttons"></div>');
250 + $card.find('.mxchat-status-header').append($actionContainer);
251 + }
252 +
253 + // Add dismiss button WITHOUT inline styles
254 + const dismissButton = $('<button type="button" class="mxchat-dismiss-button">Dismiss</button>');
255 +
256 + dismissButton.on('click', function() {
257 + // Fade out and remove the card
258 + $card.fadeOut(300, function() {
259 + $(this).remove();
260 + });
261 +
262 + // Clear the completed status on the server
263 + $.ajax({
264 + url: ajaxurl,
265 + type: 'POST',
266 + data: {
267 + action: 'mxchat_dismiss_completed_status',
268 + nonce: mxchatAdmin.status_nonce,
269 + card_type: $card.find('h4').text().includes('PDF') ? 'pdf' : 'sitemap'
270 + },
271 + success: function(response) {
272 + //console.log('MxChat: Completed status dismissed');
273 + }
274 + });
221 275 });
222 - fetchPromises.push(promise);
276 +
277 + $actionContainer.append(dismissButton);
223 278 }
279 + });
280 +}
224 281
225 - // Wait for all fetch requests to complete
226 - Promise.all(fetchPromises).then(function(responses) {
227 - // Filter out completed/error responses and extract items
228 - const items = [];
229 - let queueComplete = false;
230 -
231 - for (let response of responses) {
232 - if (response.success && response.data && !response.data.complete) {
233 - items.push(response.data.item);
234 - } else if (response.data && response.data.complete) {
235 - queueComplete = true;
236 - }
237 - }
238 -
239 - // If no items to process, queue is done
240 - if (items.length === 0) {
241 - if (queueComplete) {
242 - handleQueueComplete();
243 - } else {
244 - verifyQueueCompletion();
245 - }
246 - return;
247 - }
248 -
249 - // Process all items in this batch simultaneously
250 - const processPromises = items.map(item => processQueueItem(item));
251 -
252 - // Wait for all items to finish processing
253 - Promise.all(processPromises).then(function() {
254 - // Update progress after batch completes
255 - updateQueueProgress();
256 -
257 - // If we got fewer items than batch size, queue might be done
258 - if (items.length < BATCH_SIZE || queueComplete) {
259 - verifyQueueCompletion();
260 - } else {
261 - // Process next batch immediately
262 - processNextBatch();
263 - }
264 - }).catch(function(error) {
265 - console.error('MxChat: Error processing batch:', error);
266 - // Continue anyway
267 - updateQueueProgress();
268 - setTimeout(function() {
269 - processNextBatch();
270 - }, 1000);
271 - });
272 -
273 - }).catch(function(error) {
274 - console.error('MxChat: Error fetching batch:', error);
275 - // Verify queue status before retrying
276 - setTimeout(function() {
277 - verifyQueueCompletion();
278 - }, 2000);
279 - });
282 + // Update the UI with status information
283 +function updateStatusUI(data) {
284 + // Update PDF status if available
285 + if (data.pdf_status) {
286 + updatePdfStatus(data.pdf_status);
280 287 }
281 288
282 - /**
283 - * Verify if queue is actually complete
284 - * Prevents infinite loops when last item fails
285 - */
286 - function verifyQueueCompletion() {
287 - //console.log('MxChat: Verifying queue completion status...');
288 -
289 - $.ajax({
290 - url: ajaxurl,
291 - type: 'POST',
292 - data: {
293 - action: 'mxchat_get_queue_status',
294 - nonce: mxchatAdmin.queue_nonce,
295 - queue_id: currentQueueId
296 - },
297 - success: function(response) {
298 - if (response.success) {
299 - const status = response.data;
300 -
301 - // If no pending or processing items, queue is done
302 - if (status.pending === 0 && status.processing === 0) {
303 - //console.log('MxChat: Queue verified as complete');
304 - handleQueueComplete();
305 - } else {
306 - // Still has items, try to continue
307 - //console.log('MxChat: Queue still has pending items, continuing...');
308 - processNextBatch();
309 - }
310 - } else {
311 - // Can't verify, assume complete to prevent infinite loop
312 - //console.log('MxChat: Could not verify queue status, assuming complete');
313 - handleQueueComplete();
314 - }
315 - },
316 - error: function() {
317 - // Can't verify, assume complete to prevent infinite loop
318 - //console.log('MxChat: Network error verifying queue, assuming complete');
319 - handleQueueComplete();
320 - }
321 - });
289 + // Update sitemap status if available
290 + if (data.sitemap_status) {
291 + updateSitemapStatus(data.sitemap_status);
322 292 }
323 293
324 - /**
325 - * Process a single queue item
326 - * Returns a Promise that resolves when processing is complete
327 - */
328 - function processQueueItem(item) {
329 - return $.ajax({
330 - url: ajaxurl,
331 - type: 'POST',
332 - data: {
333 - action: 'mxchat_process_queue_item',
334 - nonce: mxchatAdmin.queue_nonce,
335 - item_id: item.id,
336 - item_type: item.type,
337 - item_data: item.data,
338 - bot_id: item.bot_id
339 - }
340 - }).then(function(response) {
341 - if (response.success) {
342 - // Item processed successfully
343 - //console.log('MxChat: Item processed successfully:', item.id);
344 - return true;
345 - } else {
346 - // Item failed but we KEEP GOING
347 - console.warn('MxChat: Item processing failed (will continue):', item.type, item.id);
348 - console.warn('MxChat: Error details:', response.data);
349 - return false;
350 - }
351 - }).catch(function(xhr, status, error) {
352 - // Network error - log it but KEEP GOING
353 - console.error('MxChat: AJAX/Network error processing item:', item.id, error);
354 - return false;
355 - });
294 + // Handle single URL status if available and no active processing
295 + if (data.single_url_status && !data.is_processing) {
296 + updateSingleUrlStatus(data.single_url_status);
297 + } else if (data.is_processing) {
298 + // Hide single URL status while processing
299 + $('#mxchat-single-url-status-container').hide();
356 300 }
357 301
358 - /**
359 - * Update queue progress (fetches latest stats)
360 - */
361 - function updateQueueProgress() {
362 - $.ajax({
363 - url: ajaxurl,
364 - type: 'POST',
365 - data: {
366 - action: 'mxchat_get_queue_status',
367 - nonce: mxchatAdmin.queue_nonce,
368 - queue_id: currentQueueId
369 - },
370 - success: function(response) {
371 - if (response.success) {
372 - const status = response.data;
373 -
374 - // Update the appropriate status card
375 - if (currentQueueType === 'pdf') {
376 - updatePdfStatusFromQueue(status);
377 - } else {
378 - updateSitemapStatusFromQueue(status);
379 - }
380 - }
381 - }
382 - });
383 - }
302 + // Add dismiss buttons to any completed cards
303 + addDismissButtonToCompletedCards();
304 +}
384 305
385 - /**
386 - * Handle queue completion
387 - * NO AUTO-REFRESH - Show completed card with errors until dismissed
388 - */
389 - function handleQueueComplete() {
390 - isProcessingQueue = false;
391 -
392 - // Stop real-time entries polling if function exists
393 - if (typeof stopEntriesPolling === 'function') {
394 - stopEntriesPolling();
395 - }
396 -
397 - // Refresh the knowledge base table with properly grouped entries
398 - refreshKnowledgeBaseTable();
399 -
400 - // Get final status with error details
401 - $.ajax({
402 - url: ajaxurl,
403 - type: 'POST',
404 - data: {
405 - action: 'mxchat_get_queue_status',
406 - nonce: mxchatAdmin.queue_nonce,
407 - queue_id: currentQueueId
408 - },
409 - success: function(response) {
410 - if (response.success) {
411 - const status = response.data;
412 -
413 - // Show completed status card (NO REFRESH)
414 - if (currentQueueType === 'pdf') {
415 - showCompletedPdfCard(status);
416 - } else {
417 - showCompletedSitemapCard(status);
418 - }
419 -
420 - // Mark the queue as complete on server
421 - markQueueAsComplete(currentQueueId);
422 -
423 - // Show notification based on results
424 - if (status.failed > 0) {
425 - showNotification('warning',
426 - `Processing completed: ${status.completed} succeeded, ${status.failed} failed. ` +
427 - `Review errors below and dismiss when ready.`
428 - );
429 - } else {
430 - showNotification('success',
431 - `Processing completed successfully! All ${status.completed} items processed. ` +
432 - `Dismiss the status card when ready.`
433 - );
434 - }
435 -
436 - // NO AUTO-REFRESH - User must manually dismiss
437 - } else {
438 - // Couldn't get final status, just show generic completion
439 - showNotification('success', 'Processing completed! Refresh page to see final results.');
440 - }
441 - },
442 - error: function() {
443 - // Error getting final status
444 - showNotification('success', 'Processing completed! Refresh page to see final results.');
445 - }
446 - });
306 + // Update PDF status card
307 +function updatePdfStatus(status) {
308 + // Check if PDF card exists
309 + let $pdfCard = $('.mxchat-status-card:contains("PDF Processing")');
310 +
311 + // If no card exists but we have status, create it
312 + if ($pdfCard.length === 0 && status) {
313 + //console.log('MxChat: Creating new PDF status card');
314 + createPdfStatusCard(status);
315 + $pdfCard = $('.mxchat-status-card:contains("PDF Processing")');
447 316 }
448 317
449 - /**
450 - * Show completed PDF card with full error details (NO RETRY BUTTON)
451 - */
452 - function showCompletedPdfCard(status) {
453 - let $card = $('.mxchat-status-card:contains("PDF Processing")');
318 + // If card exists, update it
319 + if ($pdfCard.length > 0) {
320 + // Update progress bar
321 + $pdfCard.find('.mxchat-progress-fill').css('width', status.percentage + '%');
454 322
455 - if ($card.length === 0) {
456 - return;
457 - }
323 + // Update progress text
324 + let progressText = 'Progress: ' + status.processed_pages + ' of ' +
325 + status.total_pages + ' pages (' + status.percentage + '%)';
458 326
459 - // Remove processing UI elements
460 - $card.find('.mxchat-stop-form').remove();
461 - $card.find('.mxchat-status-warning').remove();
327 + $pdfCard.find('.mxchat-status-details p:first').text(progressText);
462 328
463 - // Update header with completion badge
464 - $card.find('.mxchat-status-badge').remove();
465 - if (status.failed > 0) {
466 - $card.find('.mxchat-status-header h4').after(
467 - '<span class="mxchat-status-badge mxchat-status-warning" style="margin-left: 10px; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: normal;">⚠️ Completed with ' +
468 - status.failed + ' failures - Refresh to view entries</span>'
469 - );
470 - } else {
471 - $card.find('.mxchat-status-header h4').after(
472 - '<span class="mxchat-status-badge mxchat-status-success" style="margin-left: 10px; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: normal;">✓ Complete - Refresh to view entries</span>'
473 - );
329 + // Update failed pages count if exists
330 + const $failedText = $pdfCard.find('.mxchat-status-details p:contains("Failed pages")');
331 + if (status.failed_pages && status.failed_pages > 0) {
332 + if ($failedText.length === 0) {
333 + // Add failed pages text after progress
334 + $pdfCard.find('.mxchat-status-details p:first').after(
335 + '<p><strong>Failed pages:</strong> ' + status.failed_pages + '</p>'
336 + );
337 + } else {
338 + $failedText.html('<strong>Failed pages:</strong> ' + status.failed_pages);
339 + }
340 + } else if ($failedText.length > 0) {
341 + $failedText.remove();
474 342 }
475 -
476 - // Add dismiss button
477 - addDismissButton($card);
478 343
479 - // Update progress bar to 100%
480 - $card.find('.mxchat-progress-fill').css('width', '100%');
481 -
482 - // Update details with final stats
483 - let detailsHtml = '<div style="background: #f0f7ff; padding: 15px; border-radius: 4px; margin-bottom: 15px;">';
484 - detailsHtml += '<h4 style="margin: 0 0 10px 0;">📊 Final Results</h4>';
485 - detailsHtml += '<p style="margin: 5px 0;"><strong>Total Pages:</strong> ' + status.total + '</p>';
486 - detailsHtml += '<p style="margin: 5px 0; color: #2ea44f;"><strong>✓ Successfully Processed:</strong> ' + status.completed + '</p>';
487 -
488 - if (status.failed > 0) {
489 - detailsHtml += '<p style="margin: 5px 0; color: #cf222e;"><strong>✗ Failed:</strong> ' + status.failed + '</p>';
344 + // Update status text
345 + const $statusText = $pdfCard.find('.mxchat-status-details p:contains("Status:")');
346 + if ($statusText.length > 0) {
347 + $statusText.text('Status: ' + status.status.charAt(0).toUpperCase() + status.status.slice(1));
490 348 }
491 349
492 - detailsHtml += '</div>';
493 -
494 - // Add error details if there are failures (NO RETRY BUTTON)
495 - if (status.failed > 0 && status.failed_items && status.failed_items.length > 0) {
496 - detailsHtml += '<div class="mxchat-error-summary" style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-top: 15px;">';
497 - detailsHtml += '<h4 style="margin: 0 0 10px 0; color: #856404;">⚠️ Failed Pages</h4>';
498 -
499 - detailsHtml += '<div style="margin-top: 10px; max-height: 400px; overflow-y: auto;">';
500 - detailsHtml += '<table style="width: 100%; border-collapse: collapse;">';
501 - 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 - detailsHtml += '<tbody>';
503 -
504 - status.failed_items.forEach(function(item) {
505 - let data;
506 - try { data = JSON.parse(item.item_data); } catch(e) { data = {}; }
507 - const pageNum = data.page_number || 'Unknown';
508 -
509 - detailsHtml += '<tr style="border-bottom: 1px solid #ddd;">';
510 - detailsHtml += '<td style="padding: 8px;">Page ' + pageNum + '</td>';
511 - detailsHtml += '<td style="padding: 8px; word-break: break-word;">' + (item.error_message || 'Unknown error') + '</td>';
512 - detailsHtml += '<td style="padding: 8px;">' + item.attempts + '</td>';
513 - detailsHtml += '</tr>';
514 - });
515 -
516 - detailsHtml += '</tbody></table>';
517 - detailsHtml += '</div>';
518 -
519 - detailsHtml += '</div>';
350 + // Update last update text
351 + const $lastUpdateText = $pdfCard.find('.mxchat-status-details p:contains("Last update:")');
352 + if ($lastUpdateText.length > 0) {
353 + $lastUpdateText.text('Last update: ' + status.last_update);
520 354 }
521 355
522 - $card.find('.mxchat-status-details').html(detailsHtml);
523 - }
524 -
525 - /**
526 - * Show completed sitemap card with full error details (NO RETRY BUTTON)
527 - */
528 - function showCompletedSitemapCard(status) {
529 - let $card = $('.mxchat-status-card:contains("Sitemap Processing")');
530 -
531 - if ($card.length === 0) {
532 - return;
356 + // Update status badges
357 + $pdfCard.find('.mxchat-status-badge').remove();
358 + if (status.status === 'error') {
359 + $pdfCard.find('.mxchat-status-header').append('<span class="mxchat-status-badge mxchat-status-failed">Error</span>');
360 + } else if (status.status === 'complete') {
361 + if (status.failed_pages && status.failed_pages > 0) {
362 + $pdfCard.find('.mxchat-status-header').append('<span class="mxchat-status-badge mxchat-status-warning">Completed with ' + status.failed_pages + ' failures</span>');
363 + } else {
364 + $pdfCard.find('.mxchat-status-header').append('<span class="mxchat-status-badge mxchat-status-success">Complete</span>');
365 + }
533 366 }
534 367
535 - // Remove processing UI elements
536 - $card.find('.mxchat-stop-form').remove();
537 - $card.find('.mxchat-status-warning').remove();
538 -
539 - // Update header with completion badge
540 - $card.find('.mxchat-status-badge').remove();
541 - if (status.failed > 0) {
542 - $card.find('.mxchat-status-header h4').after(
543 - '<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 ' +
544 - status.failed + ' failures - Refresh to view entries</span>'
545 - );
546 - } else {
547 - $card.find('.mxchat-status-header h4').after(
548 - '<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>'
549 - );
368 + // Update or add completion summary
369 + if (status.completion_summary) {
370 + let $summaryContainer = $pdfCard.find('.mxchat-completion-summary');
371 + if ($summaryContainer.length === 0) {
372 + const summaryHtml = '<div class="mxchat-completion-summary">' +
373 + '<h5>Processing Summary</h5>' +
374 + '<p><strong>Total Pages:</strong> ' + status.completion_summary.total_pages + '</p>' +
375 + '<p><strong>Successful:</strong> ' + status.completion_summary.successful_pages + '</p>' +
376 + '<p><strong>Failed:</strong> ' + status.completion_summary.failed_pages + '</p>' +
377 + '<p><strong>Completed:</strong> ' + status.completion_summary.completion_time + '</p>' +
378 + '</div>';
379 + $pdfCard.find('.mxchat-status-details').append(summaryHtml);
380 + }
550 381 }
551 382
552 - // Add dismiss button
553 - addDismissButton($card);
554 -
555 - // Update progress bar to 100%
556 - $card.find('.mxchat-progress-fill').css('width', '100%');
557 -
558 - // Update details with final stats
559 - let detailsHtml = '<div style="background: #f0f7ff; padding: 15px; border-radius: 4px; margin-bottom: 15px;">';
560 - detailsHtml += '<h4 style="margin: 0 0 10px 0;">📊 Final Results</h4>';
561 - detailsHtml += '<p style="margin: 5px 0;"><strong>Total URLs:</strong> ' + status.total + '</p>';
562 - detailsHtml += '<p style="margin: 5px 0; color: #2ea44f;"><strong>✓ Successfully Processed:</strong> ' + status.completed + '</p>';
563 -
564 - if (status.failed > 0) {
565 - detailsHtml += '<p style="margin: 5px 0; color: #cf222e;"><strong>✗ Failed:</strong> ' + status.failed + '</p>';
383 + // Update failed pages list if exists
384 + if (status.failed_pages_list && status.failed_pages_list.length > 0) {
385 + updateFailedPagesList($pdfCard, status.failed_pages_list);
566 386 }
567 387
568 - detailsHtml += '</div>';
569 -
570 - // Add error details if there are failures (NO RETRY BUTTON)
571 - if (status.failed > 0 && status.failed_items && status.failed_items.length > 0) {
572 - detailsHtml += '<div class="mxchat-error-summary" style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-top: 15px;">';
573 - detailsHtml += '<h4 style="margin: 0 0 10px 0; color: #856404;">⚠️ Failed URLs</h4>';
388 + // If we have an error, show it
389 + if (status.status === 'error' && status.error) {
390 + let $errorNotice = $pdfCard.find('.mxchat-error-notice');
574 391
575 - detailsHtml += '<div style="margin-top: 10px; max-height: 400px; overflow-y: auto;">';
576 - detailsHtml += '<table style="width: 100%; border-collapse: collapse;">';
577 - 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 - detailsHtml += '<tbody>';
579 -
580 - 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 -
587 - detailsHtml += '<tr style="border-bottom: 1px solid #ddd;">';
588 - detailsHtml += '<td style="padding: 8px;"><a href="' + url + '" target="_blank" style="color: #0073aa; text-decoration: none;">' + displayUrl + '</a></td>';
589 - detailsHtml += '<td style="padding: 8px; word-break: break-word;">' + (item.error_message || 'Unknown error') + '</td>';
590 - detailsHtml += '<td style="padding: 8px;">' + item.attempts + '</td>';
591 - detailsHtml += '</tr>';
592 - });
593 -
594 - detailsHtml += '</tbody></table>';
595 - detailsHtml += '</div>';
596 -
597 - detailsHtml += '</div>';
392 + if ($errorNotice.length === 0) {
393 + $errorNotice = $('<div class="mxchat-error-notice"><p class="error"></p></div>');
394 + $pdfCard.find('.mxchat-status-details').append($errorNotice);
395 + }
396 +
397 + $errorNotice.find('p.error').text(status.error);
598 398 }
599 -
600 - $card.find('.mxchat-status-details').html(detailsHtml);
601 399 }
400 +}
602 401
603 - /**
604 - * Mark queue as complete on server side
605 - * This prevents it from auto-starting on page refresh
606 - */
607 - function markQueueAsComplete(queueId) {
608 - // This is a fire-and-forget call to update queue status
609 - $.ajax({
610 - url: ajaxurl,
611 - type: 'POST',
612 - data: {
613 - action: 'mxchat_mark_queue_complete',
614 - nonce: mxchatAdmin.queue_nonce,
615 - queue_id: queueId
616 - },
617 - success: function(response) {
618 - //console.log('MxChat: Queue marked as complete on server');
619 - },
620 - error: function() {
621 - //console.log('MxChat: Could not mark queue as complete, but continuing');
622 - }
623 - });
624 - }
402 + // Create a new PDF status card
403 +function createPdfStatusCard(status) {
404 + let html = '<div class="mxchat-status-card">';
405 + html += '<div class="mxchat-status-header">';
406 + html += '<h4>PDF Processing Status</h4>';
625 407
626 - /**
627 - * Stop processing button handler
628 - */
629 - $(document).on('submit', '.mxchat-stop-form', function() {
630 - //console.log('MxChat: Stop processing requested');
631 - isProcessingQueue = false;
632 - currentQueueId = null;
633 - currentQueueType = null;
634 - });
635 -
636 - /**
637 - * Create or update status card
638 - */
639 - function createOrUpdateStatusCard(queueType) {
640 - const cardTitle = queueType === 'pdf' ? 'PDF Processing Status' : 'Sitemap Processing Status';
641 - let $card = $('.mxchat-status-card:contains("' + cardTitle + '")');
642 -
643 - if ($card.length === 0) {
644 - // Create new card
645 - let html = '<div class="mxchat-status-card">';
646 - html += '<div class="mxchat-status-header">';
647 - html += '<h4>' + cardTitle + '</h4>';
648 - html += '<div class="mxchat-status-warning" style="background: #fff3cd; color: #856404; padding: 8px 12px; border-radius: 4px; font-size: 13px; margin: 10px 0;">';
649 - html += '⚠️ <strong>Keep this tab open</strong> - Processing runs in your browser';
650 - html += '</div>';
651 - html += '<form method="post" class="mxchat-stop-form" action="' +
652 - mxchatAdmin.admin_url + 'admin-post.php?action=mxchat_stop_processing">';
653 - html += '<input type="hidden" name="mxchat_stop_processing_nonce" value="' +
654 - mxchatAdmin.stop_nonce + '">';
655 - html += '<button type="submit" name="stop_processing" class="mxchat-button-secondary">';
656 - html += 'Stop Processing</button></form>';
657 - html += '</div>';
658 - html += '<div class="mxchat-progress-bar">';
659 - html += '<div class="mxchat-progress-fill" style="width: 0%"></div>';
660 - html += '</div>';
661 - html += '<div class="mxchat-status-details">';
662 - html += '<p>Initializing...</p>';
663 - html += '</div>';
664 - html += '</div>';
665 -
666 - // Insert card
667 - let $importSection = $('.mxchat-import-section');
668 - if ($importSection.length > 0) {
669 - $importSection.after($(html));
670 - }
671 - }
408 + // Add stop processing form if processing
409 + if (status.status === 'processing') {
410 + html += '<form method="post" class="mxchat-stop-form" action="' +
411 + mxchatAdmin.admin_url + 'admin-post.php?action=mxchat_stop_processing">';
412 + html += '<input type="hidden" name="mxchat_stop_processing_nonce" value="' +
413 + mxchatAdmin.stop_nonce + '">';
414 + html += '<button type="submit" name="stop_processing" class="mxchat-button-secondary">';
415 + html += 'Stop Processing</button></form>';
672 416 }
673 417
674 - /**
675 - * Update PDF status from queue data (DURING PROCESSING)
676 - */
677 - function updatePdfStatusFromQueue(status) {
678 - let $card = $('.mxchat-status-card:contains("PDF Processing")');
679 -
680 - if ($card.length === 0) {
681 - return;
418 + // Add status badges
419 + if (status.status === 'error') {
420 + html += '<span class="mxchat-status-badge mxchat-status-failed">Error</span>';
421 + } else if (status.status === 'complete') {
422 + if (status.failed_pages && status.failed_pages > 0) {
423 + html += '<span class="mxchat-status-badge mxchat-status-warning">Completed with ' + status.failed_pages + ' failures</span>';
424 + } else {
425 + html += '<span class="mxchat-status-badge mxchat-status-success">Complete</span>';
682 426 }
683 -
684 - // Update progress bar
685 - $card.find('.mxchat-progress-fill').css('width', status.percentage + '%');
686 -
687 - // Update details
688 - let detailsHtml = '<p>Progress: ' + (status.completed + status.failed) + ' of ' +
689 - status.total + ' pages (' + status.percentage + '%)</p>';
690 -
691 - if (status.completed > 0) {
692 - detailsHtml += '<p><strong>✓ Processed successfully:</strong> ' + status.completed + '</p>';
693 - }
694 -
695 - if (status.failed > 0) {
696 - detailsHtml += '<p class="error-count"><strong>✗ Failed pages:</strong> ' + status.failed + '</p>';
697 - }
698 -
699 - detailsHtml += '<p><strong>Status:</strong> Processing</p>';
700 -
701 - $card.find('.mxchat-status-details').html(detailsHtml);
702 427 }
703 428
704 - /**
705 - * Update sitemap status from queue data (DURING PROCESSING)
706 - */
707 - function updateSitemapStatusFromQueue(status) {
708 - let $card = $('.mxchat-status-card:contains("Sitemap Processing")');
709 -
710 - if ($card.length === 0) {
711 - return;
712 - }
713 -
714 - // Update progress bar
715 - $card.find('.mxchat-progress-fill').css('width', status.percentage + '%');
716 -
717 - // Update details
718 - let detailsHtml = '<p>Progress: ' + (status.completed + status.failed) + ' of ' +
719 - status.total + ' URLs (' + status.percentage + '%)</p>';
720 -
721 - if (status.completed > 0) {
722 - detailsHtml += '<p><strong>✓ Processed successfully:</strong> ' + status.completed + '</p>';
723 - }
724 -
725 - if (status.failed > 0) {
726 - detailsHtml += '<p class="error-count"><strong>✗ Failed URLs:</strong> ' + status.failed + '</p>';
727 - }
728 -
729 - detailsHtml += '<p><strong>Status:</strong> Processing</p>';
730 -
731 - $card.find('.mxchat-status-details').html(detailsHtml);
732 - }
429 + html += '</div>'; // End header
733 430
734 - // ========================================
735 - // STATUS UPDATES FOR COMPLETED QUEUES (FROM SERVER)
736 - // ========================================
431 + // Progress bar
432 + html += '<div class="mxchat-progress-bar">';
433 + html += '<div class="mxchat-progress-fill" style="width: ' + status.percentage + '%"></div>';
434 + html += '</div>';
737 435
738 - /**
739 - * Dismiss completed status button handler
740 - */
741 - $(document).on('click', '.mxchat-dismiss-button', function() {
742 - const $button = $(this);
743 - const $card = $button.closest('.mxchat-status-card');
744 -
745 - let cardType = $card.data('card-type');
746 - if (!cardType) {
747 - cardType = $card.find('h4').text().includes('PDF') ? 'pdf' : 'sitemap';
748 - }
749 -
750 - $card.fadeOut(300, function() {
751 - $(this).remove();
752 - });
753 -
754 - $.ajax({
755 - url: ajaxurl,
756 - type: 'POST',
757 - data: {
758 - action: 'mxchat_clear_queue',
759 - nonce: mxchatAdmin.queue_nonce,
760 - queue_id: $card.data('queue-id') || ''
761 - },
762 - success: function(response) {
763 - //console.log('MxChat: Queue cleared');
764 - }
765 - });
766 - });
436 + // Status details
437 + html += '<div class="mxchat-status-details">';
438 + html += '<p>Progress: ' + status.processed_pages + ' of ' +
439 + status.total_pages + ' pages (' + status.percentage + '%)</p>';
767 440
768 - /**
769 - * Update PDF status card (for already completed queues on page load)
770 - */
771 - function updatePdfStatus(status) {
772 - let $pdfCard = $('.mxchat-status-card:contains("PDF Processing")');
773 -
774 - if ($pdfCard.length === 0 && status) {
775 - createPdfStatusCard(status);
776 - $pdfCard = $('.mxchat-status-card:contains("PDF Processing")');
777 - }
778 -
779 - if ($pdfCard.length > 0 && status.status === 'complete') {
780 - // Show as completed (same as showCompletedPdfCard but from server data)
781 - showCompletedPdfCard(status);
782 - }
441 + // Show failed pages count if any
442 + if (status.failed_pages && status.failed_pages > 0) {
443 + html += '<p><strong>Failed pages:</strong> ' + status.failed_pages + '</p>';
783 444 }
784 445
785 - /**
786 - * Update sitemap status card (for already completed queues on page load)
787 - */
788 - function updateSitemapStatus(status) {
789 - let $sitemapCard = $('.mxchat-status-card:contains("Sitemap Processing")');
790 -
791 - if ($sitemapCard.length === 0 && status) {
792 - createSitemapStatusCard(status);
793 - $sitemapCard = $('.mxchat-status-card:contains("Sitemap Processing")');
794 - }
795 -
796 - if ($sitemapCard.length > 0 && status.status === 'complete') {
797 - // Show as completed (same as showCompletedSitemapCard but from server data)
798 - showCompletedSitemapCard(status);
799 - }
446 + html += '<p>Status: ' + status.status.charAt(0).toUpperCase() + status.status.slice(1) + '</p>';
447 + html += '<p>Last update: ' + status.last_update + '</p>';
448 +
449 + // Add completion summary if available
450 + if (status.completion_summary) {
451 + html += '<div class="mxchat-completion-summary">';
452 + html += '<h5>Processing Summary</h5>';
453 + html += '<p><strong>Total Pages:</strong> ' + status.completion_summary.total_pages + '</p>';
454 + html += '<p><strong>Successful:</strong> ' + status.completion_summary.successful_pages + '</p>';
455 + html += '<p><strong>Failed:</strong> ' + status.completion_summary.failed_pages + '</p>';
456 + html += '<p><strong>Completed:</strong> ' + status.completion_summary.completion_time + '</p>';
457 + html += '</div>';
800 458 }
801 459
802 - /**
803 - * Create PDF status card
804 - */
805 - function createPdfStatusCard(status) {
806 - let html = '<div class="mxchat-status-card" data-queue-id="' + (status.queue_id || '') + '">';
807 - html += '<div class="mxchat-status-header">';
808 - html += '<h4>PDF Processing Status</h4>';
809 - html += '</div>';
810 - html += '<div class="mxchat-progress-bar">';
811 - html += '<div class="mxchat-progress-fill" style="width: ' + status.percentage + '%"></div>';
812 - html += '</div>';
813 - html += '<div class="mxchat-status-details">';
814 - html += '<p>Progress: ' + status.processed_pages + ' of ' + status.total_pages + ' pages</p>';
815 - html += '</div>';
816 - html += '</div>';
817 -
818 - $('.mxchat-import-section').after($(html));
460 + // Add failed pages list if any
461 + if (status.failed_pages_list && status.failed_pages_list.length > 0) {
462 + html += createFailedPagesHtml(status.failed_pages_list);
819 463 }
820 464
821 - /**
822 - * Create sitemap status card
823 - */
824 - function createSitemapStatusCard(status) {
825 - let html = '<div class="mxchat-status-card" data-queue-id="' + (status.queue_id || '') + '">';
826 - html += '<div class="mxchat-status-header">';
827 - html += '<h4>Sitemap Processing Status</h4>';
465 + // Add error message if any
466 + if (status.status === 'error' && status.error) {
467 + html += '<div class="mxchat-error-notice">';
468 + html += '<p class="error">' + status.error + '</p>';
828 469 html += '</div>';
829 - html += '<div class="mxchat-progress-bar">';
830 - html += '<div class="mxchat-progress-fill" style="width: ' + status.percentage + '%"></div>';
831 - html += '</div>';
832 - html += '<div class="mxchat-status-details">';
833 - html += '<p>Progress: ' + status.processed_urls + ' of ' + status.total_urls + ' URLs</p>';
834 - html += '</div>';
835 - html += '</div>';
836 -
837 - $('.mxchat-import-section').after($(html));
838 470 }
839 471
840 - /**
841 - * Add dismiss button to completed cards
842 - */
843 - function addDismissButton($card) {
844 - if ($card.find('.mxchat-dismiss-button').length === 0) {
845 - 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>');
846 - $card.find('.mxchat-status-header').append(dismissButton);
472 + html += '</div>'; // End details
473 + html += '</div>'; // End card
474 +
475 + // Insert the card into the page
476 + let $importTabContent = $('#mxchat-kb-tab-import');
477 + if ($importTabContent.length > 0) {
478 + let $sitemapCard = $importTabContent.find('.mxchat-status-card:contains("Sitemap Processing")');
479 + if ($sitemapCard.length > 0) {
480 + $sitemapCard.before($(html));
481 + } else {
482 + $importTabContent.find('.mxchat-import-section').after($(html));
847 483 }
484 + } else {
485 + let $sitemapCard = $('.mxchat-status-card:contains("Sitemap Processing")');
486 + if ($sitemapCard.length > 0) {
487 + $sitemapCard.before($(html));
488 + } else {
489 + $('.mxchat-import-section').after($(html));
490 + }
848 491 }
492 +}
849 493
850 - /**
851 - * Show notification helper
852 - */
853 - function showNotification(type, message) {
854 - const $notification = $('<div class="mxchat-kb-notification ' + type + '">' + message + '</div>');
855 - $('.mxchat-content, body').first().prepend($notification);
856 -
857 - setTimeout(function() {
858 - $notification.fadeOut(300, function() {
859 - $(this).remove();
860 - });
861 - }, 5000);
862 - }
494 + // Update sitemap status card
495 +function updateSitemapStatus(status) {
496 + // Check if sitemap card exists
497 + let $sitemapCard = $('.mxchat-status-card:contains("Sitemap Processing")');
863 498
864 - // ========================================
865 - // ROLE-BASED CONTENT RESTRICTIONS (Keep existing code)
866 - // ========================================
867 -
868 - if ($('#mxchat-mappings-container').length > 0) {
869 - loadTagRoleMappings();
499 + // If no card exists but we have status, create it
500 + if ($sitemapCard.length === 0 && status) {
501 + //console.log('MxChat: Creating new sitemap status card');
502 + createSitemapStatusCard(status);
503 + $sitemapCard = $('.mxchat-status-card:contains("Sitemap Processing")');
870 504 }
871 505
872 - $('#mxchat-add-tag-role').on('click', function() {
873 - const tagSlug = $('#mxchat-tag-input').val().trim();
874 - const roleRestriction = $('#mxchat-role-select').val();
506 + // If card exists, update it
507 + if ($sitemapCard.length > 0) {
508 + // Update progress bar
509 + $sitemapCard.find('.mxchat-progress-fill').css('width', status.percentage + '%');
875 510
876 - if (!tagSlug) {
877 - alert('Please enter a tag name');
878 - return;
879 - }
511 + // Update progress text
512 + let progressText = 'Progress: ' + status.processed_urls + ' of ' +
513 + status.total_urls + ' URLs (' + status.percentage + '%)';
880 514
881 - const $btn = $(this);
882 - $btn.prop('disabled', true).html('<span class="dashicons dashicons-update-alt"></span> Adding...');
515 + $sitemapCard.find('.mxchat-status-details p:first').text(progressText);
883 516
884 - $.ajax({
885 - url: ajaxurl,
886 - type: 'POST',
887 - data: {
888 - action: 'mxchat_add_tag_role_mapping',
889 - nonce: mxchatAdmin.settings_nonce,
890 - tag_slug: tagSlug,
891 - role_restriction: roleRestriction
892 - },
893 - success: function(response) {
894 - if (response.success) {
895 - $('#mxchat-tag-input').val('');
896 - $('#mxchat-role-select').val('public');
897 - loadTagRoleMappings();
898 - showNotification('success', 'Tag-role mapping added successfully!');
899 - } else {
900 - alert('Error: ' + response.data);
901 - }
902 - $btn.prop('disabled', false).html('<span class="dashicons dashicons-plus-alt"></span> Add Mapping');
903 - },
904 - error: function() {
905 - alert('Network error occurred');
906 - $btn.prop('disabled', false).html('<span class="dashicons dashicons-plus-alt"></span> Add Mapping');
517 + // Update failed URLs count if exists
518 + const $failedText = $sitemapCard.find('.mxchat-status-details p:contains("Failed URLs")');
519 + if (status.failed_urls && status.failed_urls > 0) {
520 + if ($failedText.length === 0) {
521 + // Add failed URLs text after progress
522 + $sitemapCard.find('.mxchat-status-details p:first').after(
523 + '<p><strong>Failed URLs:</strong> ' + status.failed_urls + '</p>'
524 + );
525 + } else {
526 + $failedText.html('<strong>Failed URLs:</strong> ' + status.failed_urls);
907 527 }
908 - });
909 - });
910 -
911 - $(document).on('click', '.mxchat-delete-mapping', function() {
912 - if (!confirm('Are you sure you want to delete this mapping?')) {
913 - return;
528 + } else if ($failedText.length > 0) {
529 + $failedText.remove();
914 530 }
915 531
916 - const $btn = $(this);
917 - const $row = $btn.closest('tr');
918 - const tagSlug = $btn.data('tag-slug');
532 + // Update status badges
533 + $sitemapCard.find('.mxchat-status-badge').remove();
534 + if (status.status === 'error') {
535 + $sitemapCard.find('.mxchat-status-header').append('<span class="mxchat-status-badge mxchat-status-failed">Error</span>');
536 + } else if (status.status === 'complete') {
537 + if (status.failed_urls && status.failed_urls > 0) {
538 + $sitemapCard.find('.mxchat-status-header').append('<span class="mxchat-status-badge mxchat-status-warning">Completed with ' + status.failed_urls + ' failures</span>');
539 + } else {
540 + $sitemapCard.find('.mxchat-status-header').append('<span class="mxchat-status-badge mxchat-status-success">Complete</span>');
541 + }
542 + }
919 543
920 - $btn.html('<span class="dashicons dashicons-update-alt"></span> Deleting...');
921 - $row.addClass('mxchat-row-deleting');
922 -
923 - $.ajax({
924 - url: ajaxurl,
925 - type: 'POST',
926 - data: {
927 - action: 'mxchat_delete_tag_role_mapping',
928 - nonce: mxchatAdmin.settings_nonce,
929 - tag_slug: tagSlug
930 - },
931 - success: function(response) {
932 - if (response.success) {
933 - $row.fadeOut(300, function() {
934 - $(this).remove();
935 - if ($('.mxchat-mappings-table tbody tr').length === 0) {
936 - $('.mxchat-mappings-table').hide();
937 - $('#mxchat-no-mappings').show();
938 - }
939 - });
940 - showNotification('success', 'Mapping deleted successfully!');
941 - } else {
942 - alert('Error: ' + response.data);
943 - $btn.html('<span class="dashicons dashicons-trash"></span> Delete');
944 - $row.removeClass('mxchat-row-deleting');
945 - }
946 - },
947 - error: function() {
948 - alert('Network error occurred');
949 - $btn.html('<span class="dashicons dashicons-trash"></span> Delete');
950 - $row.removeClass('mxchat-row-deleting');
544 + // Update or add completion summary
545 + if (status.completion_summary) {
546 + let $summaryContainer = $sitemapCard.find('.mxchat-completion-summary');
547 + if ($summaryContainer.length === 0) {
548 + const summaryHtml = '<div class="mxchat-completion-summary">' +
549 + '<h5>Processing Summary</h5>' +
550 + '<p><strong>Total URLs:</strong> ' + status.completion_summary.total_urls + '</p>' +
551 + '<p><strong>Successful:</strong> ' + status.completion_summary.successful_urls + '</p>' +
552 + '<p><strong>Failed:</strong> ' + status.completion_summary.failed_urls + '</p>' +
553 + '<p><strong>Completed:</strong> ' + status.completion_summary.completion_time + '</p>' +
554 + '</div>';
555 + $sitemapCard.find('.mxchat-status-details').append(summaryHtml);
951 556 }
952 - });
953 - });
954 -
955 - $('#mxchat-bulk-update-roles').on('click', function() {
956 - if (!confirm('This will update role restrictions for all existing content with mapped tags. Continue?')) {
957 - return;
958 557 }
959 558
960 - const $btn = $(this);
961 - const $progress = $('#mxchat-bulk-update-progress');
962 - const $result = $('#mxchat-bulk-update-result');
559 + // Check if details is already open before updating
560 + const isDetailsOpen = $sitemapCard.find('.mxchat-failed-urls-container details').prop('open');
963 561
964 - $progress.show();
965 - $result.hide();
966 - $btn.prop('disabled', true);
562 + // Update errors display
563 + let $errorContainer = $sitemapCard.find('.mxchat-error-notice');
967 564
968 - $progress.find('.mxchat-progress-text').text('Starting bulk update...');
969 - $progress.find('.mxchat-progress-fill').css('width', '0%');
565 + if ($errorContainer.length === 0 &&
566 + (status.error || status.last_error || (status.failed_urls_list && status.failed_urls_list.length > 0))) {
567 + // Create error container if it doesn't exist
568 + $errorContainer = $('<div class="mxchat-error-notice"></div>');
569 + $sitemapCard.find('.mxchat-status-details').append($errorContainer);
570 + }
970 571
971 - $.ajax({
972 - url: ajaxurl,
973 - type: 'POST',
974 - data: {
975 - action: 'mxchat_bulk_update_tag_roles',
976 - nonce: mxchatAdmin.settings_nonce
977 - },
978 - success: function(response) {
979 - $progress.hide();
980 - $btn.prop('disabled', false);
572 + // Update or create error notices
573 + if ($errorContainer.length > 0) {
574 + let errorHTML = '';
575 +
576 + if (status.error) {
577 + errorHTML += '<p class="error">' + status.error + '</p>';
578 + }
579 +
580 + if (status.last_error) {
581 + errorHTML += '<p class="last-error">Last error: ' + status.last_error + '</p>';
582 + }
583 +
584 + // Add failed URLs list
585 + if (status.failed_urls_list && status.failed_urls_list.length > 0) {
586 + errorHTML += '<div class="mxchat-failed-urls-container">';
587 + errorHTML += '<h5>Failed URLs (' + status.failed_urls_list.length + ')</h5>';
981 588
982 - if (response.success) {
983 - $result.removeClass('error').addClass('success');
984 -
985 - let resultHtml = '<h5>Bulk Update Complete</h5>';
986 - resultHtml += '<p><strong>Total Updated:</strong> ' + response.data.updated_count + '</p>';
987 - resultHtml += '<p><strong>Tags Processed:</strong> ' + response.data.tags_processed + '</p>';
988 -
989 - if (response.data.details && response.data.details.length > 0) {
990 - resultHtml += '<ul>';
991 - response.data.details.forEach(function(detail) {
992 - resultHtml += '<li>' + detail + '</li>';
993 - });
994 - resultHtml += '</ul>';
995 - }
996 -
997 - $result.html(resultHtml).show();
998 - showNotification('success', 'Bulk update completed successfully!');
589 + // Set the 'open' attribute based on previous state
590 + errorHTML += '<details' + (isDetailsOpen ? ' open' : '') + '>';
591 + errorHTML += '<summary>Show Failed URLs</summary>';
592 + errorHTML += '<div class="mxchat-failed-urls-list">';
593 +
594 + // Create table for failed URLs
595 + errorHTML += '<table class="widefat striped">';
596 + errorHTML += '<thead><tr><th>URL</th><th>Error</th><th>Retries</th><th>Time</th></tr></thead>';
597 + errorHTML += '<tbody>';
598 +
599 + // Sort failed URLs by most recent
600 + const sortedFailedUrls = [...status.failed_urls_list].sort((a, b) => b.time - a.time);
601 +
602 + // Show up to 50 failed URLs
603 + const displayUrls = sortedFailedUrls.slice(0, 50);
604 +
605 + displayUrls.forEach(item => {
606 + const timeAgo = formatTimeAgo(item.time);
607 + const retries = item.retries || 'N/A';
608 + errorHTML += '<tr>';
609 + errorHTML += '<td style="word-break: break-all;">';
610 + errorHTML += '<a href="' + item.url + '" target="_blank" rel="noopener noreferrer">';
611 + errorHTML += truncateUrl(item.url) + '</a></td>';
612 + errorHTML += '<td style="word-break: break-word;">' + item.error + '</td>';
613 + errorHTML += '<td>' + retries + '</td>';
614 + errorHTML += '<td>' + timeAgo + '</td>';
615 + errorHTML += '</tr>';
616 + });
617 +
618 + errorHTML += '</tbody></table>';
619 +
620 + if (status.failed_urls_list.length > 50) {
621 + errorHTML += '<div class="mxchat-failed-urls-more">+ ' +
622 + (status.failed_urls_list.length - 50) +
623 + ' more failed URLs not shown</div>';
624 + }
625 +
626 + errorHTML += '</div>'; // End of failed-urls-list
627 + errorHTML += '</details>';
628 + errorHTML += '</div>'; // End of failed-urls-container
629 + }
630 +
631 + $errorContainer.html(errorHTML);
632 +
633 + // Additionally, add a click handler to pause refreshes when viewing details
634 + $sitemapCard.find('.mxchat-failed-urls-container details').on('toggle', function() {
635 + if (this.open) {
636 + // User opened the details - set a flag
637 + $(this).data('user-opened', true);
999 638 } else {
1000 - $result.removeClass('success').addClass('error');
1001 - $result.html('<h5>Update Failed</h5><p>' + response.data + '</p>').show();
639 + // User closed the details - remove the flag
640 + $(this).data('user-opened', false);
1002 641 }
1003 - },
1004 - error: function() {
1005 - $progress.hide();
1006 - $btn.prop('disabled', false);
1007 - $result.removeClass('success').addClass('error');
1008 - $result.html('<h5>Network Error</h5><p>Please try again.</p>').show();
1009 - }
1010 - });
642 + });
643 + }
644 + }
645 +}
646 +
647 +function createFailedPagesHtml(failedPagesList) {
648 + let html = '<div class="mxchat-error-notice">';
649 + html += '<div class="mxchat-failed-pages-container">';
650 + html += '<h5>Failed Pages (' + failedPagesList.length + ')</h5>';
651 + html += '<details>';
652 + html += '<summary>Show Failed Pages</summary>';
653 + html += '<div class="mxchat-failed-pages-list">';
654 +
655 + // Create table for failed pages
656 + html += '<table class="widefat striped">';
657 + html += '<thead><tr><th>Page</th><th>Error</th><th>Retries</th><th>Time</th></tr></thead>';
658 + html += '<tbody>';
659 +
660 + // Sort failed pages by most recent
661 + const sortedFailedPages = [...failedPagesList].sort((a, b) => b.time - a.time);
662 +
663 + sortedFailedPages.forEach(item => {
664 + const timeAgo = formatTimeAgo(item.time);
665 + html += '<tr>';
666 + html += '<td>Page ' + item.page + '</td>';
667 + html += '<td style="word-break: break-word;">' + item.error + '</td>';
668 + html += '<td>' + item.retries + '</td>';
669 + html += '<td>' + timeAgo + '</td>';
670 + html += '</tr>';
1011 671 });
1012 672
1013 - function loadTagRoleMappings() {
1014 - const $container = $('#mxchat-mappings-container');
1015 - $container.html('<div class="mxchat-loading-mappings"><span class="mxchat-role-spinner is-active"></span> Loading mappings...</div>');
673 + html += '</tbody></table>';
674 + html += '</div>'; // End of failed-pages-list
675 + html += '</details>';
676 + html += '</div>'; // End of failed-pages-container
677 + html += '</div>'; // End of error-notice
678 +
679 + return html;
680 +}
681 +
682 +// NEW: Update failed pages list in existing card
683 +function updateFailedPagesList($pdfCard, failedPagesList) {
684 + // Check if details is already open before updating
685 + const isDetailsOpen = $pdfCard.find('.mxchat-failed-pages-container details').prop('open');
686 +
687 + let $errorContainer = $pdfCard.find('.mxchat-failed-pages-container').parent();
688 +
689 + if ($errorContainer.length === 0) {
690 + // Create new failed pages container
691 + $pdfCard.find('.mxchat-status-details').append(createFailedPagesHtml(failedPagesList));
692 + } else {
693 + // Update existing container
694 + $errorContainer.html(createFailedPagesHtml(failedPagesList));
1016 695
1017 - $.ajax({
1018 - url: ajaxurl,
1019 - type: 'POST',
1020 - data: {
1021 - action: 'mxchat_get_tag_role_mappings',
1022 - nonce: mxchatAdmin.settings_nonce
1023 - },
1024 - success: function(response) {
1025 - if (response.success && response.data.mappings.length > 0) {
1026 - $('#mxchat-no-mappings').hide();
1027 -
1028 - let html = '<table class="mxchat-mappings-table">';
1029 - html += '<thead><tr><th>Tag</th><th>Role Restriction</th><th>Posts with Tag</th><th>Actions</th></tr></thead><tbody>';
1030 -
1031 - response.data.mappings.forEach(function(mapping) {
1032 - html += '<tr>';
1033 - html += '<td><span class="mxchat-tag-badge"><span class="dashicons dashicons-tag"></span>' + mapping.tag_slug + '</span></td>';
1034 - html += '<td><span class="mxchat-role-badge ' + mapping.role_restriction + '">' + mapping.role_label + '</span></td>';
1035 - html += '<td><span class="mxchat-post-count"><span class="dashicons dashicons-admin-post"></span>' + mapping.post_count + '</span></td>';
1036 - 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>';
1037 - html += '</tr>';
1038 - });
1039 -
1040 - html += '</tbody></table>';
1041 - $container.html(html);
1042 - } else {
1043 - $container.html('');
1044 - $('#mxchat-no-mappings').show();
1045 - }
1046 - },
1047 - error: function() {
1048 - $container.html('<div class="mxchat-error">Failed to load mappings. Please refresh the page.</div>');
1049 - }
1050 - });
696 + // Restore open state if it was open before
697 + if (isDetailsOpen) {
698 + $pdfCard.find('.mxchat-failed-pages-container details').prop('open', true);
699 + }
1051 700 }
701 +}
702 +
1052 703
1053 - // ========================================
1054 - // PINECONE DELETE HANDLER (Keep existing code)
1055 - // ========================================
1056 -
1057 - $(document).on('click', '.delete-button-ajax', function(e) {
1058 - e.preventDefault();
704 + // Create a new sitemap status card
705 + function createSitemapStatusCard(status) {
706 + let html = '<div class="mxchat-status-card">';
707 + html += '<div class="mxchat-status-header">';
708 + html += '<h4>Sitemap Processing Status</h4>';
1059 709
1060 - if (!confirm('Are you sure you want to delete this entry?')) {
1061 - return;
710 + // Add stop processing form if processing
711 + if (status.status === 'processing') {
712 + html += '<form method="post" class="mxchat-stop-form" action="' +
713 + mxchatAdmin.admin_url + 'admin-post.php?action=mxchat_stop_processing">';
714 + html += '<input type="hidden" name="mxchat_stop_processing_nonce" value="' +
715 + mxchatAdmin.stop_nonce + '">';
716 + html += '<button type="submit" name="stop_processing" class="mxchat-button-secondary">';
717 + html += 'Stop Processing</button></form>';
1062 718 }
1063 719
1064 - var $button = $(this);
1065 - var $row = $button.closest('tr');
1066 - var vectorId = $button.data('vector-id');
1067 - var botId = $button.data('bot-id') || 'default';
1068 - var nonce = $button.data('nonce');
720 + // Add error badge if error
721 + if (status.status === 'error') {
722 + html += '<span class="mxchat-status-badge mxchat-status-failed">Error</span>';
723 + }
1069 724
1070 - $button.prop('disabled', true);
1071 - $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update-alt');
1072 - $row.addClass('mxchat-row-deleting');
725 + html += '</div>'; // End header
1073 726
1074 - $.ajax({
1075 - url: ajaxurl,
1076 - type: 'POST',
1077 - data: {
1078 - action: 'mxchat_delete_pinecone_prompt',
1079 - nonce: nonce,
1080 - vector_id: vectorId,
1081 - bot_id: botId
1082 - },
1083 - success: function(response) {
1084 - if (response.success) {
1085 - $row.fadeOut(500, function() {
1086 - $(this).remove();
1087 - // Update entry count displays (header and sidebar)
1088 - var $countSpan = $('#mxchat-entry-count');
1089 - if ($countSpan.length) {
1090 - 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);
1095 - }
1096 - }
1097 - });
1098 -
1099 - $('<div class="notice notice-success is-dismissible"><p>Entry deleted successfully from Pinecone.</p></div>')
1100 - .insertAfter('.mxchat-hero')
1101 - .delay(3000)
1102 - .fadeOut();
1103 - } else {
1104 - $button.prop('disabled', false);
1105 - $button.find('.dashicons').removeClass('dashicons-update-alt').addClass('dashicons-trash');
1106 - $row.removeClass('mxchat-row-deleting');
1107 - alert('Error: ' + response.data);
1108 - }
1109 - },
1110 - error: function() {
1111 - $button.prop('disabled', false);
1112 - $button.find('.dashicons').removeClass('dashicons-update-alt').addClass('dashicons-trash');
1113 - $row.removeClass('mxchat-row-deleting');
1114 - alert('Network error occurred');
727 + // Progress bar
728 + html += '<div class="mxchat-progress-bar">';
729 + html += '<div class="mxchat-progress-fill" style="width: ' + status.percentage + '%"></div>';
730 + html += '</div>';
731 +
732 + // Status details
733 + html += '<div class="mxchat-status-details">';
734 + html += '<p>Progress: ' + status.processed_urls + ' of ' +
735 + status.total_urls + ' URLs (' + status.percentage + '%)</p>';
736 +
737 + // Add error message if any
738 + if ((status.error || status.last_error) && status.status === 'error') {
739 + html += '<div class="mxchat-error-notice">';
740 +
741 + if (status.error) {
742 + html += '<p class="error">' + status.error + '</p>';
1115 743 }
1116 - });
1117 - });
1118 -
1119 - // ========================================
1120 - // WORDPRESS DATABASE DELETE HANDLER (AJAX)
1121 - // ========================================
1122 -
1123 - $(document).on('click', '.delete-button-wordpress', function(e) {
1124 - e.preventDefault();
1125 -
1126 - if (!confirm('Are you sure you want to delete this entry?')) {
1127 - return;
744 +
745 + if (status.last_error) {
746 + html += '<p class="last-error">Last error: ' + status.last_error + '</p>';
747 + }
748 +
749 + html += '</div>';
1128 750 }
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');
751 +
752 + html += '</div>'; // End details
753 + html += '</div>'; // End card
754 +
755 + // Try to find the import tab content to insert the status card into
756 + let $importTabContent = $('#mxchat-kb-tab-import');
757 + if ($importTabContent.length > 0) {
758 + // For the tabbed interface, add to the import tab
759 + let $pdfCard = $importTabContent.find('.mxchat-status-card:contains("PDF Processing")');
760 + if ($pdfCard.length > 0) {
761 + $pdfCard.after($(html));
762 + } else {
763 + $importTabContent.find('.mxchat-import-section').after($(html));
1179 764 }
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 765 } else {
1202 - // Show Delete All form, hide Delete Selected button
1203 - $deleteAllForm.show();
1204 - $deleteBtn.hide();
1205 - $countEl.text('');
766 + // Fallback to the old method
767 + let $pdfCard = $('.mxchat-status-card:contains("PDF Processing")');
768 + if ($pdfCard.length > 0) {
769 + $pdfCard.after($(html));
770 + } else {
771 + $('.mxchat-import-section').after($(html));
772 + }
1206 773 }
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 774 }
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 -
775 +
776 + // Update single URL status
777 + function updateSingleUrlStatus(status) {
778 + // Check if container exists
779 + let $container = $('#mxchat-single-url-status-container');
780 +
781 + if ($container.length === 0) {
782 + // Create container
783 + $container = $('<div id="mxchat-single-url-status-container"></div>');
784 +
785 + // Try to find the import tab content to insert the status card into
786 + let $importTabContent = $('#mxchat-kb-tab-import');
787 + if ($importTabContent.length > 0) {
788 + // For the tabbed interface, add to the import tab
789 + let $lastStatusCard = $importTabContent.find('.mxchat-status-card').last();
790 + if ($lastStatusCard.length > 0) {
791 + $lastStatusCard.after($container);
1364 792 } else {
1365 - alert('Error: ' + (response.data || 'Unknown error'));
1366 - $('tr.mxchat-row-deleting').removeClass('mxchat-row-deleting');
793 + $importTabContent.find('.mxchat-import-section').after($container);
1367 794 }
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;
795 + } else {
796 + // Fallback to the old method
797 + let $lastStatusCard = $('.mxchat-status-card').last();
798 + if ($lastStatusCard.length > 0) {
799 + $lastStatusCard.after($container);
800 + } else {
801 + $('.mxchat-import-section').after($container);
1377 802 }
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 803 }
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 804 }
1411 -
1412 - // Show loading state
1413 - $link.addClass('loading');
1414 - $tbody.css('opacity', '0.5');
1415 -
1416 - // Add loading indicator to pagination
1417 - var $loadingIndicator = $('<span class="mxchat-pagination-loading"><span class="dashicons dashicons-update spin"></span></span>');
1418 - $paginationWrapper.find('.mxchat-ajax-pagination').append($loadingIndicator);
1419 -
1420 - // Get search and filter values from pagination wrapper
1421 - var searchQuery = $paginationWrapper.data('search') || '';
1422 - var contentType = $paginationWrapper.data('content-type') || '';
1423 -
1424 - $.ajax({
1425 - url: ajaxurl,
1426 - type: 'POST',
1427 - data: {
1428 - action: 'mxchat_paginate_entries',
1429 - nonce: mxchatAdmin.entries_nonce,
1430 - bot_id: mxchatAdmin.bot_id || 'default',
1431 - page: page,
1432 - search: searchQuery,
1433 - content_type: contentType
1434 - },
1435 - success: function(response) {
1436 - $link.removeClass('loading');
1437 - $tbody.css('opacity', '1');
1438 - $loadingIndicator.remove();
1439 -
1440 - if (response.success && response.data) {
1441 - // Update the table body with new HTML
1442 - $tbody.html(response.data.html);
1443 -
1444 - // Update the pagination - find the inner container
1445 - if (response.data.pagination_html) {
1446 - // Replace the inner pagination div content
1447 - $paginationWrapper.html(response.data.pagination_html);
1448 - }
1449 -
1450 - // Update data attributes on wrapper (preserve search/filter for next pagination)
1451 - $paginationWrapper.attr('data-current-page', response.data.page);
1452 - if (response.data.total_pages) {
1453 - $paginationWrapper.attr('data-total-pages', response.data.total_pages);
1454 - }
1455 - // Preserve search and content_type on the wrapper from the inner pagination div
1456 - var $innerPagination = $paginationWrapper.find('.mxchat-ajax-pagination');
1457 - if ($innerPagination.length) {
1458 - $paginationWrapper.attr('data-search', $innerPagination.data('search') || '');
1459 - $paginationWrapper.attr('data-content-type', $innerPagination.data('content-type') || '');
1460 - }
1461 -
1462 - // Scroll to top of the table smoothly
1463 - $('html, body').animate({
1464 - scrollTop: $('#knowledge-base').offset().top - 50
1465 - }, 300);
1466 -
1467 - // Update URL hash to stay on knowledge-base tab
1468 - if (window.history && window.history.replaceState) {
1469 - window.history.replaceState(null, '', window.location.pathname + window.location.search + '#knowledge-base');
1470 - }
1471 -
1472 - // Show success feedback
1473 - showNotification('success', 'Page ' + response.data.page + ' loaded');
1474 - } else {
1475 - showNotification('error', 'Failed to load page: ' + (response.data?.message || 'Unknown error'));
1476 - }
1477 - },
1478 - error: function(xhr, status, error) {
1479 - $link.removeClass('loading');
1480 - $tbody.css('opacity', '1');
1481 - $loadingIndicator.remove();
1482 - showNotification('error', 'Network error while loading page');
1483 - }
1484 - });
1485 - });
1486 -
1487 - // ========================================
1488 - // PINECONE REFRESH ENTRIES BUTTON
1489 - // ========================================
1490 -
1491 - $('#mxchat-refresh-pinecone-entries').on('click', function() {
1492 - var $button = $(this);
1493 - var $icon = $button.find('.dashicons');
1494 - var $tbody = $('#mxchat-entries-tbody');
1495 - var $paginationWrapper = $('#mxchat-kb-pagination');
1496 -
1497 - // Show loading state
1498 - $button.prop('disabled', true);
1499 - $icon.addClass('spin');
1500 - $tbody.css('opacity', '0.5');
1501 -
1502 - $.ajax({
1503 - url: ajaxurl,
1504 - type: 'POST',
1505 - data: {
1506 - action: 'mxchat_refresh_pinecone_entries',
1507 - nonce: mxchatAdmin.entries_nonce,
1508 - bot_id: mxchatAdmin.bot_id || 'default',
1509 - page: 1
1510 - },
1511 - success: function(response) {
1512 - $button.prop('disabled', false);
1513 - $icon.removeClass('spin');
1514 - $tbody.css('opacity', '1');
1515 -
1516 - if (response.success && response.data) {
1517 - // Update the table body with new HTML
1518 - $tbody.html(response.data.html);
1519 -
1520 - // Update the pagination (same pattern as refreshKnowledgeBaseTable)
1521 - if ($paginationWrapper.length) {
1522 - if (response.data.pagination_html) {
1523 - $paginationWrapper.html(response.data.pagination_html);
1524 - $paginationWrapper.attr('style', 'padding: 16px; border-top: 1px solid var(--mxch-card-border); text-align: center;');
1525 - } else {
1526 - $paginationWrapper.html('');
1527 - $paginationWrapper.attr('style', '');
1528 - }
1529 - }
1530 -
1531 - // Update the count display
1532 - if (response.data.total_count !== undefined) {
1533 - updateEntryCount(response.data.total_count);
1534 - }
1535 -
1536 - // Show success feedback
1537 - showNotification('success', 'Entries refreshed successfully!');
1538 - } else {
1539 - showNotification('error', 'Failed to refresh entries: ' + (response.data?.message || 'Unknown error'));
1540 - }
1541 - },
1542 - error: function(xhr, status, error) {
1543 - $button.prop('disabled', false);
1544 - $icon.removeClass('spin');
1545 - $tbody.css('opacity', '1');
1546 - showNotification('error', 'Network error while refreshing entries');
1547 - }
1548 - });
1549 - });
1550 -
1551 - // ========================================
1552 - // ACCORDION FUNCTIONALITY
1553 - // ========================================
1554 -
1555 - // Handle expand/collapse toggle
1556 - $(document).on('click', '.mxchat-expand-toggle', function(e) {
1557 - e.preventDefault();
1558 - e.stopPropagation();
1559 -
1560 - const $button = $(this);
1561 - const $wrapper = $button.closest('.mxchat-accordion-wrapper');
1562 - const $preview = $wrapper.find('.mxchat-content-preview');
1563 - const $fullContent = $wrapper.find('.mxchat-content-full');
1564 -
1565 - // Toggle expanded state
1566 - if ($fullContent.is(':visible')) {
1567 - // Collapse
1568 - $fullContent.slideUp(300);
1569 - $button.removeClass('expanded');
805 +
806 + // Update container content
807 + let html = '<div class="mxchat-status-card">';
808 + html += '<div class="mxchat-status-header">';
809 + html += '<h4>Last URL Submission</h4>';
810 +
811 + if (status.status === 'failed') {
812 + html += '<span class="mxchat-status-badge mxchat-status-failed">Failed</span>';
1570 813 } else {
1571 - // Expand
1572 - $fullContent.slideDown(300);
1573 - $button.addClass('expanded');
814 + html += '<span class="mxchat-status-badge mxchat-status-success">Success</span>';
1574 815 }
1575 - });
1576 -
1577 - // Click anywhere on preview to toggle (expand or collapse)
1578 - $(document).on('click', '.mxchat-content-preview', function(e) {
1579 - // Only trigger if not clicking the button directly
1580 - if (!$(e.target).closest('.mxchat-expand-toggle').length) {
1581 - const $preview = $(this);
1582 - const $wrapper = $preview.closest('.mxchat-accordion-wrapper');
1583 - const $button = $preview.find('.mxchat-expand-toggle');
1584 -
1585 - // Only trigger if there's a button (meaning content is long enough to expand)
1586 - if ($button.length) {
1587 - $button.trigger('click');
1588 - }
816 +
817 + html += '</div>'; // End header
818 +
819 + html += '<div class="mxchat-status-details">';
820 + html += '<p><strong>URL:</strong> ';
821 + html += '<a href="' + status.url + '" target="_blank">';
822 +
823 + // Truncate URL if needed
824 + const displayUrl = status.url.length > 60 ? status.url.substring(0, 57) + '...' : status.url;
825 + html += displayUrl;
826 +
827 + html += '</a></p>';
828 + html += '<p><strong>Submitted:</strong> ' + status.human_time + '</p>';
829 +
830 + if (status.status === 'failed' && status.error) {
831 + html += '<div class="mxchat-error-notice">';
832 + html += '<p class="error">' + status.error + '</p>';
833 + html += '</div>';
1589 834 }
1590 - });
1591 -
1592 - // ========================================
1593 - // CHUNK GROUP TOGGLE FUNCTIONALITY
1594 - // ========================================
1595 -
1596 - // Handle chunk group expand/collapse toggle
1597 - $(document).on('click', '.mxchat-chunk-toggle', function(e) {
1598 - e.preventDefault();
1599 - e.stopPropagation();
1600 -
1601 - const $button = $(this);
1602 - const groupId = $button.data('group-id');
1603 - const $chunkRows = $('.mxchat-chunk-row.' + groupId);
1604 -
1605 - // Toggle expanded state
1606 - if ($button.hasClass('expanded')) {
1607 - // Collapse
1608 - $chunkRows.slideUp(300);
1609 - $button.removeClass('expanded');
1610 - } else {
1611 - // Expand
1612 - $chunkRows.slideDown(300);
1613 - $button.addClass('expanded');
835 +
836 + if (status.status === 'complete') {
837 + html += '<p><strong>Content Length:</strong> ' + status.content_length + ' characters</p>';
838 + html += '<p><strong>Embedding Dimensions:</strong> ' + status.embedding_dimensions + '</p>';
1614 839 }
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 - });
840 +
841 + html += '</div>'; // End details
842 + html += '</div>'; // End card
843 +
844 + $container.html(html).show();
1700 845 }
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;
846 +
847 + // Helper function to format time ago
848 + function formatTimeAgo(timestamp) {
849 + const now = Math.floor(Date.now() / 1000);
850 + const seconds = now - timestamp;
851 +
852 + if (seconds < 60) {
853 + return seconds + ' seconds ago';
854 + } else if (seconds < 3600) {
855 + return Math.floor(seconds / 60) + ' minutes ago';
856 + } else if (seconds < 86400) {
857 + return Math.floor(seconds / 3600) + ' hours ago';
858 + } else {
859 + return Math.floor(seconds / 86400) + ' days ago';
1720 860 }
1721 - isEntriesPolling = false;
1722 861 }
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);
862 +
863 + // Helper function to truncate long URLs
864 + function truncateUrl(url) {
865 + const maxLength = 50;
866 + if (url.length <= maxLength) return url;
867 +
868 + // Remove protocol
869 + let displayUrl = url.replace(/^https?:\/\//, '');
870 +
871 + if (displayUrl.length <= maxLength) return displayUrl;
872 +
873 + // Keep the domain and truncate the path
874 + const domainMatch = displayUrl.match(/^([^\/]+)\//);
875 + if (domainMatch) {
876 + const domain = domainMatch[1];
877 + const path = displayUrl.substring(domain.length);
878 +
879 + if (path.length > 10) {
880 + return domain + path.substring(0, maxLength - domain.length - 3) + '...';
1819 881 }
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 882 }
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 - }
883 +
884 + // Final fallback for very long strings
885 + return displayUrl.substring(0, maxLength - 3) + '...';
1848 886 }
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);
887 +
888 + // Manual Batch Processing Button Handler
889 +$(document).on('click', '.mxchat-manual-batch-btn', function() {
890 + const $btn = $(this);
891 + const processType = $btn.data('process-type');
892 + const url = $btn.data('url');
893 +
894 + //console.log('Manual batch processing requested:', processType, url);
895 +
896 + // Disable button and show loading
897 + $btn.prop('disabled', true).text('Processing...');
898 +
899 + $.ajax({
900 + url: ajaxurl,
901 + type: 'POST',
902 + data: {
903 + action: 'mxchat_manual_batch_process',
904 + nonce: mxchatAdmin.status_nonce,
905 + process_type: processType,
906 + url: url
907 + },
908 + success: function(response) {
909 + //console.log('Manual batch response:', response);
910 +
911 + if (response.success) {
912 + // Show success message briefly
913 + $btn.text('✓ Processed ' + response.data.processed);
914 +
915 + // Re-enable button after 2 seconds
916 + setTimeout(function() {
917 + $btn.prop('disabled', false).text('Process Batch');
918 + }, 2000);
919 +
920 + // Trigger status update to show progress
921 + setTimeout(function() {
922 + fetchStatusUpdates();
923 + }, 1000); // Wait 1 second then refresh status
924 +
925 + } else {
926 + console.error('Manual batch failed:', response.data);
927 + alert('Error: ' + response.data);
928 + $btn.prop('disabled', false).text('Process Batch');
1904 929 }
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();
930 + },
931 + error: function(xhr, status, error) {
932 + console.error('Manual batch AJAX error:', error);
933 + alert('Processing failed. Please try again.');
934 + $btn.prop('disabled', false).text('Process Batch');
1920 935 }
936 + });
937 +});
938 +});
1921 939
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 940
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 - });
941 +// Handle AJAX delete for Pinecone
942 +// Handle AJAX delete for Pinecone
943 +jQuery(document).on('click', '.delete-button-ajax', function(e) {
944 + e.preventDefault();
945 +
946 + if (!confirm('Are you sure you want to delete this entry?')) {
947 + return;
1985 948 }
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);
949 +
950 + var $button = jQuery(this);
951 + var $row = $button.closest('tr');
952 + var vectorId = $button.data('vector-id');
953 + var botId = $button.data('bot-id') || 'default'; // Get bot_id from data attribute
954 + var nonce = $button.data('nonce');
955 +
956 + // Disable button and show loading
957 + $button.prop('disabled', true);
958 + $button.find('.dashicons').removeClass('dashicons-trash').addClass('dashicons-update-alt');
959 + $row.addClass('mxchat-row-deleting');
960 +
961 + jQuery.ajax({
962 + url: ajaxurl,
963 + type: 'POST',
964 + data: {
965 + action: 'mxchat_delete_pinecone_prompt',
966 + nonce: nonce,
967 + vector_id: vectorId,
968 + bot_id: botId // Include bot_id in the request
969 + },
970 + success: function(response) {
971 + if (response.success) {
972 + // Immediately remove the row with animation
973 + $row.fadeOut(500, function() {
974 + jQuery(this).remove();
975 +
976 + // Update record count
977 + var $countSpan = jQuery('.mxchat-record-count');
978 + if ($countSpan.length) {
979 + var currentText = $countSpan.text();
980 + var matches = currentText.match(/\((\d+)/);
981 + if (matches) {
982 + var currentCount = parseInt(matches[1]);
983 + var newCount = Math.max(0, currentCount - 1);
984 + $countSpan.text($countSpan.text().replace(/\(\d+/, '(' + newCount));
2076 985 }
2077 986 }
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';
987 + });
988 +
989 + // Show success message
990 + jQuery('<div class="notice notice-success is-dismissible"><p>Entry deleted successfully from Pinecone.</p></div>')
991 + .insertAfter('.mxchat-hero')
992 + .delay(3000)
993 + .fadeOut();
994 +
995 + } else {
996 + // Re-enable button and show error
997 + $button.prop('disabled', false);
998 + $button.find('.dashicons').removeClass('dashicons-update-alt').addClass('dashicons-trash');
999 + $row.removeClass('mxchat-row-deleting');
1000 +
1001 + alert('Error: ' + response.data);
2179 1002 }
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;
1003 + },
1004 + error: function() {
1005 + // Re-enable button
1006 + $button.prop('disabled', false);
1007 + $button.find('.dashicons').removeClass('dashicons-update-alt').addClass('dashicons-trash');
1008 + $row.removeClass('mxchat-row-deleting');
1009 +
1010 + alert('Network error occurred');
2189 1011 }
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 1012 });
1013 +});
2269 1014
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 1015
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 -});