jQuery(document).ready(function($) {
// Manual test button click
$('#mxchat-test-streaming-btn').on('click', function() {
const $button = $(this);
const $result = $('#mxchat-test-streaming-result');
$button.prop('disabled', true).text('Testing...');
$result.text('Testing streaming environment...');
// Test both backend capability AND frontend environment
testCompleteStreamingEnvironment()
.then(result => {
if (result.success) {
$result.css('color', 'green').html(result.message);
} else {
$result.css('color', 'red').html(result.message);
}
})
.catch(error => {
$result.css('color', 'red').html('❌ Test failed: ' + error.message);
})
.finally(() => {
$button.prop('disabled', false).html('Test Streaming Compatibility');
});
});
// Intercept the streaming toggle BEFORE autosave fires
// We use a capturing event handler to run first
const streamingToggle = document.getElementById('enable_streaming_toggle');
if (streamingToggle) {
streamingToggle.addEventListener('change', function(e) {
if (this.checked) {
// Prevent the default autosave from firing immediately
e.stopImmediatePropagation();
const $toggle = $(this);
const $result = $('#mxchat-test-streaming-result');
const $button = $('#mxchat-test-streaming-btn');
// User is enabling streaming - run automatic compatibility test first
$button.prop('disabled', true);
$toggle.prop('disabled', true);
$result.css('color', '#666').html('🔄 Testing streaming compatibility before enabling...');
testCompleteStreamingEnvironment()
.then(result => {
if (result.success) {
// Test passed - now save the setting
saveStreamingSetting('on');
$result.css('color', 'green').html('✅ Streaming enabled - compatibility verified!');
} else {
// Streaming failed - turn it back off (don't save 'on')
$toggle.prop('checked', false);
$result.css('color', 'red').html(result.message);
}
})
.catch(error => {
// Error during test - turn it back off
$toggle.prop('checked', false);
$result.css('color', 'red').html('❌ Streaming test failed: ' + error.message + '
Streaming has been disabled.');
})
.finally(() => {
$button.prop('disabled', false);
$toggle.prop('disabled', false);
});
}
// If turning OFF, let the normal autosave handle it
}, true); // 'true' for capturing phase - runs before jQuery handlers
}
// Helper function to save the streaming setting via AJAX
function saveStreamingSetting(value) {
$.ajax({
url: mxchatTestStreamingAjax.ajax_url,
type: 'POST',
data: {
action: 'mxchat_save_setting',
name: 'enable_streaming_toggle',
value: value,
_ajax_nonce: mxchatTestStreamingAjax.settings_nonce
}
});
}
function testCompleteStreamingEnvironment() {
return new Promise((resolve, reject) => {
let results = {
backendSupported: false,
frontendWorking: false,
chunks: 0,
timing: null,
issues: []
};
let startTime = Date.now();
let firstChunkTime = null;
let lastChunkTime = null;
let timeout;
// Set overall timeout
timeout = setTimeout(() => {
resolve({
success: false,
message: getFailureMessage(results)
});
}, 15000);
// Test the actual chat streaming endpoint (not the test endpoint)
const formData = new FormData();
formData.append('action', 'mxchat_stream_chat');
formData.append('message', 'Say "test 1", then "test 2", then "test 3" - each on a separate line.');
formData.append('session_id', 'streaming_test_' + Date.now());
formData.append('nonce', mxchatTestStreamingAjax.nonce);
formData.append('force_streaming_test', '1'); // Force streaming mode for testing
fetch(mxchatTestStreamingAjax.ajax_url, {
method: 'POST',
body: formData,
credentials: 'same-origin'
})
.then(response => {
const contentType = response.headers.get('content-type');
console.log('Content-Type:', contentType);
// Check if we got JSON (streaming failed)
if (contentType && contentType.includes('application/json')) {
response.json().then(data => {
clearTimeout(timeout);
results.backendSupported = true;
results.frontendWorking = false;
results.issues.push('Server fell back to JSON response');
resolve({
success: false,
message: getFailureMessage(results)
});
});
return;
}
// Check for proper SSE content type
if (!contentType || !contentType.includes('text/event-stream')) {
clearTimeout(timeout);
results.issues.push(`Wrong content-type: ${contentType || 'none'}`);
resolve({
success: false,
message: getFailureMessage(results)
});
return;
}
results.backendSupported = true;
// Process streaming response
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
function processStream() {
reader.read().then(({ done, value }) => {
if (done) {
clearTimeout(timeout);
results.timing = {
total: Date.now() - startTime,
firstChunk: firstChunkTime ? firstChunkTime - startTime : null,
lastChunk: lastChunkTime ? lastChunkTime - startTime : null
};
if (results.chunks > 0) {
resolve({
success: true,
message: getSuccessMessage(results)
});
} else {
results.issues.push('No chunks received');
resolve({
success: false,
message: getFailureMessage(results)
});
}
return;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.substring(6);
if (data === '[DONE]') {
// Stream complete - will be handled in done section
continue;
}
try {
const json = JSON.parse(data);
if (json.content && json.content.trim().length > 0) {
results.chunks++;
results.frontendWorking = true;
if (!firstChunkTime) {
firstChunkTime = Date.now();
}
lastChunkTime = Date.now();
$result.text(`✅ Streaming working... (${results.chunks} chunks received)`);
}
} catch (e) {
// Non-JSON data is fine for some SSE implementations
}
}
}
processStream();
}).catch(error => {
clearTimeout(timeout);
results.issues.push('Stream read error: ' + error.message);
resolve({
success: false,
message: getFailureMessage(results)
});
});
}
processStream();
})
.catch(error => {
clearTimeout(timeout);
results.issues.push('Fetch error: ' + error.message);
resolve({
success: false,
message: getFailureMessage(results)
});
});
});
}
function getSuccessMessage(results) {
return `✅ Streaming is working!
📊 Received ${results.chunks} chunks in ${results.timing.total}ms
⚡ First chunk: ${results.timing.firstChunk}ms
🎯 Your users will see real-time streaming responses.`;
}
function getFailureMessage(results) {
let message = '❌ Streaming is not working in your environment. Please disable and use regular response.
';
if (results.backendSupported) {
message += 'Likely causes:
';
message += '• Caching plugin (WP Rocket, W3 Total Cache, etc.)
';
message += '• CDN buffering (Cloudflare, etc.)
';
message += '• Server-level buffering (nginx, Apache)
';
message += '• Hosting provider optimizations
';
message += 'Solutions:
';
message += '• Disable caching for chat endpoints
';
message += '• Add streaming exceptions in your CDN
';
message += '• Contact your hosting provider
';
} else {
message += '❌ Backend streaming not supported
';
if (results.issues.length > 0) {
message += '
Issues detected:
';
results.issues.forEach(issue => {
message += `• ${issue}
`;
});
}
}
return message;
}
});