Skip to content

Instantly share code, notes, and snippets.

@KevinBatdorf
Last active February 1, 2024 07:11
Show Gist options
  • Save KevinBatdorf/717afd8f52e9242210f34122d8dc98a8 to your computer and use it in GitHub Desktop.
Save KevinBatdorf/717afd8f52e9242210f34122d8dc98a8 to your computer and use it in GitHub Desktop.
useStreaming hook and Server Sent Svents (SSE) basic parser
const parseEvent = (eventData) => {
const lines = eventData.split('\n');
const eventType = (
lines.find((line) => line.startsWith('event:')) || 'event:message'
)
.slice(6)
.trim();
const eventDataExtracted = lines
.filter((line) => line.startsWith('data:'))
.map((line) => line.slice(5).trim())
.join('');
return { type: eventType, data: eventDataExtracted };
};
export const createParser = (onParse) => {
let eventString = '';
return {
async feed(chunk) {
eventString += chunk;
while (eventString.includes('\n\n')) {
const eventEnd = eventString.indexOf('\n\n');
await new Promise((resolve) => setTimeout(resolve, 50));
onParse(parseEvent(eventString.slice(0, eventEnd)));
eventString = eventString.slice(eventEnd + 2);
}
},
};
};
// Simple example. not well tested or complete
export const useStreaming = (response) => {
const [stream, setStream] = useState('');
const onParse = (event) => {
if (event.type !== 'message') return;
const data = event.data;
if (data === '[DONE]') return;
try {
const json = JSON.parse(data);
const text = json.choices[0]?.delta?.content || '';
if (!text) return;
setStream((prev) => prev + text);
} catch (e) {
console.error(e);
}
};
useEffect(() => {
if (!response) return;
setStream('');
const reader = response.body.getReader();
const decoder = new TextDecoder();
const parser = createParser(onParse);
(async () => {
while (true) {
const { done, value } = await reader.read();
if (done) break;
await parser.feed(decoder.decode(value));
}
})();
}, [response]);
return { stream, error: undefined };
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment