@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
91 lines (89 loc) • 3.28 kB
JavaScript
import { useCallback, useEffect, useRef, useState } from "react";
//#region src/Markdown/SyntaxMarkdown/useStreamQueue.ts
const BASE_DELAY = 18;
const ACCELERATION_FACTOR = .3;
const MAX_BLOCK_DURATION = 3e3;
const FADE_DURATION = 280;
function countChars(text) {
return [...text].length;
}
function computeCharDelay(queueLength, charCount) {
let delay = BASE_DELAY / (1 + queueLength * ACCELERATION_FACTOR);
delay = Math.min(delay, MAX_BLOCK_DURATION / Math.max(charCount, 1));
return delay;
}
function useStreamQueue(blocks) {
const [revealedCount, setRevealedCount] = useState(0);
const timerRef = useRef(null);
const prevBlocksLenRef = useRef(0);
const minRevealedRef = useRef(0);
if (blocks.length === 0 && prevBlocksLenRef.current !== 0) minRevealedRef.current = 0;
if (blocks.length > prevBlocksLenRef.current && prevBlocksLenRef.current > 0) {
const prevTail = prevBlocksLenRef.current - 1;
minRevealedRef.current = Math.max(minRevealedRef.current, prevTail + 1);
}
prevBlocksLenRef.current = blocks.length;
useEffect(() => {
if (blocks.length === 0) {
setRevealedCount(0);
minRevealedRef.current = 0;
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
}
}, [blocks.length]);
const effectiveRevealedCount = Math.max(revealedCount, minRevealedRef.current);
const tailIndex = blocks.length - 1;
const getBlockState = useCallback((index) => {
if (index < effectiveRevealedCount) return "revealed";
if (index === effectiveRevealedCount && index < tailIndex) return "animating";
if (index === effectiveRevealedCount && index === tailIndex) return "streaming";
return "queued";
}, [effectiveRevealedCount, tailIndex]);
const queueLength = Math.max(0, tailIndex - effectiveRevealedCount - 1);
const animatingIndex = effectiveRevealedCount < tailIndex ? effectiveRevealedCount : -1;
const animatingCharCount = animatingIndex >= 0 ? countChars(blocks[animatingIndex]?.content ?? "") : 0;
const activeIndex = animatingIndex >= 0 ? animatingIndex : animatingIndex < 0 && tailIndex >= effectiveRevealedCount ? tailIndex : -1;
const activeCharCount = activeIndex >= 0 ? countChars(blocks[activeIndex]?.content ?? "") : 0;
const frozenRef = useRef({
delay: BASE_DELAY,
index: -1
});
if (activeIndex >= 0 && activeIndex !== frozenRef.current.index) frozenRef.current = {
delay: computeCharDelay(queueLength, activeCharCount),
index: activeIndex
};
const charDelay = activeIndex >= 0 ? frozenRef.current.delay : BASE_DELAY;
const onAnimationDone = useCallback(() => {
setRevealedCount(effectiveRevealedCount + 1);
}, [effectiveRevealedCount]);
useEffect(() => {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
if (animatingIndex < 0) return;
const totalTime = Math.max(0, (animatingCharCount - 1) * charDelay) + FADE_DURATION;
timerRef.current = setTimeout(onAnimationDone, totalTime);
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
};
}, [
animatingIndex,
animatingCharCount,
charDelay,
onAnimationDone
]);
return {
charDelay,
getBlockState,
queueLength
};
}
//#endregion
export { useStreamQueue };
//# sourceMappingURL=useStreamQueue.mjs.map