PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.2.7
MxChat – AI Chatbot & Content Generation for WordPress v2.2.7
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/mxchat_transcripts.js +139 -1215 3.2.12.2.7 View file →
@@ -1,1279 +1,203 @@
1 -/**
2 - * MxChat Transcripts Page JavaScript - v3.0
3 - * Split-panel layout with chat list and conversation view
4 - */
5 1 jQuery(document).ready(function($) {
6 - // ==========================================================================
7 - // Sidebar Navigation
8 - // ==========================================================================
9 -
10 - // Desktop sidebar navigation
11 - $('.mxch-nav-link').on('click', function(e) {
12 - e.preventDefault();
13 - const target = $(this).data('target');
14 -
15 - // Update active states
16 - $('.mxch-nav-link').removeClass('active');
17 - $(this).addClass('active');
18 -
19 - // Show target section
20 - $('.mxch-section').removeClass('active');
21 - $('#' + target).addClass('active');
22 -
23 - // Reset scroll position of content area
24 - $('.mxch-content').scrollTop(0);
25 -
26 - // Also update mobile nav if open
27 - $('.mxch-mobile-nav-link').removeClass('active');
28 - $('.mxch-mobile-nav-link[data-target="' + target + '"]').addClass('active');
29 -
30 - // Load transcripts when switching to all-chats
31 - if (target === 'all-chats' && !transcriptsLoaded) {
32 - loadChatList(1, '');
33 - }
34 - });
35 -
36 - // Mobile menu toggle
37 - $('.mxch-mobile-menu-btn').on('click', function() {
38 - $('.mxch-mobile-menu').addClass('open');
39 - $('.mxch-mobile-overlay').addClass('open');
40 - });
41 -
42 - // Close mobile menu
43 - $('.mxch-mobile-menu-close, .mxch-mobile-overlay').on('click', function() {
44 - $('.mxch-mobile-menu').removeClass('open');
45 - $('.mxch-mobile-overlay').removeClass('open');
46 - });
47 -
48 - // Mobile navigation
49 - $('.mxch-mobile-nav-link').on('click', function(e) {
50 - e.preventDefault();
51 - const target = $(this).data('target');
52 -
53 - $('.mxch-mobile-nav-link').removeClass('active');
54 - $(this).addClass('active');
55 -
56 - $('.mxch-section').removeClass('active');
57 - $('#' + target).addClass('active');
58 -
59 - // Reset scroll position of content area
60 - $('.mxch-content').scrollTop(0);
61 -
62 - $('.mxch-nav-link').removeClass('active');
63 - $('.mxch-nav-link[data-target="' + target + '"]').addClass('active');
64 -
65 - $('.mxch-mobile-menu').removeClass('open');
66 - $('.mxch-mobile-overlay').removeClass('open');
67 - });
68 -
69 - // Quick action buttons
70 - $('.mxch-quick-action-btn[data-action="view-chats"]').on('click', function() {
71 - $('.mxch-nav-link[data-target="all-chats"]').trigger('click');
72 - });
73 -
74 - $('.mxch-quick-action-btn[data-action="settings"]').on('click', function() {
75 - $('.mxch-nav-link[data-target="notifications"]').trigger('click');
76 - });
77 -
78 - // ==========================================================================
79 - // Mobile Panel Management
80 - // ==========================================================================
81 -
82 - function isMobile() {
83 - return window.innerWidth <= 782;
84 - }
85 -
86 - function showMobileConversationPanel() {
87 - if (isMobile()) {
88 - $('.mxch-chat-list-panel').addClass('panel-hidden');
89 - $('#mxch-conversation-panel').addClass('panel-active');
90 - $('#mxch-transcript-back-btn').show();
91 - }
92 - }
93 -
94 - function hideMobileConversationPanel() {
95 - if (isMobile()) {
96 - $('#mxch-conversation-panel').removeClass('panel-active');
97 - $('.mxch-chat-list-panel').removeClass('panel-hidden');
98 - $('#mxch-transcript-back-btn').hide();
99 - }
100 - }
101 -
102 - // Mobile back button handler
103 - $('#mxch-transcript-back-btn').on('click', function(e) {
104 - e.preventDefault();
105 - hideMobileConversationPanel();
106 - $('.mxch-chat-item').removeClass('active');
107 - currentSessionId = null;
108 - });
109 -
110 - // Handle window resize
111 - $(window).on('resize', function() {
112 - if (!isMobile()) {
113 - // Reset panel states when switching to desktop
114 - $('.mxch-chat-list-panel').removeClass('panel-hidden');
115 - $('#mxch-conversation-panel').removeClass('panel-active');
116 - $('#mxch-transcript-back-btn').hide();
117 - }
118 - updateMobileViewportHeight();
119 - });
120 -
121 - // Fix for mobile browser address bar - sets CSS custom property for accurate viewport height
122 - function updateMobileViewportHeight() {
123 - if (isMobile()) {
124 - // Use visualViewport if available (most reliable for mobile)
125 - const vh = window.visualViewport ? window.visualViewport.height : window.innerHeight;
126 - document.documentElement.style.setProperty('--mxch-mobile-vh', vh + 'px');
127 - }
128 - }
129 -
130 - // Update on load and viewport changes
131 - updateMobileViewportHeight();
132 - if (window.visualViewport) {
133 - window.visualViewport.addEventListener('resize', updateMobileViewportHeight);
134 - }
135 -
136 - // ==========================================================================
137 - // Chat List - Split Panel
138 - // ==========================================================================
139 -
2 + // Current page state
140 3 let currentPage = 1;
141 - const perPage = 50;
4 + const perPage = 50; // Display 50 sessions per page
142 5 let totalPages = 1;
143 - let currentSessionId = null;
144 - let transcriptsLoaded = false;
145 - let selectedSessions = new Set();
146 - let currentSortOrder = 'desc'; // newest first
147 6
148 - // Load chat list on page load
149 - loadChatList(1, '');
7 + // Select/Deselect All functionality
8 + const selectButton = $('#mxchat-select-all-transcripts');
9 + let isSelected = false;
150 10
151 - // Search functionality with debounce
152 - let searchTimeout;
153 - $('#mxch-search-transcripts').on('input', function() {
154 - clearTimeout(searchTimeout);
155 - const searchTerm = $(this).val().toLowerCase();
11 + selectButton.click(function() {
12 + isSelected = !isSelected;
13 + $(this).toggleClass('selected');
14 +
15 + // Update button text
16 + const buttonText = $(this).find('.button-text');
17 + buttonText.text(isSelected ? 'Deselect All' : 'Select All');
18 +
19 + // Update checkboxes (only for current page)
20 + $('#mxchat-transcripts').find('input[type=checkbox]').prop('checked', isSelected);
21 + });
156 22
157 - searchTimeout = setTimeout(function() {
23 + // Search functionality
24 + $('#mxchat-search-transcripts').on('input', function() {
25 + var searchTerm = $(this).val().toLowerCase();
26 +
27 + if (searchTerm.length > 0) {
28 + // Reset to first page when searching
158 29 currentPage = 1;
159 - loadChatList(currentPage, searchTerm);
160 - }, 300);
161 - });
162 -
163 - // Refresh button
164 - $('#mxch-refresh-list').on('click', function() {
165 - const $btn = $(this);
166 - $btn.addClass('spinning');
167 - loadChatList(currentPage, $('#mxch-search-transcripts').val());
168 - setTimeout(() => $btn.removeClass('spinning'), 500);
169 - });
170 -
171 - // ==========================================================================
172 - // Bulk Selection & Actions
173 - // ==========================================================================
174 -
175 - // Select all checkbox
176 - $('#mxch-select-all').on('change', function() {
177 - const isChecked = $(this).is(':checked');
178 - $('.mxch-chat-checkbox').prop('checked', isChecked);
179 -
180 - if (isChecked) {
181 - $('.mxch-chat-item').each(function() {
182 - selectedSessions.add($(this).data('session-id'));
183 - $(this).addClass('selected');
184 - });
185 - $('#mxch-chat-list').addClass('selection-mode');
30 +
31 + // Load with search filter
32 + loadTranscripts(currentPage, searchTerm);
186 33 } else {
187 - selectedSessions.clear();
188 - $('.mxch-chat-item').removeClass('selected');
189 - $('#mxch-chat-list').removeClass('selection-mode');
34 + // Reset to first page with no search term
35 + currentPage = 1;
36 + loadTranscripts(currentPage, '');
190 37 }
191 -
192 - updateSelectionUI();
193 38 });
194 39
195 - // Update selection UI
196 - function updateSelectionUI() {
197 - const count = selectedSessions.size;
198 - const $countEl = $('#mxch-selected-count');
199 - const $deleteBtn = $('#mxch-delete-selected');
40 + // Initial load of transcripts
41 + loadTranscripts(currentPage, '');
200 42
201 - if (count > 0) {
202 - $countEl.text(count + ' selected').addClass('has-selection');
203 - $deleteBtn.prop('disabled', false);
204 - $('#mxch-chat-list').addClass('selection-mode');
205 - } else {
206 - $countEl.removeClass('has-selection');
207 - $deleteBtn.prop('disabled', true);
208 - $('#mxch-chat-list').removeClass('selection-mode');
209 - }
210 -
211 - // Update select all checkbox state
212 - const totalItems = $('.mxch-chat-checkbox').length;
213 - const checkedItems = $('.mxch-chat-checkbox:checked').length;
214 - $('#mxch-select-all').prop('checked', totalItems > 0 && checkedItems === totalItems);
215 - $('#mxch-select-all').prop('indeterminate', checkedItems > 0 && checkedItems < totalItems);
216 - }
217 -
218 - // Sort button
219 - $('#mxch-sort-btn').on('click', function() {
220 - currentSortOrder = currentSortOrder === 'desc' ? 'asc' : 'desc';
221 - $(this).find('svg').css('transform', currentSortOrder === 'asc' ? 'rotate(180deg)' : 'rotate(0deg)');
222 - loadChatList(currentPage, $('#mxch-search-transcripts').val());
223 - });
224 -
225 - // Delete selected button
226 - $('#mxch-delete-selected').on('click', function() {
227 - const count = selectedSessions.size;
228 - if (count === 0) return;
229 -
230 - if (!confirm('Are you sure you want to delete ' + count + ' conversation(s)? This action cannot be undone.')) {
231 - return;
232 - }
233 -
234 - deleteMultipleSessions(Array.from(selectedSessions));
235 - });
236 -
237 - // Delete multiple sessions
238 - function deleteMultipleSessions(sessionIds) {
43 + // Function to load transcripts with pagination
44 + function loadTranscripts(page, searchTerm = '') {
45 + $('#mxchat-transcripts').html('<div class="mxchat-loading">Loading transcripts...</div>');
46 +
239 47 $.ajax({
240 48 url: ajaxurl,
241 49 type: 'POST',
242 50 data: {
243 - action: 'mxchat_delete_chat_history',
244 - delete_session_ids: sessionIds,
245 - security: $('#mxchat_delete_chat_nonce').val()
246 - },
247 - success: function(response) {
248 - try {
249 - const jsonResponse = typeof response === 'object' ? response : JSON.parse(response);
250 -
251 - if (jsonResponse.success) {
252 - // Clear selection
253 - selectedSessions.clear();
254 - $('#mxch-select-all').prop('checked', false);
255 - updateSelectionUI();
256 -
257 - // If current conversation was deleted, reset panel
258 - if (sessionIds.includes(currentSessionId)) {
259 - currentSessionId = null;
260 - $('#mxch-conversation-content').hide();
261 - $('#mxch-conversation-empty').show();
262 - $('#mxch-details-drawer').hide();
263 - }
264 -
265 - // Reload list
266 - loadChatList(currentPage, $('#mxch-search-transcripts').val());
267 - } else if (jsonResponse.error) {
268 - alert('Error: ' + jsonResponse.error);
269 - }
270 - } catch (e) {
271 - alert('An error occurred while processing the response.');
272 - }
273 - },
274 - error: function() {
275 - alert('An error occurred while deleting conversations.');
276 - }
277 - });
278 - }
279 -
280 - // Load chat list function
281 - function loadChatList(page, searchTerm) {
282 - const $container = $('#mxch-chat-list');
283 - $container.html('<div class="mxch-list-loading"><span class="spinner is-active"></span></div>');
284 -
285 - $.ajax({
286 - url: ajaxurl,
287 - type: 'POST',
288 - data: {
289 51 action: 'mxchat_fetch_chat_history',
290 52 page: page,
291 53 per_page: perPage,
292 - search: searchTerm,
293 - sort_order: currentSortOrder
54 + search: searchTerm
294 55 },
295 56 success: function(response) {
296 - transcriptsLoaded = true;
297 -
298 - if (response.success && response.sessions && response.sessions.length > 0) {
299 - renderChatList(response.sessions);
300 - currentPage = response.page;
301 - totalPages = response.total_pages;
302 - updateChatCount(response.showing_start, response.showing_end, response.total_sessions);
303 - renderPagination(response.page, response.total_pages, searchTerm);
304 - } else {
305 - $container.html('<div class="mxch-list-empty"><p>No chats found</p></div>');
306 - updateChatCount(0, 0, 0);
307 - $('#mxch-pagination').html('');
308 - }
57 + $('#mxchat-transcripts').html(response.html);
58 + currentPage = response.page;
59 + totalPages = response.total_pages;
60 +
61 + // Reset selection state when page changes
62 + isSelected = false;
63 + selectButton.removeClass('selected');
64 + selectButton.find('.button-text').text('Select All');
65 +
66 + // Add click handlers to pagination buttons
67 + $('.mxchat-pagination-button').on('click', function() {
68 + var pageNum = $(this).data('page');
69 + loadTranscripts(pageNum, searchTerm);
70 +
71 + // Scroll to top of transcripts
72 + $('html, body').animate({
73 + scrollTop: $('#mxchat-transcripts').offset().top - 50
74 + }, 300);
75 + });
76 +
77 + // Re-attach event handlers for newly loaded content if needed
78 + // This is important because the content is dynamically loaded
79 + attachDynamicEventHandlers();
309 80 },
310 - error: function() {
311 - $container.html('<div class="mxch-list-empty"><p>Error loading chats</p></div>');
81 + error: function(xhr, status, error) {
82 + $('#mxchat-transcripts').html('<div class="mxchat-error">Error loading chat transcripts. Please try again.</div>');
83 + console.error("AJAX Error: " + status + " - " + error);
312 84 }
313 85 });
314 86 }
315 -
316 - // Render chat list items
317 - function renderChatList(sessions) {
318 - const $container = $('#mxch-chat-list');
319 - let html = '';
320 -
321 - sessions.forEach(function(session) {
322 - const isActive = session.session_id === currentSessionId ? ' active' : '';
323 - const isSelected = selectedSessions.has(session.session_id) ? ' selected' : '';
324 - const isChecked = selectedSessions.has(session.session_id) ? ' checked' : '';
325 - html += `
326 - <div class="mxch-chat-item${isActive}${isSelected}" data-session-id="${escapeHtml(session.session_id)}">
327 - <input type="checkbox" class="mxch-chat-checkbox"${isChecked}>
328 - <div class="mxch-chat-avatar">
329 - <span>${escapeHtml(session.initials)}</span>
330 - </div>
331 - <div class="mxch-chat-info">
332 - <div class="mxch-chat-name">${escapeHtml(session.display_name)}</div>
333 - <div class="mxch-chat-preview">${escapeHtml(session.preview)}</div>
334 - </div>
335 - <div class="mxch-chat-meta">
336 - <span class="mxch-chat-time">${escapeHtml(session.time_display)}</span>
337 - <span class="mxch-chat-count">${session.message_count}</span>
338 - </div>
339 - </div>
340 - `;
341 - });
342 -
343 - $container.html(html);
344 -
345 - // Attach checkbox handlers
346 - $('.mxch-chat-checkbox').on('click', function(e) {
347 - e.stopPropagation(); // Prevent triggering chat item click
348 - const $item = $(this).closest('.mxch-chat-item');
349 - const sessionId = $item.data('session-id');
350 -
351 - if ($(this).is(':checked')) {
352 - selectedSessions.add(sessionId);
353 - $item.addClass('selected');
354 - } else {
355 - selectedSessions.delete(sessionId);
356 - $item.removeClass('selected');
357 - }
358 -
359 - updateSelectionUI();
360 - });
361 -
362 - // Attach click handlers for selecting chat
363 - $('.mxch-chat-item').on('click', function(e) {
364 - // Don't trigger if clicking on checkbox
365 - if ($(e.target).is('.mxch-chat-checkbox')) return;
366 -
367 - const sessionId = $(this).data('session-id');
368 - selectChat(sessionId);
369 -
370 - // Update active state
371 - $('.mxch-chat-item').removeClass('active');
372 - $(this).addClass('active');
373 -
374 - // Show conversation panel on mobile
375 - showMobileConversationPanel();
376 - });
377 -
378 - // Update selection UI after render
379 - updateSelectionUI();
87 +
88 + // Attach event handlers to dynamically loaded content
89 + function attachDynamicEventHandlers() {
90 + // Add any event handlers for dynamically loaded content here if needed
380 91 }
381 92
382 - // Update chat count display
383 - function updateChatCount(start, end, total) {
384 - if (total === 0) {
385 - $('#mxch-chat-count').text('0 chats');
386 - } else {
387 - $('#mxch-chat-count').text(`${start}-${end} / ${total} chats`);
388 - }
389 - }
390 -
391 - // Render pagination
392 - function renderPagination(currentPage, totalPages, searchTerm) {
393 - const $container = $('#mxch-pagination');
394 -
395 - if (totalPages <= 1) {
396 - $container.html('');
93 + // Delete form submission
94 + $('#mxchat-delete-form').submit(function(e) {
95 + e.preventDefault();
96 +
97 + // Get all checked session IDs
98 + var checkedSessionIds = $('input[name="delete_session_ids[]"]:checked').map(function() {
99 + return $(this).val();
100 + }).get();
101 +
102 + if (checkedSessionIds.length === 0) {
103 + alert("Please select at least one chat session to delete.");
397 104 return;
398 105 }
399 -
400 - let html = '<div class="mxch-pagination-btns">';
401 -
402 - if (currentPage > 1) {
403 - html += `<button class="mxch-page-btn" data-page="${currentPage - 1}">&laquo;</button>`;
404 - }
405 -
406 - html += `<span class="mxch-page-info">${currentPage} / ${totalPages}</span>`;
407 -
408 - if (currentPage < totalPages) {
409 - html += `<button class="mxch-page-btn" data-page="${currentPage + 1}">&raquo;</button>`;
410 - }
411 -
412 - html += '</div>';
413 - $container.html(html);
414 -
415 - // Pagination click handlers
416 - $('.mxch-page-btn').on('click', function() {
417 - const pageNum = $(this).data('page');
418 - loadChatList(pageNum, searchTerm);
419 - });
420 - }
421 -
422 - // ==========================================================================
423 - // Conversation Panel
424 - // ==========================================================================
425 -
426 - // Select and load a chat conversation
427 - function selectChat(sessionId) {
428 - currentSessionId = sessionId;
429 -
430 - // Reset translation state when selecting new chat
431 - if (typeof resetTranslationState === 'function') {
432 - resetTranslationState();
433 - }
434 -
435 - // Show loading in conversation panel
436 - $('#mxch-conversation-empty').hide();
437 - $('#mxch-conversation-content').show();
438 - $('#mxch-messages-area').html('<div class="mxch-messages-loading"><span class="spinner is-active"></span> Loading conversation...</div>');
439 -
440 - $.ajax({
441 - url: ajaxurl,
442 - type: 'POST',
443 - data: {
444 - action: 'mxchat_fetch_conversation',
445 - session_id: sessionId
446 - },
447 - success: function(response) {
448 - if (response.success) {
449 - renderConversation(response);
450 - // Load saved translation after rendering
451 - if (typeof loadSavedTranslation === 'function') {
452 - setTimeout(function() {
453 - loadSavedTranslation(sessionId);
454 - }, 100);
455 - }
456 - } else {
457 - $('#mxch-messages-area').html('<div class="mxch-messages-error">Failed to load conversation</div>');
458 - }
459 - },
460 - error: function() {
461 - $('#mxch-messages-area').html('<div class="mxch-messages-error">Error loading conversation</div>');
462 - }
463 - });
464 - }
465 -
466 - // Render conversation content
467 - function renderConversation(data) {
468 - // Update header
469 - $('#mxch-user-avatar span').text(data.user.initials);
470 - $('#mxch-user-name').text(data.user.name);
471 - $('#mxch-user-meta').text(data.user.sub);
472 -
473 - // Update details drawer
474 - $('#mxch-detail-messages').text(data.message_count);
475 - $('#mxch-detail-started').text(data.started);
476 -
477 - if (data.page.url) {
478 - $('#mxch-detail-page').html(`<a href="${escapeHtml(data.page.url)}" target="_blank">${escapeHtml(data.page.title || data.page.url)}</a>`);
479 - } else {
480 - $('#mxch-detail-page').text('-');
481 - }
482 -
483 - if (data.user.email) {
484 - $('#mxch-detail-email').text(data.user.email);
485 - $('#mxch-detail-email-row').show();
486 - } else {
487 - $('#mxch-detail-email-row').hide();
488 - }
489 -
490 - // Clicked links
491 - if (data.clicked_urls && data.clicked_urls.length > 0) {
492 - let linksHtml = '';
493 - data.clicked_urls.forEach(function(url) {
494 - linksHtml += `<a href="${escapeHtml(url)}" target="_blank" class="mxch-clicked-link">${escapeHtml(url)}</a>`;
495 - });
496 - $('#mxch-clicked-links').html(linksHtml);
497 - $('#mxch-clicked-section').show();
498 - } else {
499 - $('#mxch-clicked-section').hide();
500 - }
501 -
502 - // Render messages
503 - let messagesHtml = '';
504 - data.messages.forEach(function(msg) {
505 - if (msg.is_user) {
506 - messagesHtml += `
507 - <div class="mxch-message mxch-message-user" data-message-id="${msg.id}">
508 - <div class="mxch-message-row">
509 - <div class="mxch-message-bubble">
510 - ${msg.content}
511 - </div>
512 - </div>
513 - <div class="mxch-message-time">${escapeHtml(msg.timestamp)}</div>
514 - </div>
515 - `;
516 - } else {
517 - const ragLink = msg.has_rag ? `<a href="#" class="mxch-rag-link" data-message-id="${msg.id}">Sources</a>` : '';
518 - messagesHtml += `
519 - <div class="mxch-message mxch-message-bot" data-message-id="${msg.id}">
520 - <div class="mxch-message-header">
521 - <span class="mxch-bot-label">AI Assistant</span>
522 - ${ragLink}
523 - </div>
524 - <div class="mxch-message-row">
525 - <div class="mxch-message-bubble">
526 - ${msg.content}
527 - </div>
528 - </div>
529 - <div class="mxch-message-time">${escapeHtml(msg.timestamp)}</div>
530 - </div>
531 - `;
532 - }
533 - });
534 -
535 - $('#mxch-messages-area').html(messagesHtml);
536 -
537 - // Scroll to bottom
538 - const $area = $('#mxch-messages-area');
539 - $area.scrollTop($area[0].scrollHeight);
540 -
541 - // Attach RAG link handlers
542 - $('.mxch-rag-link').on('click', function(e) {
543 - e.preventDefault();
544 - const messageId = $(this).data('message-id');
545 - if (messageId) {
546 - openRagContextModal(messageId);
547 - }
548 - });
549 - }
550 -
551 - // Toggle details drawer
552 - $('#mxch-toggle-details').on('click', function() {
553 - const $drawer = $('#mxch-details-drawer');
554 - const $btn = $(this);
555 -
556 - if ($drawer.is(':visible')) {
557 - $drawer.slideUp(200);
558 - $btn.removeClass('active');
559 - } else {
560 - $drawer.slideDown(200);
561 - $btn.addClass('active');
562 - }
563 - });
564 -
565 - // Delete current chat
566 - $('#mxch-delete-current').on('click', function() {
567 - if (!currentSessionId) return;
568 -
569 - if (!confirm('Are you sure you want to delete this conversation? This action cannot be undone.')) {
106 +
107 + // Confirm deletion
108 + if (!confirm("Are you sure you want to delete the selected chat sessions? This action cannot be undone.")) {
570 109 return;
571 110 }
572 -
573 - deleteSession(currentSessionId);
574 - });
575 -
576 - // Delete session function
577 - function deleteSession(sessionId) {
111 +
578 112 $.ajax({
579 113 url: ajaxurl,
580 114 type: 'POST',
581 115 data: {
582 116 action: 'mxchat_delete_chat_history',
583 - delete_session_ids: [sessionId],
117 + delete_session_ids: checkedSessionIds,
584 118 security: $('#mxchat_delete_chat_nonce').val()
585 119 },
586 120 success: function(response) {
587 - try {
588 - const jsonResponse = typeof response === 'object' ? response : JSON.parse(response);
589 -
590 - if (jsonResponse.success) {
591 - // Reset conversation panel
592 - currentSessionId = null;
593 - $('#mxch-conversation-content').hide();
594 - $('#mxch-conversation-empty').show();
595 - $('#mxch-details-drawer').hide();
596 -
597 - // Reload list
598 - loadChatList(currentPage, $('#mxch-search-transcripts').val());
599 - } else if (jsonResponse.error) {
600 - alert('Error: ' + jsonResponse.error);
601 - }
602 - } catch (e) {
603 - alert('An error occurred while processing the response.');
121 + var jsonResponse = JSON.parse(response);
122 + if (jsonResponse.success) {
123 + alert("Success: " + jsonResponse.success);
124 + } else if (jsonResponse.error) {
125 + alert("Error: " + jsonResponse.error);
126 + } else {
127 + //console.log("Unexpected response format.");
604 128 }
129 +
130 + // Reload the current page of transcripts
131 + loadTranscripts(currentPage);
605 132 },
606 - error: function() {
607 - alert('An error occurred while deleting the conversation.');
133 + error: function(xhr, status, error) {
134 + //console.error("AJAX Error: " + status + " - " + error);
135 + //console.log(xhr.responseText);
136 + alert("An error occurred while deleting chat sessions. Please try again.");
608 137 }
609 138 });
610 - }
611 -
612 - // ==========================================================================
613 - // Export Functionality
614 - // ==========================================================================
615 -
616 - $('#mxch-export-btn, #mxch-export-current').on('click', function() {
617 - const $button = $(this);
139 + });
140 +
141 + // Export functionality - this remains unchanged as it should export all transcripts
142 + $('#mxchat-export-transcripts').on('click', function() {
143 + var $button = $(this);
618 144 $button.prop('disabled', true).addClass('loading');
619 145
620 - const $form = $('<form>', {
621 - method: 'post',
622 - action: ajaxurl
146 + // Create a form and submit it
147 + var $form = $('<form>', {
148 + 'method': 'post',
149 + 'action': ajaxurl
623 150 });
624 151
625 152 $form.append($('<input>', {
626 - type: 'hidden',
627 - name: 'action',
628 - value: 'mxchat_export_transcripts'
153 + 'type': 'hidden',
154 + 'name': 'action',
155 + 'value': 'mxchat_export_transcripts'
629 156 }));
630 157
631 158 $form.append($('<input>', {
632 - type: 'hidden',
633 - name: 'security',
634 - value: mxchatAdmin.export_nonce
159 + 'type': 'hidden',
160 + 'name': 'security',
161 + 'value': mxchatAdmin.export_nonce
635 162 }));
636 163
637 164 $form.appendTo('body').submit();
638 165
166 + // Re-enable the button after a short delay
639 167 setTimeout(function() {
640 168 $button.prop('disabled', false).removeClass('loading');
641 169 }, 2000);
642 170 });
643 -
644 - // ==========================================================================
645 - // Translation Functionality
646 - // ==========================================================================
647 -
648 - // Store original messages for reverting
649 - let originalMessages = null;
650 - let isTranslated = false;
651 - let currentTranslationLang = null;
652 -
653 - // Load saved language preference from localStorage
654 - const savedLang = localStorage.getItem('mxch_translate_lang');
655 - if (savedLang) {
656 - $('#mxch-translate-lang').val(savedLang);
657 - }
658 -
659 - // Save language preference when changed
660 - $('#mxch-translate-lang').on('change', function() {
661 - localStorage.setItem('mxch_translate_lang', $(this).val());
171 +
172 + // Chat Email Notification Modal functionality
173 +
174 + // Open modal
175 + $('#mxchat-chat-email-notification-btn').on('click', function(e) {
176 + e.preventDefault();
177 + $('#mxchat-chat-email-notification-modal').fadeIn(300);
662 178 });
663 -
664 - // Apply translations to messages
665 - function applyTranslations(translations) {
666 - // Store original messages if not already stored
667 - if (!originalMessages) {
668 - originalMessages = [];
669 - $('#mxch-messages-area .mxch-message-bubble').each(function() {
670 - originalMessages.push($(this).html());
671 - });
672 - }
673 -
674 - // Apply translations
675 - translations.forEach(function(item) {
676 - const $bubble = $('#mxch-messages-area .mxch-message-bubble').eq(item.index);
677 - if ($bubble.length) {
678 - $bubble.html(item.translated);
679 - $bubble.addClass('translated');
680 - }
681 - });
682 -
683 - isTranslated = true;
684 - $('#mxch-show-original-btn').show();
685 - }
686 -
687 - // Load saved translation for current session
688 - function loadSavedTranslation(sessionId) {
689 - $.ajax({
690 - url: ajaxurl,
691 - type: 'POST',
692 - data: {
693 - action: 'mxchat_get_transcript_translation',
694 - session_id: sessionId
695 - },
696 - success: function(response) {
697 - if (response.success && response.has_translation) {
698 - currentTranslationLang = response.language;
699 - applyTranslations(response.translations);
700 - // Update language selector to show saved language
701 - $('#mxch-translate-lang').val(response.language);
702 - }
703 - }
704 - });
705 - }
706 -
707 - // Translate button click handler
708 - $('#mxch-translate-btn').on('click', function() {
709 - if (!currentSessionId) return;
710 -
711 - const $btn = $(this);
712 - const targetLang = $('#mxch-translate-lang').val();
713 -
714 - // Disable button and show loading state
715 - $btn.prop('disabled', true);
716 - $btn.find('.mxch-translate-text').text('Translating...');
717 - $btn.find('svg').addClass('mxch-translate-spinner');
718 -
719 - // Store original messages before translation
720 - if (!originalMessages) {
721 - originalMessages = [];
722 - $('#mxch-messages-area .mxch-message-bubble').each(function() {
723 - originalMessages.push($(this).html());
724 - });
725 - }
726 -
727 - // If already translated, restore originals first before re-translating
728 - if (isTranslated) {
729 - $('#mxch-messages-area .mxch-message-bubble').each(function(index) {
730 - if (originalMessages[index]) {
731 - $(this).html(originalMessages[index]);
732 - $(this).removeClass('translated');
733 - }
734 - });
735 - }
736 -
737 - // Collect all message content (from originals)
738 - const messages = [];
739 - originalMessages.forEach(function(html, index) {
740 - // Create temp element to get text content
741 - const $temp = $('<div>').html(html);
742 - messages.push({
743 - index: index,
744 - content: $temp.text().trim()
745 - });
746 - });
747 -
748 - // Send translation request
749 - $.ajax({
750 - url: ajaxurl,
751 - type: 'POST',
752 - data: {
753 - action: 'mxchat_translate_messages',
754 - session_id: currentSessionId,
755 - target_lang: targetLang,
756 - messages: JSON.stringify(messages),
757 - security: mxchatAdmin.translate_nonce || ''
758 - },
759 - success: function(response) {
760 - if (response.success && response.translations) {
761 - currentTranslationLang = response.language;
762 - applyTranslations(response.translations);
763 - $btn.find('.mxch-translate-text').text('Translate');
764 - } else {
765 - alert(response.error || 'Translation failed. Please try again.');
766 - $btn.find('.mxch-translate-text').text('Translate');
767 - }
768 - },
769 - error: function() {
770 - alert('Translation request failed. Please try again.');
771 - $btn.find('.mxch-translate-text').text('Translate');
772 - },
773 - complete: function() {
774 - $btn.prop('disabled', false);
775 - $btn.find('svg').removeClass('mxch-translate-spinner');
776 - }
777 - });
179 +
180 + // Close modal
181 + $('.mxchat-chat-notification-modal-close, .mxchat-chat-notification-modal-cancel').on('click', function() {
182 + $('#mxchat-chat-email-notification-modal').fadeOut(300);
778 183 });
779 -
780 - // Show original button click handler
781 - $('#mxch-show-original-btn').on('click', function() {
782 - if (!originalMessages) return;
783 -
784 - // Restore original messages
785 - $('#mxch-messages-area .mxch-message-bubble').each(function(index) {
786 - if (originalMessages[index]) {
787 - $(this).html(originalMessages[index]);
788 - $(this).removeClass('translated');
789 - }
790 - });
791 -
792 - isTranslated = false;
793 - $(this).hide();
794 - });
795 -
796 - // Reset translation state (called when selecting new chat)
797 - function resetTranslationState() {
798 - originalMessages = null;
799 - isTranslated = false;
800 - currentTranslationLang = null;
801 - $('#mxch-show-original-btn').hide();
802 - }
803 -
804 - // Make functions available to selectChat
805 - window.resetTranslationState = resetTranslationState;
806 - window.loadSavedTranslation = loadSavedTranslation;
807 -
808 - // ==========================================================================
809 - // RAG Context Modal (Sources & Actions Tabs)
810 - // ==========================================================================
811 -
812 - function openRagContextModal(messageId) {
813 - const $modal = $('#mxch-rag-modal');
814 - const $loading = $modal.find('.mxch-rag-loading');
815 - const $sourcesContent = $modal.find('.mxch-rag-content');
816 - const $actionsContent = $modal.find('.mxch-actions-content');
817 -
818 - // Reset to Sources tab
819 - $modal.find('.mxch-context-tab').removeClass('active');
820 - $modal.find('.mxch-context-tab[data-tab="sources"]').addClass('active');
821 - $('#mxch-tab-sources').show();
822 - $('#mxch-tab-actions').hide();
823 -
824 - // Reset badge counts
825 - $('#mxch-sources-count, #mxch-actions-count').hide().text('0');
826 -
827 - $modal.fadeIn(200);
828 - $loading.show();
829 - $sourcesContent.html('');
830 - $actionsContent.html('');
831 -
832 - $.ajax({
833 - url: ajaxurl,
834 - type: 'POST',
835 - data: {
836 - action: 'mxchat_get_rag_context',
837 - message_id: messageId
838 - },
839 - success: function(response) {
840 - $loading.hide();
841 -
842 - if (response.success && response.data) {
843 - // Render sources tab
844 - renderRagContext(response.data, $sourcesContent);
845 -
846 - // Render actions tab
847 - renderActionsContext(response.data, $actionsContent);
848 -
849 - // Update badge counts
850 - const sourcesCount = response.data.top_matches ? response.data.top_matches.length : 0;
851 - const actionsCount = response.data.action_analysis ? response.data.action_analysis.length : 0;
852 -
853 - if (sourcesCount > 0) {
854 - $('#mxch-sources-count').text(sourcesCount).show();
855 - }
856 - if (actionsCount > 0) {
857 - $('#mxch-actions-count').text(actionsCount).show();
858 - }
859 - } else {
860 - $sourcesContent.html('<div class="mxch-rag-error">Unable to load document context.</div>');
861 - $actionsContent.html('<div class="mxch-rag-error">No action data available.</div>');
862 - }
863 - },
864 - error: function() {
865 - $loading.hide();
866 - $sourcesContent.html('<div class="mxch-rag-error">Error loading context. Please try again.</div>');
867 - $actionsContent.html('<div class="mxch-rag-error">Error loading context. Please try again.</div>');
868 - }
869 - });
870 - }
871 -
872 - // Tab switching
873 - $(document).on('click', '.mxch-context-tab', function() {
874 - const $tab = $(this);
875 - const tabName = $tab.data('tab');
876 -
877 - // Update active tab
878 - $('.mxch-context-tab').removeClass('active');
879 - $tab.addClass('active');
880 -
881 - // Show/hide content
882 - $('.mxch-tab-content').hide();
883 - $('#mxch-tab-' + tabName).show();
884 - });
885 -
886 - function renderRagContext(data, $container) {
887 - let html = '';
888 -
889 - // Check if we have any source data
890 - if (!data.top_matches || data.top_matches.length === 0) {
891 - html += '<div class="mxch-no-results"><p>No document matches found for this response.</p></div>';
892 - $container.html(html);
893 - return;
184 +
185 + // Close modal on outside click
186 + $('#mxchat-chat-email-notification-modal').on('click', function(e) {
187 + if ($(e.target).is('#mxchat-chat-email-notification-modal')) {
188 + $(this).fadeOut(300);
894 189 }
895 -
896 - html += '<div class="mxch-rag-summary">';
897 - html += '<div class="mxch-rag-summary-item"><span class="mxch-rag-label">Knowledge Base:</span> <span class="mxch-rag-value">' + escapeHtml(data.knowledge_base_type || 'WordPress Database') + '</span></div>';
898 - html += '<div class="mxch-rag-summary-item"><span class="mxch-rag-label">Similarity Threshold:</span> <span class="mxch-rag-value">' + Math.round((data.similarity_threshold || 0.35) * 100) + '%</span></div>';
899 - html += '<div class="mxch-rag-summary-item"><span class="mxch-rag-label">Documents Checked:</span> <span class="mxch-rag-value">' + (data.total_documents_checked || 0) + '</span></div>';
900 - html += '</div>';
901 -
902 - const groupedByUrl = {};
903 -
904 - data.top_matches.forEach(function(match) {
905 - const url = match.source_display || 'Unknown';
906 - if (!groupedByUrl[url]) {
907 - groupedByUrl[url] = {
908 - url: url,
909 - isUrl: url.startsWith('http'),
910 - bestScore: 0,
911 - usedForContext: false,
912 - matchedChunks: []
913 - };
914 - }
915 -
916 - if (match.similarity_percentage > groupedByUrl[url].bestScore) {
917 - groupedByUrl[url].bestScore = match.similarity_percentage;
918 - }
919 -
920 - if (match.used_for_context) {
921 - groupedByUrl[url].usedForContext = true;
922 - }
923 -
924 - groupedByUrl[url].matchedChunks.push({
925 - chunkIndex: match.chunk_index,
926 - score: match.similarity_percentage,
927 - usedForContext: match.used_for_context
928 - });
929 - });
930 -
931 - const urlGroups = Object.values(groupedByUrl).sort((a, b) => b.bestScore - a.bestScore);
932 - const usedUrlCount = data.sources_used > 0 ? data.sources_used : urlGroups.filter(g => g.usedForContext).length;
933 - const chunksInfo = data.total_chunks_used > 0 ? data.total_chunks_used + ' chunks sent to AI' : '';
934 -
935 - html += '<div class="mxch-rag-matches">';
936 - html += '<h3>Retrieved Documents</h3>';
937 - html += '<p style="color: var(--mxch-text-secondary); font-size: 13px; margin-bottom: 16px;">' + usedUrlCount + ' source' + (usedUrlCount === 1 ? '' : 's') + ' used for response' + (chunksInfo ? ' &middot; ' + chunksInfo : '') + '</p>';
938 -
939 - urlGroups.forEach(function(group) {
940 - const cardClass = group.usedForContext ? 'mxch-rag-match-used' : 'mxch-rag-match-below';
941 - const statusIcon = group.usedForContext ? '&#10003;' : '&#10007;';
942 - const statusLabel = group.usedForContext ? 'Used' : 'Not Used';
943 -
944 - html += '<div class="mxch-rag-match-card ' + cardClass + '">';
945 - html += '<div class="mxch-rag-match-header">';
946 - html += '<span class="mxch-rag-match-score">' + group.bestScore + '%</span>';
947 -
948 - if (group.matchedChunks.length > 1) {
949 - html += '<span class="mxch-rag-chunk-badge">' + group.matchedChunks.length + ' chunks</span>';
950 - }
951 -
952 - html += '<span class="mxch-rag-match-status ' + (group.usedForContext ? 'status-used' : 'status-below') + '">' + statusIcon + ' ' + statusLabel + '</span>';
953 - html += '</div>';
954 -
955 - html += '<div class="mxch-rag-match-source">';
956 - if (group.isUrl) {
957 - html += '<a href="' + escapeHtml(group.url) + '" target="_blank">' + escapeHtml(group.url) + '</a>';
958 - } else {
959 - html += escapeHtml(group.url);
960 - }
961 - html += '</div>';
962 - html += '</div>';
963 - });
964 -
965 - html += '</div>';
966 - $container.html(html);
967 - }
968 -
969 - function renderActionsContext(data, $container) {
970 - let html = '';
971 -
972 - // Check if we have action analysis data
973 - if (!data.action_analysis || data.action_analysis.length === 0) {
974 - html += '<div class="mxch-no-results"><p>No action analysis available for this message.</p><p style="color: var(--mxch-text-secondary); font-size: 13px; margin-top: 8px;">Actions are only evaluated when enabled in your bot configuration.</p></div>';
975 - $container.html(html);
976 - return;
977 - }
978 -
979 - const actions = data.action_analysis;
980 - const triggeredAction = actions.find(a => a.triggered);
981 - const actionsAboveThreshold = actions.filter(a => a.above_threshold).length;
982 -
983 - // Summary section
984 - html += '<div class="mxch-rag-summary">';
985 - html += '<div class="mxch-rag-summary-item"><span class="mxch-rag-label">Actions Evaluated:</span> <span class="mxch-rag-value">' + actions.length + '</span></div>';
986 - html += '<div class="mxch-rag-summary-item"><span class="mxch-rag-label">Above Threshold:</span> <span class="mxch-rag-value">' + actionsAboveThreshold + '</span></div>';
987 - if (triggeredAction) {
988 - html += '<div class="mxch-rag-summary-item"><span class="mxch-rag-label">Triggered:</span> <span class="mxch-rag-value" style="color: #10b981; font-weight: 600;">' + escapeHtml(triggeredAction.intent_label) + '</span></div>';
989 - }
990 - html += '</div>';
991 -
992 - // Actions list
993 - html += '<div class="mxch-rag-matches">';
994 - html += '<h3>Action Scores</h3>';
995 - html += '<p style="color: var(--mxch-text-secondary); font-size: 13px; margin-bottom: 16px;">Showing all evaluated actions sorted by similarity score</p>';
996 -
997 - actions.forEach(function(action) {
998 - let cardClass = 'mxch-rag-match-below';
999 - let statusIcon = '&#10007;';
1000 - let statusLabel = 'Below Threshold';
1001 -
1002 - if (action.triggered) {
1003 - cardClass = 'mxch-action-triggered';
1004 - statusIcon = '&#9889;';
1005 - statusLabel = 'Triggered';
1006 - } else if (action.above_threshold) {
1007 - cardClass = 'mxch-rag-match-used';
1008 - statusIcon = '&#10003;';
1009 - statusLabel = 'Above Threshold';
1010 - }
1011 -
1012 - html += '<div class="mxch-rag-match-card ' + cardClass + '">';
1013 - html += '<div class="mxch-rag-match-header">';
1014 - html += '<span class="mxch-rag-match-score">' + action.similarity_percentage + '%</span>';
1015 - html += '<span class="mxch-action-threshold-badge">Threshold: ' + action.threshold_percentage + '%</span>';
1016 - html += '<span class="mxch-rag-match-status ' + (action.triggered ? 'status-triggered' : (action.above_threshold ? 'status-used' : 'status-below')) + '">' + statusIcon + ' ' + statusLabel + '</span>';
1017 - html += '</div>';
1018 -
1019 - html += '<div class="mxch-action-details">';
1020 - html += '<div class="mxch-action-label">' + escapeHtml(action.intent_label) + '</div>';
1021 - html += '<div class="mxch-action-callback"><span class="mxch-action-callback-label">Callback:</span> ' + escapeHtml(action.callback_function) + '</div>';
1022 - html += '</div>';
1023 -
1024 - // Score bar visualization
1025 - const scoreBarWidth = Math.min(action.similarity_percentage, 100);
1026 - const thresholdPos = Math.min(action.threshold_percentage, 100);
1027 - html += '<div class="mxch-action-score-bar">';
1028 - html += '<div class="mxch-action-score-fill" style="width: ' + scoreBarWidth + '%;"></div>';
1029 - html += '<div class="mxch-action-threshold-marker" style="left: ' + thresholdPos + '%;"></div>';
1030 - html += '</div>';
1031 -
1032 - html += '</div>';
1033 - });
1034 -
1035 - html += '</div>';
1036 - $container.html(html);
1037 - }
1038 -
1039 - function escapeHtml(text) {
1040 - if (!text) return '';
1041 - const div = document.createElement('div');
1042 - div.textContent = text;
1043 - return div.innerHTML;
1044 - }
1045 -
1046 - // Close RAG modal
1047 - $('.mxch-modal-close').on('click', function() {
1048 - $(this).closest('.mxch-modal-overlay').fadeOut(200);
1049 190 });
1050 -
1051 - $('.mxch-modal-overlay').on('click', function(e) {
1052 - if ($(e.target).is('.mxch-modal-overlay')) {
1053 - $(this).fadeOut(200);
1054 - }
191 +
192 + // Handle form submission - Let WordPress handle it normally for settings
193 + $('#mxchat-chat-email-notification-form').on('submit', function(e) {
194 + // Don't prevent default - let the form submit normally to WordPress options.php
195 + var $submitButton = $(this).find('button[type="submit"]');
196 + var originalText = $submitButton.text();
197 +
198 + // Just show a loading state
199 + $submitButton.text('Saving...').prop('disabled', true);
200 +
201 + // The form will submit normally and reload the page
1055 202 });
1056 -
1057 - $(document).on('keydown', function(e) {
1058 - if (e.key === 'Escape') {
1059 - $('.mxch-modal-overlay').fadeOut(200);
1060 - }
1061 - });
1062 -
1063 - // ==========================================================================
1064 - // Activity Chart
1065 - // ==========================================================================
1066 -
1067 - // Simple chart implementation (no external dependencies)
1068 - class SimpleChart {
1069 - constructor(canvas, config) {
1070 - this.canvas = canvas;
1071 - this.ctx = canvas.getContext('2d');
1072 - this.config = config;
1073 - this.padding = { top: 20, right: 20, bottom: 40, left: 50 };
1074 - this.render();
1075 - }
1076 -
1077 - destroy() {
1078 - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
1079 - }
1080 -
1081 - render() {
1082 - const dpr = window.devicePixelRatio || 1;
1083 - const rect = this.canvas.getBoundingClientRect();
1084 -
1085 - this.canvas.width = rect.width * dpr;
1086 - this.canvas.height = rect.height * dpr;
1087 - this.ctx.scale(dpr, dpr);
1088 -
1089 - this.canvas.style.width = rect.width + 'px';
1090 - this.canvas.style.height = rect.height + 'px';
1091 -
1092 - const width = rect.width - this.padding.left - this.padding.right;
1093 - const height = rect.height - this.padding.top - this.padding.bottom;
1094 -
1095 - // Find max value
1096 - let maxValue = 0;
1097 - this.config.datasets.forEach(dataset => {
1098 - const max = Math.max(...dataset.data);
1099 - if (max > maxValue) maxValue = max;
1100 - });
1101 -
1102 - // Add some padding to max value
1103 - maxValue = Math.ceil(maxValue * 1.1);
1104 - if (maxValue === 0) maxValue = 10;
1105 -
1106 - // Draw grid lines
1107 - this.ctx.strokeStyle = '#e5e7eb';
1108 - this.ctx.lineWidth = 1;
1109 - const gridLines = 5;
1110 -
1111 - for (let i = 0; i <= gridLines; i++) {
1112 - const y = this.padding.top + (height / gridLines) * i;
1113 - this.ctx.beginPath();
1114 - this.ctx.moveTo(this.padding.left, y);
1115 - this.ctx.lineTo(this.padding.left + width, y);
1116 - this.ctx.stroke();
1117 -
1118 - // Draw y-axis labels
1119 - const value = maxValue - (maxValue / gridLines) * i;
1120 - this.ctx.fillStyle = '#6b7280';
1121 - this.ctx.font = '12px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
1122 - this.ctx.textAlign = 'right';
1123 - this.ctx.fillText(Math.round(value), this.padding.left - 10, y + 4);
1124 - }
1125 -
1126 - // Draw datasets
1127 - this.config.datasets.forEach(dataset => {
1128 - const points = [];
1129 - const xStep = width / (this.config.labels.length - 1 || 1);
1130 -
1131 - dataset.data.forEach((value, index) => {
1132 - const x = this.padding.left + (xStep * index);
1133 - const y = this.padding.top + height - (value / maxValue * height);
1134 - points.push({ x, y, value });
1135 - });
1136 -
1137 - // Draw filled area
1138 - if (dataset.fill && dataset.backgroundColor) {
1139 - this.ctx.fillStyle = dataset.backgroundColor;
1140 - this.ctx.beginPath();
1141 - this.ctx.moveTo(points[0].x, this.padding.top + height);
1142 - points.forEach(point => {
1143 - this.ctx.lineTo(point.x, point.y);
1144 - });
1145 - this.ctx.lineTo(points[points.length - 1].x, this.padding.top + height);
1146 - this.ctx.closePath();
1147 - this.ctx.fill();
1148 - }
1149 -
1150 - // Draw line
1151 - this.ctx.strokeStyle = dataset.borderColor;
1152 - this.ctx.lineWidth = 3;
1153 - this.ctx.lineCap = 'round';
1154 - this.ctx.lineJoin = 'round';
1155 -
1156 - this.ctx.beginPath();
1157 - points.forEach((point, index) => {
1158 - if (index === 0) {
1159 - this.ctx.moveTo(point.x, point.y);
1160 - } else {
1161 - this.ctx.lineTo(point.x, point.y);
1162 - }
1163 - });
1164 - this.ctx.stroke();
1165 -
1166 - // Draw points
1167 - points.forEach(point => {
1168 - this.ctx.fillStyle = '#ffffff';
1169 - this.ctx.beginPath();
1170 - this.ctx.arc(point.x, point.y, 5, 0, Math.PI * 2);
1171 - this.ctx.fill();
1172 - this.ctx.strokeStyle = dataset.borderColor;
1173 - this.ctx.lineWidth = 2;
1174 - this.ctx.stroke();
1175 - });
1176 - });
1177 -
1178 - // Draw x-axis labels
1179 - const xStep = width / (this.config.labels.length - 1 || 1);
1180 - this.ctx.fillStyle = '#6b7280';
1181 - this.ctx.font = '12px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
1182 - this.ctx.textAlign = 'center';
1183 -
1184 - this.config.labels.forEach((label, index) => {
1185 - const x = this.padding.left + (xStep * index);
1186 - this.ctx.fillText(label, x, this.padding.top + height + 20);
1187 - });
1188 - }
1189 - }
1190 -
1191 - // Initialize activity chart
1192 - function initActivityChart() {
1193 - console.log('[MxChat Chart] initActivityChart called');
1194 -
1195 - const canvas = document.getElementById('mxchat-activity-chart');
1196 - console.log('[MxChat Chart] Canvas element:', canvas);
1197 -
1198 - if (!canvas) {
1199 - console.log('[MxChat Chart] Canvas not found, aborting');
1200 - return;
1201 - }
1202 -
1203 - console.log('[MxChat Chart] mxchatChartData exists:', typeof mxchatChartData !== 'undefined');
1204 - if (typeof mxchatChartData === 'undefined') {
1205 - console.log('[MxChat Chart] mxchatChartData is undefined, aborting');
1206 - return;
1207 - }
1208 -
1209 - console.log('[MxChat Chart] Raw mxchatChartData:', mxchatChartData);
1210 -
1211 - // Check if chart already exists and destroy it
1212 - if (canvas.chartInstance) {
1213 - canvas.chartInstance.destroy();
1214 - }
1215 -
1216 - const ctx = canvas.getContext('2d');
1217 - console.log('[MxChat Chart] Canvas context:', ctx);
1218 - console.log('[MxChat Chart] Canvas dimensions:', canvas.getBoundingClientRect());
1219 -
1220 - // Create gradient for chats line
1221 - const chatsGradient = ctx.createLinearGradient(0, 0, 0, 300);
1222 - chatsGradient.addColorStop(0, 'rgba(102, 126, 234, 0.3)');
1223 - chatsGradient.addColorStop(1, 'rgba(102, 126, 234, 0.05)');
1224 -
1225 - // Create gradient for messages line
1226 - const messagesGradient = ctx.createLinearGradient(0, 0, 0, 300);
1227 - messagesGradient.addColorStop(0, 'rgba(118, 75, 162, 0.3)');
1228 - messagesGradient.addColorStop(1, 'rgba(118, 75, 162, 0.05)');
1229 -
1230 - // Convert wp_localize_script objects to arrays (WordPress converts indexed arrays to objects)
1231 - const labels = Object.values(mxchatChartData.labels);
1232 - const chatsData = Object.values(mxchatChartData.chats).map(Number);
1233 - const messagesData = Object.values(mxchatChartData.messages).map(Number);
1234 -
1235 - console.log('[MxChat Chart] Processed labels:', labels);
1236 - console.log('[MxChat Chart] Processed chatsData:', chatsData);
1237 - console.log('[MxChat Chart] Processed messagesData:', messagesData);
1238 -
1239 - // Create chart
1240 - try {
1241 - canvas.chartInstance = new SimpleChart(canvas, {
1242 - labels: labels,
1243 - datasets: [
1244 - {
1245 - label: 'Chats',
1246 - data: chatsData,
1247 - borderColor: '#667eea',
1248 - backgroundColor: chatsGradient,
1249 - fill: true
1250 - },
1251 - {
1252 - label: 'Messages',
1253 - data: messagesData,
1254 - borderColor: '#764ba2',
1255 - backgroundColor: messagesGradient,
1256 - fill: true
1257 - }
1258 - ]
1259 - });
1260 - console.log('[MxChat Chart] Chart created successfully');
1261 - } catch (error) {
1262 - console.error('[MxChat Chart] Error creating chart:', error);
1263 - }
1264 - }
1265 -
1266 - // Initialize chart on page load (dashboard is shown by default)
1267 - setTimeout(function() {
1268 - initActivityChart();
1269 - }, 100);
1270 -
1271 - // Reinitialize chart on window resize
1272 - let resizeTimeout;
1273 - $(window).on('resize', function() {
1274 - clearTimeout(resizeTimeout);
1275 - resizeTimeout = setTimeout(function() {
1276 - initActivityChart();
1277 - }, 250);
1278 - });
1279 -});
203 +});