PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.21
MxChat – AI Chatbot & Content Generation for WordPress v3.2.21
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 / admin-testing-tab.js

admin-testing-tab.js in MxChat – AI Chatbot & Content Generation for WordPress 3.2.21, at js/admin-testing-tab.js

699 lines 30.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * MxChat Admin Testing Tab
3 * Handles the in-admin testing interface with debug data display.
4 * Ported from test-panel.js but adapted for inline admin use.
5 */
6
7 (function($) {
8 'use strict';
9
10 var AdminTestingPanel = {
11 initialized: false,
12 lastQueryData: null,
13
14 init: function() {
15 if (this.initialized) return;
16
17 // Only initialize when the testing section becomes visible
18 this.bindTabActivation();
19 this.bindActionButtons();
20 this.interceptChatResponses();
21
22 this.initialized = true;
23 this.log('Admin testing panel initialized');
24 },
25
26 // =====================================================
27 // Tab activation - load system info when testing tab is shown
28 // =====================================================
29
30 bindTabActivation: function() {
31 var self = this;
32
33 // Watch for the testing section becoming active
34 var observer = new MutationObserver(function(mutations) {
35 mutations.forEach(function(mutation) {
36 if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
37 var testingSection = document.getElementById('testing');
38 if (testingSection && testingSection.classList.contains('active')) {
39 self.loadSystemInfo();
40 }
41 }
42 });
43 });
44
45 var testingSection = document.getElementById('testing');
46 if (testingSection) {
47 observer.observe(testingSection, { attributes: true });
48
49 // If already active (e.g. direct URL hash)
50 if (testingSection.classList.contains('active')) {
51 self.loadSystemInfo();
52 }
53 }
54 },
55
56 // =====================================================
57 // Action buttons
58 // =====================================================
59
60 bindActionButtons: function() {
61 var self = this;
62
63 $('#mxch-testing-clear-session').on('click', function() {
64 self.clearChatSession();
65 });
66
67 $('#mxch-testing-clear-debug').on('click', function() {
68 self.clearDebugConsole();
69 });
70 },
71
72 // =====================================================
73 // AJAX Interception - capture testing_data from responses
74 // =====================================================
75
76 interceptChatResponses: function() {
77 var self = this;
78
79 // Store reference globally so interception can find us
80 window.mxchatTestPanelInstance = self;
81
82 // Intercept jQuery AJAX calls
83 if (window.jQuery) {
84 var originalAjax = jQuery.ajax;
85
86 jQuery.ajax = function(options) {
87 var originalSuccess = options.success;
88
89 options.success = function(data, textStatus, jqXHR) {
90 // Check if this is a chat request
91 if (options.data &&
92 (options.data.action === 'mxchat_handle_chat_request' ||
93 options.data.action === 'mxchat_stream_chat')) {
94
95 if (self && data && data.testing_data) {
96 self.handleTestingData(data.testing_data);
97 } else if (self && data && data.data && data.data.testing_data) {
98 self.handleTestingData(data.data.testing_data);
99 }
100 }
101
102 if (originalSuccess) {
103 originalSuccess.call(this, data, textStatus, jqXHR);
104 }
105 };
106
107 return originalAjax.call(this, options);
108 };
109 }
110
111 // Intercept fetch API calls (for both JSON and SSE streaming)
112 var originalFetch = window.fetch;
113
114 window.fetch = function() {
115 var args = arguments;
116 return originalFetch.apply(this, args).then(function(response) {
117 if (args[0] && typeof args[0] === 'string' &&
118 (args[0].includes('admin-ajax.php') || args[0].includes('mxchat'))) {
119 var contentType = response.headers.get('content-type') || '';
120
121 if (contentType.includes('application/json')) {
122 // Non-streaming: parse the full JSON response
123 response.clone().json().then(function(data) {
124 if (self && data && data.testing_data) {
125 self.handleTestingData(data.testing_data);
126 } else if (self && data && data.data && data.data.testing_data) {
127 self.handleTestingData(data.data.testing_data);
128 }
129 }).catch(function() {});
130 } else if (contentType.includes('text/event-stream') || contentType.includes('text/plain')) {
131 // Streaming (SSE): read just enough to find the testing_data event
132 // Using clone() so the original stream is unaffected for chat-script.js
133 try {
134 var clonedResponse = response.clone();
135 var reader = clonedResponse.body.getReader();
136 var decoder = new TextDecoder();
137 var sseBuffer = '';
138 var found = false;
139
140 function readChunk() {
141 reader.read().then(function(result) {
142 if (result.done || found) {
143 reader.cancel().catch(function() {});
144 return;
145 }
146
147 sseBuffer += decoder.decode(result.value, { stream: true });
148 var sseLines = sseBuffer.split('\n');
149
150 for (var i = 0; i < sseLines.length; i++) {
151 var sseLine = sseLines[i];
152 if (sseLine.indexOf('data: ') === 0) {
153 var payload = sseLine.substring(6);
154 try {
155 var parsed = JSON.parse(payload);
156 if (parsed && parsed.testing_data) {
157 self.handleTestingData(parsed.testing_data);
158 found = true;
159 reader.cancel().catch(function() {});
160 return;
161 }
162 } catch (e) {}
163 }
164 }
165
166 // Keep reading until we find testing_data or hit a limit
167 if (sseBuffer.length < 50000) {
168 readChunk();
169 } else {
170 reader.cancel().catch(function() {});
171 }
172 }).catch(function() {
173 // Stream read error — safe to ignore
174 });
175 }
176
177 readChunk();
178 } catch (e) {}
179 }
180 }
181 return response;
182 });
183 };
184
185 this.log('Chat interception active');
186 },
187
188 // =====================================================
189 // Handle incoming testing data
190 // =====================================================
191
192 handleTestingData: function(testingData) {
193 this.log('Chat data captured');
194
195 // Update query
196 this.updateLastQuery(testingData.query || 'No query');
197
198 // Update approved URLs
199 this.updateApprovedUrls(testingData.approved_urls || []);
200
201 // Update document matches
202 this.updateTopMatches(
203 testingData.top_matches || [],
204 testingData.similarity_threshold || 0.75,
205 testingData.sources_used || 0,
206 testingData.total_chunks_used || 0
207 );
208
209 // Update action matches
210 this.updateActionMatches(testingData.action_matches || []);
211
212 // Log summary
213 // 58f8b4: URL-guard outcome — surface silent stripping.
214 if (testingData.url_validation) {
215 var uv = testingData.url_validation;
216 if (uv.removed_count > 0) {
217 this.log('URL guard stripped ' + uv.removed_count + ' unapproved link' + (uv.removed_count !== 1 ? 's' : '') + ': ' + (uv.removed_urls || []).join(', '));
218 } else if (uv.checked > 0) {
219 this.log('URL guard: all ' + uv.checked + ' link' + (uv.checked !== 1 ? 's' : '') + ' in the answer approved (' + (uv.strict ? 'strict' : 'lenient') + ' mode)');
220 }
221 }
222
223 if (testingData.knowledge_base_type) {
224 this.log('Knowledge Base: ' + testingData.knowledge_base_type);
225 }
226 if (testingData.similarity_threshold) {
227 this.log('Similarity Threshold: ' + (testingData.similarity_threshold * 100) + '%');
228 }
229 if (testingData.top_matches && testingData.top_matches.length > 0) {
230 var aboveThreshold = testingData.top_matches.filter(function(m) { return m.above_threshold; }).length;
231 this.log(aboveThreshold + ' above threshold, ' + (testingData.top_matches.length - aboveThreshold) + ' below');
232 this.log('Highest similarity: ' + testingData.top_matches[0].similarity_percentage + '%');
233 } else {
234 this.log('No document matches found');
235 }
236 if (testingData.action_matches && testingData.action_matches.length > 0) {
237 var triggered = testingData.action_matches.find(function(a) { return a.triggered; });
238 if (triggered) {
239 this.log('Action Triggered: ' + triggered.intent_label + ' (' + triggered.similarity_percentage + '%)');
240 } else {
241 this.log('No actions triggered - Highest: ' + testingData.action_matches[0].intent_label + ' (' + testingData.action_matches[0].similarity_percentage + '%)');
242 }
243 }
244 },
245
246 // =====================================================
247 // Update UI sections
248 // =====================================================
249
250 updateLastQuery: function(query) {
251 $('#mxch-testing-last-query').text(query);
252 this.lastQueryData = { query: query, timestamp: new Date() };
253 },
254
255 updateApprovedUrls: function(approvedUrls) {
256 var el = $('#mxch-testing-approved-urls');
257
258 if (!approvedUrls || !Array.isArray(approvedUrls) || approvedUrls.length === 0) {
259 el.html('<div class="mxch-testing-no-data">No approved URLs (AI cannot cite links)</div>');
260 return;
261 }
262
263 var html = '<div class="urls-header"><strong>' + approvedUrls.length + ' URL' + (approvedUrls.length !== 1 ? 's' : '') + ' approved for AI citations</strong></div>';
264
265 approvedUrls.forEach(function(url) {
266 var displayUrl = url;
267 try {
268 var urlObj = new URL(url);
269 displayUrl = urlObj.hostname + urlObj.pathname;
270 } catch (e) {}
271
272 html += '<div class="url-card"><div class="url-line">' +
273 '<span class="url-icon">&#128279;</span>' +
274 '<a href="' + url + '" target="_blank" rel="noopener noreferrer" class="url-link" title="' + url + '">' + displayUrl + '</a>' +
275 '</div></div>';
276 });
277
278 html += '<div class="urls-note">The AI can only cite these URLs. Others are automatically removed from responses.</div>';
279 el.html(html);
280 },
281
282 updateActionMatches: function(actionMatches) {
283 var el = $('#mxch-testing-action-scores');
284
285 if (!actionMatches || actionMatches.length === 0) {
286 el.html('<div class="mxch-testing-no-data">No actions checked</div>');
287 return;
288 }
289
290 var html = '<div class="actions-header"><strong>Top ' + actionMatches.length + ' actions checked</strong></div>';
291
292 actionMatches.forEach(function(action) {
293 var isTriggered = action.triggered;
294 var isAboveThreshold = action.above_threshold;
295 var statusIcon = isTriggered ? '&#127919;' : (isAboveThreshold ? '&#9888;&#65039;' : '&#10060;');
296
297 var statusLabel;
298 if (isTriggered) {
299 statusLabel = 'TRIGGERED';
300 } else if (isAboveThreshold) {
301 statusLabel = 'Above threshold';
302 } else {
303 statusLabel = 'Below threshold';
304 }
305
306 var cardClass = isTriggered ? 'action-triggered' : (isAboveThreshold ? 'action-above-threshold' : 'action-below-threshold');
307
308 html += '<div class="action-card ' + cardClass + '">' +
309 '<div class="action-line">' +
310 '<span class="action-icon">' + statusIcon + '</span>' +
311 '<span class="action-name">' + action.intent_label + '</span>' +
312 '<span class="action-score">' + action.similarity_percentage + '%</span>' +
313 '</div>' +
314 '<div class="action-details">' +
315 '<span class="action-status">' + statusLabel + '</span>' +
316 '<span class="action-threshold">Threshold: ' + action.threshold_percentage + '%</span>' +
317 '</div>' +
318 '</div>';
319 });
320
321 el.html(html);
322 },
323
324 updateTopMatches: function(topMatches, threshold, sourcesUsed, totalChunksUsed) {
325 var el = $('#mxch-testing-similarity-scores');
326
327 if (!topMatches || topMatches.length === 0) {
328 el.html('<div class="mxch-testing-no-data">No similarity data available</div>');
329 return;
330 }
331
332 // Hybrid keyword boost (38ffa1): rows carry matched_via + fused_rank
333 // when hybrid retrieval was on for this test message.
334 var hybridOn = topMatches.some(function(m) { return m.matched_via; });
335
336 // Group matches by source URL
337 var groupedByUrl = {};
338 topMatches.forEach(function(match) {
339 var url = match.source_display || 'Unknown';
340 if (!groupedByUrl[url]) {
341 groupedByUrl[url] = {
342 url: url,
343 isUrl: url.indexOf('http') === 0,
344 bestScore: 0,
345 usedForContext: false,
346 totalChunks: match.total_chunks || 1,
347 matchedChunks: [],
348 isChunked: match.is_chunk || false,
349 bestFusedRank: Infinity,
350 viaSet: {}
351 };
352 }
353
354 if (match.similarity_percentage > groupedByUrl[url].bestScore) {
355 groupedByUrl[url].bestScore = match.similarity_percentage;
356 }
357
358 if (match.used_for_context) {
359 groupedByUrl[url].usedForContext = true;
360 }
361
362 if (match.fused_rank && match.fused_rank < groupedByUrl[url].bestFusedRank) {
363 groupedByUrl[url].bestFusedRank = match.fused_rank;
364 }
365 if (match.matched_via) {
366 groupedByUrl[url].viaSet[match.matched_via] = true;
367 }
368
369 groupedByUrl[url].matchedChunks.push({
370 chunkIndex: match.chunk_index,
371 score: match.similarity_percentage,
372 usedForContext: match.used_for_context,
373 aboveThreshold: match.above_threshold
374 });
375 });
376
377 var urlGroups = Object.values(groupedByUrl).sort(function(a, b) {
378 if (hybridOn && a.bestFusedRank !== b.bestFusedRank) {
379 return a.bestFusedRank - b.bestFusedRank;
380 }
381 return b.bestScore - a.bestScore;
382 });
383 var usedUrlCount = sourcesUsed > 0 ? sourcesUsed : urlGroups.filter(function(g) { return g.usedForContext; }).length;
384 var chunksInfo = totalChunksUsed > 0 ? totalChunksUsed + ' chunks sent to AI' : topMatches.length + ' chunk matches';
385
386 var html = '<div class="matches-header">' +
387 '<strong>' + usedUrlCount + ' source' + (usedUrlCount === 1 ? '' : 's') + ' used for AI context</strong>' +
388 '<span class="matches-subheader">(' + chunksInfo + ')</span>' +
389 '</div>';
390
391 var self = this;
392
393 urlGroups.forEach(function(group, groupIndex) {
394 var cardClass = group.usedForContext ? 'above-threshold' : 'below-threshold';
395 var statusIcon = group.usedForContext ? '&#10003;' : '&#10007;';
396 var contextLabel = group.usedForContext ? 'Used for AI context' : 'Not used';
397
398 var chunkSummary = '';
399 if (group.isChunked && group.totalChunks > 1) {
400 var usedChunkCount = group.matchedChunks.filter(function(c) { return c.usedForContext; }).length;
401 chunkSummary = '<span class="chunk-summary">' + usedChunkCount + '/' + group.totalChunks + ' chunks matched</span>';
402 }
403
404 var hasMultipleChunks = group.matchedChunks.length > 1;
405 var expandToggle = hasMultipleChunks
406 ? '<span class="chunk-expand-toggle" data-group="' + groupIndex + '">&#9654; Show chunks</span>'
407 : '';
408
409 var viaChip = '';
410 if (hybridOn) {
411 var vias = Object.keys(group.viaSet);
412 if (vias.length) {
413 var viaLabel = (vias.length > 1 || vias[0] === 'both') ? 'Both'
414 : (vias[0] === 'keyword' ? 'Keyword' : 'Vector');
415 viaChip = '<span class="mxch-rag-via-chip mxch-rag-via-' + viaLabel.toLowerCase() + '">' + viaLabel + '</span>';
416 }
417 }
418
419 html += '<div class="match-card ' + cardClass + '">' +
420 '<div class="match-header">' +
421 '<div class="match-title">' +
422 '<span class="status-icon">' + statusIcon + '</span>' +
423 '<span class="similarity-score">' + group.bestScore + '%</span>' +
424 viaChip +
425 chunkSummary +
426 '</div>' +
427 '<span class="context-label">' + contextLabel + '</span>' +
428 '</div>' +
429 '<div class="match-source">' +
430 (group.isUrl ?
431 '<span class="source-icon link-icon">&#128279;</span> ' + group.url :
432 '<span class="source-icon doc-icon">&#128196;</span> ' + group.url
433 ) +
434 '</div>' +
435 expandToggle +
436 (hasMultipleChunks ? self.renderChunkDetails(group.matchedChunks, groupIndex) : '') +
437 '</div>';
438 });
439
440 el.html(html);
441
442 // Bind expand toggles
443 el.find('.chunk-expand-toggle').on('click', function() {
444 var groupId = $(this).data('group');
445 var details = el.find('.chunk-details[data-group="' + groupId + '"]');
446 var isExpanded = details.toggleClass('expanded').hasClass('expanded');
447 $(this).html(isExpanded ? '&#9660; Hide chunks' : '&#9654; Show chunks');
448 });
449 },
450
451 renderChunkDetails: function(chunks, groupIndex) {
452 var sortedChunks = chunks.slice().sort(function(a, b) { return (a.chunkIndex || 0) - (b.chunkIndex || 0); });
453
454 var html = '<div class="chunk-details" data-group="' + groupIndex + '">';
455
456 sortedChunks.forEach(function(chunk) {
457 var chunkNum = (chunk.chunkIndex !== null && chunk.chunkIndex !== undefined) ? chunk.chunkIndex + 1 : '?';
458 var statusClass = chunk.usedForContext ? 'chunk-used' : 'chunk-not-used';
459 var statusIcon = chunk.usedForContext ? '&#10003;' : '&#9675;';
460
461 html += '<div class="chunk-detail-row ' + statusClass + '">' +
462 '<span class="chunk-detail-icon">' + statusIcon + '</span>' +
463 '<span class="chunk-detail-num">Chunk ' + chunkNum + '</span>' +
464 '<span class="chunk-detail-score">' + chunk.score + '%</span>' +
465 '</div>';
466 });
467
468 html += '</div>';
469 return html;
470 },
471
472 // =====================================================
473 // System info loading
474 // =====================================================
475
476 loadSystemInfo: function() {
477 this.updateSimilarityThreshold();
478 this.updateSystemPrompt();
479 this.updateKnowledgeBaseStatus();
480 },
481
482 updateSimilarityThreshold: function() {
483 $.post(mxchatAdminTestData.ajaxUrl, {
484 action: 'mxchat_get_similarity_threshold',
485 nonce: mxchatAdminTestData.nonce
486 }).done(function(data) {
487 if (data.success) {
488 $('#mxch-testing-threshold').html('<code>' + data.data.threshold_percentage + '</code>');
489 } else {
490 $('#mxch-testing-threshold').html('<span class="error-text">Error loading threshold</span>');
491 }
492 }).fail(function() {
493 $('#mxch-testing-threshold').html('<span class="error-text">Connection error</span>');
494 });
495 },
496
497 updateSystemPrompt: function() {
498 var self = this;
499 var el = $('#mxch-testing-system-prompt');
500 el.text('Loading system prompt...');
501
502 $.post(mxchatAdminTestData.ajaxUrl, {
503 action: 'mxchat_get_system_info',
504 nonce: mxchatAdminTestData.nonce
505 }).done(function(data) {
506 if (data.success) {
507 el.text(data.data.system_prompt || 'No system prompt configured');
508
509 if (data.data.is_openrouter) {
510 self.log('Model: OpenRouter - ' + data.data.openrouter_model);
511 } else {
512 self.log('Model: ' + data.data.selected_model);
513 }
514
515 var apiStatus = data.data.api_status;
516 var configuredApis = Object.keys(apiStatus).filter(function(key) { return apiStatus[key]; });
517 if (configuredApis.length > 0) {
518 self.log('Configured APIs: ' + configuredApis.join(', '));
519 }
520 } else {
521 el.text('Error loading system prompt');
522 }
523 }).fail(function() {
524 el.text('Connection error');
525 });
526 },
527
528 updateKnowledgeBaseStatus: function() {
529 var el = $('#mxch-testing-kb-status');
530 el.html('Checking...');
531
532 $.post(mxchatAdminTestData.ajaxUrl, {
533 action: 'mxchat_get_kb_status',
534 nonce: mxchatAdminTestData.nonce
535 }).done(function(data) {
536 if (data.success) {
537 var kbData = data.data;
538 el.html('<span class="success-text">&#10003; ' + kbData.status + '</span> (' + kbData.type + ' - ' + kbData.documents + ')');
539 } else {
540 el.html('<span class="error-text">Error loading KB status</span>');
541 }
542 }).fail(function() {
543 el.html('<span class="error-text">Connection error</span>');
544 });
545 },
546
547 // =====================================================
548 // Clear session
549 // =====================================================
550
551 clearChatSession: function() {
552 var self = this;
553
554 // Determine the bot ID for the testing chatbot
555 var botId = 'testing';
556
557 // Get current session ID
558 var cookieName = 'mxchat_session_id_' + botId;
559 var sessionId = this.getCookie(cookieName) || this.getCurrentSessionId(botId);
560
561 if (!sessionId) {
562 this.log('No active session found');
563 return;
564 }
565
566 this.log('Clearing session: ' + sessionId);
567
568 // Use MxChatInstances to properly reset
569 if (typeof MxChatInstances !== 'undefined' && typeof MxChatInstances.resetChatSession === 'function') {
570 MxChatInstances.resetChatSession(botId);
571 }
572
573 // Get new session ID
574 var newSessionId = '';
575 if (typeof MxChatInstances !== 'undefined' && typeof MxChatInstances.getChatSession === 'function') {
576 newSessionId = MxChatInstances.getChatSession(botId);
577 }
578
579 if (!newSessionId) {
580 newSessionId = 'mxchat_chat_' + Math.random().toString(36).substr(2, 9);
581 document.cookie = cookieName + '=' + newSessionId + '; path=/; max-age=86400; SameSite=Lax';
582 }
583
584 // Call backend to clear old session
585 $.post(mxchatAdminTestData.ajaxUrl, {
586 action: 'mxchat_start_fresh_session',
587 nonce: mxchatAdminTestData.nonce,
588 old_session_id: sessionId,
589 new_session_id: newSessionId
590 }).done(function(data) {
591 if (data.success) {
592 self.log('Session cleared successfully');
593
594 // Clear the chat UI for the testing bot
595 var chatBox = document.getElementById('chat-box-testing');
596 if (chatBox) {
597 var messages = chatBox.querySelectorAll('.bot-message, .user-message');
598 messages.forEach(function(msg, index) {
599 // Keep first welcome message
600 if (index === 0 && msg.classList.contains('bot-message')) return;
601 msg.remove();
602 });
603 }
604
605 // Clear input
606 var chatInput = document.getElementById('chat-input-testing');
607 if (chatInput) {
608 chatInput.value = '';
609 }
610
611 // Show popular questions again
612 var pq = document.getElementById('mxchat-popular-questions-testing');
613 if (pq) {
614 pq.style.display = 'block';
615 }
616
617 // Clear debug displays
618 self.updateLastQuery('New session started');
619 self.updateTopMatches([], 0, 0, 0);
620 self.updateApprovedUrls([]);
621 self.updateActionMatches([]);
622
623 self.log('Fresh session started: ' + newSessionId);
624 } else {
625 self.log('Error: ' + (data.data && data.data.message ? data.data.message : 'Unknown error'));
626 }
627 }).fail(function() {
628 self.log('Connection error when clearing session');
629 });
630 },
631
632 // =====================================================
633 // Debug console
634 // =====================================================
635
636 log: function(message) {
637 var consoleEl = document.getElementById('mxch-testing-debug-console');
638 if (!consoleEl) return;
639
640 var timestamp = new Date().toLocaleTimeString();
641 var entry = document.createElement('div');
642 entry.className = 'mxch-testing-debug-entry';
643 entry.innerHTML = '<span class="mxch-testing-debug-timestamp">[' + timestamp + ']</span> ' + message;
644 consoleEl.appendChild(entry);
645 consoleEl.scrollTop = consoleEl.scrollHeight;
646
647 // Keep only last 50 entries
648 var entries = consoleEl.querySelectorAll('.mxch-testing-debug-entry');
649 if (entries.length > 50) {
650 entries[0].remove();
651 }
652 },
653
654 clearDebugConsole: function() {
655 var consoleEl = document.getElementById('mxch-testing-debug-console');
656 if (consoleEl) {
657 consoleEl.innerHTML = '<div class="mxch-testing-debug-entry">Debug console cleared...</div>';
658 }
659 },
660
661 // =====================================================
662 // Helpers
663 // =====================================================
664
665 getCookie: function(name) {
666 var value = '; ' + document.cookie;
667 var parts = value.split('; ' + name + '=');
668 if (parts.length === 2) return parts.pop().split(';').shift();
669 return '';
670 },
671
672 getCurrentSessionId: function(botId) {
673 if (typeof MxChatInstances !== 'undefined' && typeof MxChatInstances.getChatSession === 'function') {
674 var session = MxChatInstances.getChatSession(botId);
675 if (session) return session;
676 }
677
678 var cookieId = this.getCookie('mxchat_session_id_' + botId);
679 if (cookieId) return cookieId;
680
681 var chatInput = document.getElementById('chat-input-' + botId);
682 if (chatInput && chatInput.dataset.sessionId) {
683 return chatInput.dataset.sessionId;
684 }
685
686 return '';
687 }
688 };
689
690 // Initialize when DOM is ready
691 $(document).ready(function() {
692 // Only init if we're on the settings page and the testing section exists
693 if (document.getElementById('testing')) {
694 AdminTestingPanel.init();
695 }
696 });
697
698 })(jQuery);
699