30 lines
1.3 KiB
JavaScript
30 lines
1.3 KiB
JavaScript
/** Accumulate deltas and render at most once per animation frame. */
|
|
export function createStreamRenderer({onReply, onThinking, schedule = callback => requestAnimationFrame(callback), cancel = id => cancelAnimationFrame(id)}) {
|
|
const text = {reply: "", thinking: ""};
|
|
const dirty = new Set();
|
|
let frame = null;
|
|
const flush = () => {
|
|
if (frame !== null) cancel(frame);
|
|
frame = null;
|
|
if (dirty.has("thinking")) onThinking?.(text.thinking);
|
|
if (dirty.has("reply")) onReply?.(text.reply);
|
|
dirty.clear();
|
|
};
|
|
const update = (target, value, replace = false) => {
|
|
text[target] = replace ? value : text[target] + value;
|
|
dirty.add(target);
|
|
if (frame === null) frame = schedule(flush);
|
|
};
|
|
return {
|
|
onChunk: ({chunk, fullReply}) => update("reply", typeof fullReply === "string" ? fullReply : chunk, typeof fullReply === "string"),
|
|
onThinking: ({chunk, fullThinking}) => update("thinking", typeof fullThinking === "string" ? fullThinking : chunk, typeof fullThinking === "string"),
|
|
onReplace: ({target, text: value}) => update(target, value, true),
|
|
flush,
|
|
dispose() {
|
|
if (frame !== null) cancel(frame);
|
|
frame = null;
|
|
dirty.clear();
|
|
}
|
|
};
|
|
}
|