Hello everyone,
I’m working on a project where I need to stream HTML content from a server to a client using Server-Sent Events (SSE). The server sends chunks of HTML content that should be displayed on the client side in real-time. However, I’m facing an issue where leading parts of HTML tags, such as <h
and <p
, are getting removed during the rendering process on the client side.
Here’s what I’ve done so far:
- Server-Side PHP Code:
- The server sends chunks of HTML content using SSE.
- Each chunk is processed and sent to the client.
PHP code snippet which acts as a proxy between Open API chat completion API and my client.
function process_openai_stream_data($data, &$full_response) {
if (connection_aborted()) {
return 0; // Stop the stream if the client has disconnected
}
$lines = explode("\n", $data);
foreach ($lines as $line) {
if (strpos($line, 'data: ') === 0) {
$line = trim(substr($line, 6)); // Remove 'data: ' prefix
if ($line === '[DONE]') {
// End of stream signal
echo "data: [END]\n\n";
flush();
return 0; // Stop the stream
}
$json = json_decode($line, true);
if (json_last_error() !== JSON_ERROR_NONE) {
continue;
}
if (isset($json['choices'][0]['delta']['content'])) {
$content = $json['choices'][0]['delta']['content'];
$full_response .= $content;
echo "data: " . $content . "\n\n";
flush();
}
}
}
return strlen($data);
}
- Client-Side JavaScript Code:
- The client receives the data and appends it to a div.
javascript code which processes the events:
function downloadResults(requestId) {
const formData = new FormData();
formData.append('action', 'download_feedback_streaming');
formData.append('request_id', requestId);
formData.append('nonce', beyond.nonce);
formData.append('ajax', 'frontend');
const resultDisplay = document.getElementById('result-content');
if (!resultDisplay) {
console.error('Result display element not found');
return;
}
resultDisplay.innerHTML = ''; // Clear previous content
fetch(beyond.ajaxurl, {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
function readStream() {
reader.read().then(({ done, value }) => {
if (done) {
displayAudioFeedback();
console.log('Stream complete');
return;
}
buffer += decoder.decode(value, { stream: true });
const events = buffer.split('\n\n');
buffer = events.pop(); // Keep the last incomplete event in the buffer
events.forEach(event => {
if (event.startsWith('data: ')) {
const data = event.slice(6); // Remove 'data: ' prefix
// Append to the result display
resultDisplay.insertAdjacentHTML('beforeend', data);
}
});
readStream(); // Continue reading next chunk
}).catch(error => {
console.error('Error reading stream:', error);
displayError("An error occurred while retrieving your feedback. Please try again.");
});
}
readStream(); // Start reading the stream
})
.catch(error => {
console.error('Error downloading results:', error);
displayError("An error occurred while retrieving your feedback. Please try again.");
});
}
The Issue:
When streaming, some HTML tags are split across chunks, resulting in the leading parts of the tags getting removed. For instance:
- The tag
<h3>
might be split into<h
,3
, and>
, and I see that the <h in this example is not getting added to the div. This happens for other tags such as.
What I’ve Tried:
I printed the data on the server side, during the API invocation from the client and the Javascript code. The data is coming as expected in the first two places. I noticed a new line added to the closing tag such as “>n”.
[07-Jul-2024 22:20:35 UTC] data: {"id":"chatcmpl-9iUXCKCHUnVncnVOWUOP8W9I8cqOE","object":"chat.completion.chunk","created":1720390834,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":">\n"},"logprobs":null,"finish_reason":null}]}
On the Javascript side, the opening portion of the tag that comes right after the closing tag with the new line is getting ignored.
Question:
How can I ensure that HTML tags split across multiple chunks are correctly handled and displayed on the client? Is there a better way to handle this if I want to display HTML-formatted content to the user?
Any help or suggestions would be greatly appreciated!