PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.6.3
MxChat – AI Chatbot & Content Generation for WordPress v2.6.3
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
mxchat-basic / js / mxchat_transcripts.js

mxchat_transcripts.js in MxChat – AI Chatbot & Content Generation for WordPress 2.6.3, at js/mxchat_transcripts.js

525 lines 21.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 jQuery(document).ready(function($) {
2 // Current page state
3 let currentPage = 1;
4 const perPage = 50; // Display 50 sessions per page
5 let totalPages = 1;
6
7 // Track selected sessions for bulk delete
8 let selectedSessions = new Set();
9
10 // Select/Deselect All functionality
11 const selectButton = $('#mxchat-select-all-transcripts');
12 let isSelected = false;
13
14 selectButton.click(function() {
15 isSelected = !isSelected;
16 $(this).toggleClass('selected');
17
18 // Update button text
19 const buttonText = $(this).find('.button-text');
20 buttonText.text(isSelected ? 'Deselect All' : 'Select All');
21
22 // Update session selection (for current page)
23 $('.mxchat-session-header').each(function() {
24 const sessionId = $(this).data('session-id');
25 const sessionContainer = $(this).closest('.mxchat-session');
26 if (isSelected) {
27 selectedSessions.add(sessionId);
28 sessionContainer.addClass('selected');
29 } else {
30 selectedSessions.delete(sessionId);
31 sessionContainer.removeClass('selected');
32 }
33 });
34
35 updateDeleteButtonState();
36 });
37
38 // Search functionality
39 $('#mxchat-search-transcripts').on('input', function() {
40 var searchTerm = $(this).val().toLowerCase();
41
42 if (searchTerm.length > 0) {
43 // Reset to first page when searching
44 currentPage = 1;
45
46 // Load with search filter
47 loadTranscripts(currentPage, searchTerm);
48 } else {
49 // Reset to first page with no search term
50 currentPage = 1;
51 loadTranscripts(currentPage, '');
52 }
53 });
54
55 // Initial load of transcripts
56 loadTranscripts(currentPage, '');
57
58 // Function to load transcripts with pagination
59 function loadTranscripts(page, searchTerm = '') {
60 $('#mxchat-transcripts').html('<div class="mxchat-loading">Loading transcripts...</div>');
61
62 $.ajax({
63 url: ajaxurl,
64 type: 'POST',
65 data: {
66 action: 'mxchat_fetch_chat_history',
67 page: page,
68 per_page: perPage,
69 search: searchTerm
70 },
71 success: function(response) {
72 $('#mxchat-transcripts').html(response.html);
73 currentPage = response.page;
74 totalPages = response.total_pages;
75
76 // Reset selection state when page changes (but keep selectedSessions for bulk operations)
77 isSelected = false;
78 selectButton.removeClass('selected');
79 selectButton.find('.button-text').text('Select All');
80
81 // Restore selection state for sessions on this page
82 $('.mxchat-session-header').each(function() {
83 const sessionId = $(this).data('session-id');
84 const sessionContainer = $(this).closest('.mxchat-session');
85 if (selectedSessions.has(sessionId)) {
86 sessionContainer.addClass('selected');
87 }
88 });
89
90 updateDeleteButtonState();
91
92 // Add click handlers to pagination buttons
93 $('.mxchat-pagination-button').on('click', function() {
94 var pageNum = $(this).data('page');
95 loadTranscripts(pageNum, searchTerm);
96
97 // Scroll to top of transcripts
98 $('html, body').animate({
99 scrollTop: $('#mxchat-transcripts').offset().top - 50
100 }, 300);
101 });
102
103 // Re-attach event handlers for newly loaded content
104 attachDynamicEventHandlers();
105 },
106 error: function(xhr, status, error) {
107 $('#mxchat-transcripts').html('<div class="mxchat-error">Error loading chat transcripts. Please try again.</div>');
108 console.error("AJAX Error: " + status + " - " + error);
109 }
110 });
111 }
112
113 // Attach event handlers to dynamically loaded content
114 function attachDynamicEventHandlers() {
115 // Handle individual delete button clicks
116 $('.mxchat-delete-btn').off('click').on('click', function(e) {
117 e.preventDefault();
118 e.stopPropagation();
119
120 const sessionId = $(this).data('session-id');
121
122 if (!confirm("Are you sure you want to delete this chat session? This action cannot be undone.")) {
123 return;
124 }
125
126 // Delete single session
127 deleteSessions([sessionId]);
128 });
129
130 // Handle session header clicks for selection (bulk delete)
131 $('.mxchat-session-header').off('click').on('click', function(e) {
132 // Don't trigger if clicking the delete button
133 if ($(e.target).hasClass('mxchat-delete-btn') || $(e.target).closest('.mxchat-delete-btn').length) {
134 return;
135 }
136
137 const sessionId = $(this).data('session-id');
138 const sessionContainer = $(this).closest('.mxchat-session');
139
140 if (selectedSessions.has(sessionId)) {
141 selectedSessions.delete(sessionId);
142 sessionContainer.removeClass('selected');
143 } else {
144 selectedSessions.add(sessionId);
145 sessionContainer.addClass('selected');
146 }
147
148 updateDeleteButtonState();
149 });
150
151 // Handle clicks on Sources link for RAG context
152 $('.mxchat-rag-link').off('click').on('click', function(e) {
153 e.preventDefault();
154 e.stopPropagation();
155
156 const messageId = $(this).closest('.mxchat-message').data('message-id');
157 if (messageId) {
158 openRagContextModal(messageId);
159 }
160 });
161 }
162
163 // Update delete button state based on selections
164 function updateDeleteButtonState() {
165 const deleteButton = $('.delete-chats-button');
166 if (selectedSessions.size > 0) {
167 deleteButton.prop('disabled', false);
168 } else {
169 deleteButton.prop('disabled', true);
170 }
171 }
172
173 // Function to delete sessions
174 function deleteSessions(sessionIds) {
175 $.ajax({
176 url: ajaxurl,
177 type: 'POST',
178 data: {
179 action: 'mxchat_delete_chat_history',
180 delete_session_ids: sessionIds,
181 security: $('#mxchat_delete_chat_nonce').val()
182 },
183 success: function(response) {
184 var jsonResponse = JSON.parse(response);
185 if (jsonResponse.success) {
186 alert("Success: " + jsonResponse.success);
187
188 // Remove deleted sessions from selectedSessions
189 sessionIds.forEach(id => selectedSessions.delete(id));
190
191 } else if (jsonResponse.error) {
192 alert("Error: " + jsonResponse.error);
193 } else {
194 //console.log("Unexpected response format.");
195 }
196
197 // Reload the current page of transcripts
198 loadTranscripts(currentPage);
199 },
200 error: function(xhr, status, error) {
201 //console.error("AJAX Error: " + status + " - " + error);
202 //console.log(xhr.responseText);
203 alert("An error occurred while deleting chat sessions. Please try again.");
204 }
205 });
206 }
207
208 // Delete form submission (bulk delete)
209 $('#mxchat-delete-form').submit(function(e) {
210 e.preventDefault();
211
212 if (selectedSessions.size === 0) {
213 alert("Please select at least one chat session to delete.");
214 return;
215 }
216
217 // Confirm deletion
218 if (!confirm(`Are you sure you want to delete the selected ${selectedSessions.size} chat session(s)? This action cannot be undone.`)) {
219 return;
220 }
221
222 // Convert Set to Array and delete
223 deleteSessions(Array.from(selectedSessions));
224 });
225
226 // Export functionality - this remains unchanged as it should export all transcripts
227 $('#mxchat-export-transcripts').on('click', function() {
228 var $button = $(this);
229 $button.prop('disabled', true).addClass('loading');
230
231 // Create a form and submit it
232 var $form = $('<form>', {
233 'method': 'post',
234 'action': ajaxurl
235 });
236
237 $form.append($('<input>', {
238 'type': 'hidden',
239 'name': 'action',
240 'value': 'mxchat_export_transcripts'
241 }));
242
243 $form.append($('<input>', {
244 'type': 'hidden',
245 'name': 'security',
246 'value': mxchatAdmin.export_nonce
247 }));
248
249 $form.appendTo('body').submit();
250
251 // Re-enable the button after a short delay
252 setTimeout(function() {
253 $button.prop('disabled', false).removeClass('loading');
254 }, 2000);
255 });
256
257 // Chat Email Notification Modal functionality
258
259 // Open modal
260 $('#mxchat-chat-email-notification-btn').on('click', function(e) {
261 e.preventDefault();
262 $('#mxchat-chat-email-notification-modal').fadeIn(300);
263 });
264
265 // Close modal
266 $('.mxchat-chat-notification-modal-close, .mxchat-chat-notification-modal-cancel').on('click', function() {
267 $('#mxchat-chat-email-notification-modal').fadeOut(300);
268 });
269
270 // Close modal on outside click
271 $('#mxchat-chat-email-notification-modal').on('click', function(e) {
272 if ($(e.target).is('#mxchat-chat-email-notification-modal')) {
273 $(this).fadeOut(300);
274 }
275 });
276
277 // Handle form submission - Let WordPress handle it normally for settings
278 $('#mxchat-chat-email-notification-form').on('submit', function(e) {
279 // Don't prevent default - let the form submit normally to WordPress options.php
280 var $submitButton = $(this).find('button[type="submit"]');
281 var originalText = $submitButton.text();
282
283 // Just show a loading state
284 $submitButton.text('Saving...').prop('disabled', true);
285
286 // The form will submit normally and reload the page
287 });
288
289 // ========== RAG Context Modal Functions ==========
290
291 // Open RAG context modal and fetch data
292 function openRagContextModal(messageId) {
293 const $modal = $('#mxchat-rag-context-modal');
294 const $loading = $modal.find('.mxchat-rag-loading');
295 const $content = $modal.find('.mxchat-rag-content');
296
297 // Show modal with loading state
298 $modal.fadeIn(300);
299 $loading.show();
300 $content.html('');
301
302 // Fetch RAG context via AJAX
303 $.ajax({
304 url: ajaxurl,
305 type: 'POST',
306 data: {
307 action: 'mxchat_get_rag_context',
308 message_id: messageId
309 },
310 success: function(response) {
311 $loading.hide();
312 if (response.success && response.data) {
313 renderRagContext(response.data, $content);
314 } else {
315 $content.html('<div class="mxchat-rag-error">Unable to load document context.</div>');
316 }
317 },
318 error: function() {
319 $loading.hide();
320 $content.html('<div class="mxchat-rag-error">Error loading document context. Please try again.</div>');
321 }
322 });
323 }
324
325 // Render RAG context data in the modal - grouped by URL
326 function renderRagContext(data, $container) {
327 let html = '';
328
329 // Summary section
330 html += '<div class="mxchat-rag-summary">';
331 html += '<div class="mxchat-rag-summary-item">';
332 html += '<span class="mxchat-rag-label">Knowledge Base:</span> ';
333 html += '<span class="mxchat-rag-value">' + escapeHtml(data.knowledge_base_type || 'WordPress Database') + '</span>';
334 html += '</div>';
335 html += '<div class="mxchat-rag-summary-item">';
336 html += '<span class="mxchat-rag-label">Similarity Threshold:</span> ';
337 html += '<span class="mxchat-rag-value">' + Math.round((data.similarity_threshold || 0.35) * 100) + '%</span>';
338 html += '</div>';
339 html += '<div class="mxchat-rag-summary-item">';
340 html += '<span class="mxchat-rag-label">Documents Checked:</span> ';
341 html += '<span class="mxchat-rag-value">' + (data.total_documents_checked || 0) + '</span>';
342 html += '</div>';
343 html += '</div>';
344
345 // Top matches section - grouped by URL
346 if (data.top_matches && data.top_matches.length > 0) {
347 // Group matches by source URL
348 const groupedByUrl = {};
349 data.top_matches.forEach(function(match) {
350 const url = match.source_display || 'Unknown';
351 if (!groupedByUrl[url]) {
352 groupedByUrl[url] = {
353 url: url,
354 isUrl: url.startsWith('http'),
355 bestScore: 0,
356 usedForContext: false,
357 totalChunks: match.total_chunks || 1,
358 matchedChunks: [],
359 isChunked: match.is_chunk || false,
360 roleRestriction: match.role_restriction
361 };
362 }
363
364 // Track best score
365 if (match.similarity_percentage > groupedByUrl[url].bestScore) {
366 groupedByUrl[url].bestScore = match.similarity_percentage;
367 }
368
369 // Track if any chunk was used
370 if (match.used_for_context) {
371 groupedByUrl[url].usedForContext = true;
372 }
373
374 // Add chunk info
375 groupedByUrl[url].matchedChunks.push({
376 chunkIndex: match.chunk_index,
377 score: match.similarity_percentage,
378 usedForContext: match.used_for_context,
379 aboveThreshold: match.above_threshold,
380 contentPreview: match.content_preview
381 });
382 });
383
384 // Convert to array and sort by best score
385 const urlGroups = Object.values(groupedByUrl).sort(function(a, b) {
386 return b.bestScore - a.bestScore;
387 });
388
389 // Count unique URLs used
390 const usedUrlCount = urlGroups.filter(function(g) { return g.usedForContext; }).length;
391
392 html += '<div class="mxchat-rag-matches">';
393 html += '<h3>Retrieved Documents</h3>';
394 html += '<p class="mxchat-rag-matches-summary">' + usedUrlCount + ' entr' + (usedUrlCount === 1 ? 'y' : 'ies') + ' used for response';
395 html += ' <span style="color: #64748b; font-size: 12px;">(from ' + data.top_matches.length + ' chunk matches)</span></p>';
396
397 urlGroups.forEach(function(group, groupIndex) {
398 const cardClass = group.usedForContext ? 'mxchat-rag-match-used' : 'mxchat-rag-match-below';
399 const statusIcon = group.usedForContext ? '�
400 ' : '';
401 const statusLabel = group.usedForContext ? 'Used for Response' : 'Not Used';
402
403 // Build chunk summary badge
404 let chunkBadge = '';
405 if (group.isChunked && group.totalChunks > 1) {
406 const usedChunkCount = group.matchedChunks.filter(function(c) { return c.usedForContext; }).length;
407 chunkBadge = '<span class="mxchat-rag-chunk-badge">' + usedChunkCount + '/' + group.totalChunks + ' chunks</span>';
408 }
409
410 html += '<div class="mxchat-rag-match-card ' + cardClass + '">';
411 html += '<div class="mxchat-rag-match-header">';
412 html += '<span class="mxchat-rag-match-score">' + group.bestScore + '%</span>';
413 html += chunkBadge;
414 html += '<span class="mxchat-rag-match-status ' + (group.usedForContext ? 'status-used' : 'status-below') + '">';
415 html += statusIcon + ' ' + statusLabel;
416 html += '</span>';
417 html += '</div>';
418
419 html += '<div class="mxchat-rag-match-source">';
420 if (group.isUrl) {
421 html += '<a href="' + escapeHtml(group.url) + '" target="_blank" rel="noopener noreferrer">';
422 html += '🔗 ' + escapeHtml(group.url);
423 html += '</a>';
424 } else {
425 html += '📄 ' + escapeHtml(group.url);
426 }
427 html += '</div>';
428
429 // Show role restriction if not public
430 if (group.roleRestriction && group.roleRestriction !== 'public') {
431 html += '<div class="mxchat-rag-match-meta">';
432 html += '<span class="mxchat-rag-role-badge">🔒 ' + escapeHtml(group.roleRestriction) + '</span>';
433 html += '</div>';
434 }
435
436 // Expandable chunk details if multiple chunks
437 if (group.matchedChunks.length > 1) {
438 html += '<div class="mxchat-rag-chunk-toggle" data-group="' + groupIndex + '">▶ Show ' + group.matchedChunks.length + ' matched chunks</div>';
439 html += '<div class="mxchat-rag-chunk-details" data-group="' + groupIndex + '">';
440
441 // Sort chunks by index
442 const sortedChunks = group.matchedChunks.slice().sort(function(a, b) {
443 return (a.chunkIndex || 0) - (b.chunkIndex || 0);
444 });
445
446 sortedChunks.forEach(function(chunk) {
447 const chunkNum = (chunk.chunkIndex !== null && chunk.chunkIndex !== undefined) ? chunk.chunkIndex + 1 : '?';
448 const chunkClass = chunk.usedForContext ? 'chunk-used' : 'chunk-not-used';
449 const chunkIcon = chunk.usedForContext ? '' : '';
450
451 html += '<div class="mxchat-rag-chunk-row ' + chunkClass + '">';
452 html += '<span class="mxchat-rag-chunk-icon">' + chunkIcon + '</span>';
453 html += '<span class="mxchat-rag-chunk-num">Chunk ' + chunkNum + '</span>';
454 html += '<span class="mxchat-rag-chunk-score">' + chunk.score + '%</span>';
455 html += '</div>';
456 });
457
458 html += '</div>';
459 }
460
461 html += '</div>';
462 });
463
464 html += '</div>';
465 } else {
466 html += '<div class="mxchat-rag-no-matches">No document matches found for this response.</div>';
467 }
468
469 // Approved URLs section
470 if (data.approved_urls && data.approved_urls.length > 0) {
471 html += '<div class="mxchat-rag-urls">';
472 html += '<h3>Approved URLs for Citations (' + data.approved_urls.length + ')</h3>';
473 html += '<ul class="mxchat-rag-url-list">';
474 data.approved_urls.forEach(function(url) {
475 html += '<li><a href="' + escapeHtml(url) + '" target="_blank" rel="noopener noreferrer">' + escapeHtml(url) + '</a></li>';
476 });
477 html += '</ul>';
478 html += '</div>';
479 }
480
481 $container.html(html);
482
483 // Add click handlers for chunk toggles
484 $container.find('.mxchat-rag-chunk-toggle').on('click', function() {
485 const groupId = $(this).data('group');
486 const $details = $container.find('.mxchat-rag-chunk-details[data-group="' + groupId + '"]');
487 const isExpanded = $details.is(':visible');
488
489 if (isExpanded) {
490 $details.slideUp(200);
491 $(this).text('▶ Show ' + $details.find('.mxchat-rag-chunk-row').length + ' matched chunks');
492 } else {
493 $details.slideDown(200);
494 $(this).text('▼ Hide chunks');
495 }
496 });
497 }
498
499 // Helper function to escape HTML
500 function escapeHtml(text) {
501 if (!text) return '';
502 const div = document.createElement('div');
503 div.textContent = text;
504 return div.innerHTML;
505 }
506
507 // Close RAG context modal
508 $('.mxchat-rag-modal-close').on('click', function() {
509 $('#mxchat-rag-context-modal').fadeOut(300);
510 });
511
512 // Close modal on outside click
513 $('#mxchat-rag-context-modal').on('click', function(e) {
514 if ($(e.target).is('#mxchat-rag-context-modal')) {
515 $(this).fadeOut(300);
516 }
517 });
518
519 // Close modal on Escape key
520 $(document).on('keydown', function(e) {
521 if (e.key === 'Escape') {
522 $('#mxchat-rag-context-modal').fadeOut(300);
523 }
524 });
525 });