/** * MxChat Admin Testing Tab * Handles the in-admin testing interface with debug data display. * Ported from test-panel.js but adapted for inline admin use. */ (function($) { 'use strict'; var AdminTestingPanel = { initialized: false, lastQueryData: null, init: function() { if (this.initialized) return; // Only initialize when the testing section becomes visible this.bindTabActivation(); this.bindActionButtons(); this.interceptChatResponses(); this.initialized = true; this.log('Admin testing panel initialized'); }, // ===================================================== // Tab activation - load system info when testing tab is shown // ===================================================== bindTabActivation: function() { var self = this; // Watch for the testing section becoming active var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'attributes' && mutation.attributeName === 'class') { var testingSection = document.getElementById('testing'); if (testingSection && testingSection.classList.contains('active')) { self.loadSystemInfo(); } } }); }); var testingSection = document.getElementById('testing'); if (testingSection) { observer.observe(testingSection, { attributes: true }); // If already active (e.g. direct URL hash) if (testingSection.classList.contains('active')) { self.loadSystemInfo(); } } }, // ===================================================== // Action buttons // ===================================================== bindActionButtons: function() { var self = this; $('#mxch-testing-clear-session').on('click', function() { self.clearChatSession(); }); $('#mxch-testing-clear-debug').on('click', function() { self.clearDebugConsole(); }); }, // ===================================================== // AJAX Interception - capture testing_data from responses // ===================================================== interceptChatResponses: function() { var self = this; // Store reference globally so interception can find us window.mxchatTestPanelInstance = self; // Intercept jQuery AJAX calls if (window.jQuery) { var originalAjax = jQuery.ajax; jQuery.ajax = function(options) { var originalSuccess = options.success; options.success = function(data, textStatus, jqXHR) { // Check if this is a chat request if (options.data && (options.data.action === 'mxchat_handle_chat_request' || options.data.action === 'mxchat_stream_chat')) { if (self && data && data.testing_data) { self.handleTestingData(data.testing_data); } else if (self && data && data.data && data.data.testing_data) { self.handleTestingData(data.data.testing_data); } } if (originalSuccess) { originalSuccess.call(this, data, textStatus, jqXHR); } }; return originalAjax.call(this, options); }; } // Intercept fetch API calls (for both JSON and SSE streaming) var originalFetch = window.fetch; window.fetch = function() { var args = arguments; return originalFetch.apply(this, args).then(function(response) { if (args[0] && typeof args[0] === 'string' && (args[0].includes('admin-ajax.php') || args[0].includes('mxchat'))) { var contentType = response.headers.get('content-type') || ''; if (contentType.includes('application/json')) { // Non-streaming: parse the full JSON response response.clone().json().then(function(data) { if (self && data && data.testing_data) { self.handleTestingData(data.testing_data); } else if (self && data && data.data && data.data.testing_data) { self.handleTestingData(data.data.testing_data); } }).catch(function() {}); } else if (contentType.includes('text/event-stream') || contentType.includes('text/plain')) { // Streaming (SSE): read just enough to find the testing_data event // Using clone() so the original stream is unaffected for chat-script.js try { var clonedResponse = response.clone(); var reader = clonedResponse.body.getReader(); var decoder = new TextDecoder(); var sseBuffer = ''; var found = false; function readChunk() { reader.read().then(function(result) { if (result.done || found) { reader.cancel().catch(function() {}); return; } sseBuffer += decoder.decode(result.value, { stream: true }); var sseLines = sseBuffer.split('\n'); for (var i = 0; i < sseLines.length; i++) { var sseLine = sseLines[i]; if (sseLine.indexOf('data: ') === 0) { var payload = sseLine.substring(6); try { var parsed = JSON.parse(payload); if (parsed && parsed.testing_data) { self.handleTestingData(parsed.testing_data); found = true; reader.cancel().catch(function() {}); return; } } catch (e) {} } } // Keep reading until we find testing_data or hit a limit if (sseBuffer.length < 50000) { readChunk(); } else { reader.cancel().catch(function() {}); } }).catch(function() { // Stream read error — safe to ignore }); } readChunk(); } catch (e) {} } } return response; }); }; this.log('Chat interception active'); }, // ===================================================== // Handle incoming testing data // ===================================================== handleTestingData: function(testingData) { this.log('Chat data captured'); // Update query this.updateLastQuery(testingData.query || 'No query'); // Update approved URLs this.updateApprovedUrls(testingData.approved_urls || []); // Update document matches this.updateTopMatches( testingData.top_matches || [], testingData.similarity_threshold || 0.75, testingData.sources_used || 0, testingData.total_chunks_used || 0 ); // Update action matches this.updateActionMatches(testingData.action_matches || []); // Log summary if (testingData.knowledge_base_type) { this.log('Knowledge Base: ' + testingData.knowledge_base_type); } if (testingData.similarity_threshold) { this.log('Similarity Threshold: ' + (testingData.similarity_threshold * 100) + '%'); } if (testingData.top_matches && testingData.top_matches.length > 0) { var aboveThreshold = testingData.top_matches.filter(function(m) { return m.above_threshold; }).length; this.log(aboveThreshold + ' above threshold, ' + (testingData.top_matches.length - aboveThreshold) + ' below'); this.log('Highest similarity: ' + testingData.top_matches[0].similarity_percentage + '%'); } else { this.log('No document matches found'); } if (testingData.action_matches && testingData.action_matches.length > 0) { var triggered = testingData.action_matches.find(function(a) { return a.triggered; }); if (triggered) { this.log('Action Triggered: ' + triggered.intent_label + ' (' + triggered.similarity_percentage + '%)'); } else { this.log('No actions triggered - Highest: ' + testingData.action_matches[0].intent_label + ' (' + testingData.action_matches[0].similarity_percentage + '%)'); } } }, // ===================================================== // Update UI sections // ===================================================== updateLastQuery: function(query) { $('#mxch-testing-last-query').text(query); this.lastQueryData = { query: query, timestamp: new Date() }; }, updateApprovedUrls: function(approvedUrls) { var el = $('#mxch-testing-approved-urls'); if (!approvedUrls || !Array.isArray(approvedUrls) || approvedUrls.length === 0) { el.html('
' + data.data.threshold_percentage + '');
} else {
$('#mxch-testing-threshold').html('Error loading threshold');
}
}).fail(function() {
$('#mxch-testing-threshold').html('Connection error');
});
},
updateSystemPrompt: function() {
var self = this;
var el = $('#mxch-testing-system-prompt');
el.text('Loading system prompt...');
$.post(mxchatAdminTestData.ajaxUrl, {
action: 'mxchat_get_system_info',
nonce: mxchatAdminTestData.nonce
}).done(function(data) {
if (data.success) {
el.text(data.data.system_prompt || 'No system prompt configured');
if (data.data.is_openrouter) {
self.log('Model: OpenRouter - ' + data.data.openrouter_model);
} else {
self.log('Model: ' + data.data.selected_model);
}
var apiStatus = data.data.api_status;
var configuredApis = Object.keys(apiStatus).filter(function(key) { return apiStatus[key]; });
if (configuredApis.length > 0) {
self.log('Configured APIs: ' + configuredApis.join(', '));
}
} else {
el.text('Error loading system prompt');
}
}).fail(function() {
el.text('Connection error');
});
},
updateKnowledgeBaseStatus: function() {
var el = $('#mxch-testing-kb-status');
el.html('Checking...');
$.post(mxchatAdminTestData.ajaxUrl, {
action: 'mxchat_get_kb_status',
nonce: mxchatAdminTestData.nonce
}).done(function(data) {
if (data.success) {
var kbData = data.data;
el.html('✓ ' + kbData.status + ' (' + kbData.type + ' - ' + kbData.documents + ')');
} else {
el.html('Error loading KB status');
}
}).fail(function() {
el.html('Connection error');
});
},
// =====================================================
// Clear session
// =====================================================
clearChatSession: function() {
var self = this;
// Determine the bot ID for the testing chatbot
var botId = 'testing';
// Get current session ID
var cookieName = 'mxchat_session_id_' + botId;
var sessionId = this.getCookie(cookieName) || this.getCurrentSessionId(botId);
if (!sessionId) {
this.log('No active session found');
return;
}
this.log('Clearing session: ' + sessionId);
// Use MxChatInstances to properly reset
if (typeof MxChatInstances !== 'undefined' && typeof MxChatInstances.resetChatSession === 'function') {
MxChatInstances.resetChatSession(botId);
}
// Get new session ID
var newSessionId = '';
if (typeof MxChatInstances !== 'undefined' && typeof MxChatInstances.getChatSession === 'function') {
newSessionId = MxChatInstances.getChatSession(botId);
}
if (!newSessionId) {
newSessionId = 'mxchat_chat_' + Math.random().toString(36).substr(2, 9);
document.cookie = cookieName + '=' + newSessionId + '; path=/; max-age=86400; SameSite=Lax';
}
// Call backend to clear old session
$.post(mxchatAdminTestData.ajaxUrl, {
action: 'mxchat_start_fresh_session',
nonce: mxchatAdminTestData.nonce,
old_session_id: sessionId,
new_session_id: newSessionId
}).done(function(data) {
if (data.success) {
self.log('Session cleared successfully');
// Clear the chat UI for the testing bot
var chatBox = document.getElementById('chat-box-testing');
if (chatBox) {
var messages = chatBox.querySelectorAll('.bot-message, .user-message');
messages.forEach(function(msg, index) {
// Keep first welcome message
if (index === 0 && msg.classList.contains('bot-message')) return;
msg.remove();
});
}
// Clear input
var chatInput = document.getElementById('chat-input-testing');
if (chatInput) {
chatInput.value = '';
}
// Show popular questions again
var pq = document.getElementById('mxchat-popular-questions-testing');
if (pq) {
pq.style.display = 'block';
}
// Clear debug displays
self.updateLastQuery('New session started');
self.updateTopMatches([], 0, 0, 0);
self.updateApprovedUrls([]);
self.updateActionMatches([]);
self.log('Fresh session started: ' + newSessionId);
} else {
self.log('Error: ' + (data.data && data.data.message ? data.data.message : 'Unknown error'));
}
}).fail(function() {
self.log('Connection error when clearing session');
});
},
// =====================================================
// Debug console
// =====================================================
log: function(message) {
var consoleEl = document.getElementById('mxch-testing-debug-console');
if (!consoleEl) return;
var timestamp = new Date().toLocaleTimeString();
var entry = document.createElement('div');
entry.className = 'mxch-testing-debug-entry';
entry.innerHTML = ' ' + message;
consoleEl.appendChild(entry);
consoleEl.scrollTop = consoleEl.scrollHeight;
// Keep only last 50 entries
var entries = consoleEl.querySelectorAll('.mxch-testing-debug-entry');
if (entries.length > 50) {
entries[0].remove();
}
},
clearDebugConsole: function() {
var consoleEl = document.getElementById('mxch-testing-debug-console');
if (consoleEl) {
consoleEl.innerHTML = '