From ac2f9d6fcde9e2e4ca8a2cb7e53ffc6f73287088 Mon Sep 17 00:00:00 2001 From: Zacharias-Brohn Date: Wed, 14 Jan 2026 20:58:57 +0100 Subject: [PATCH] changes --- components/Chat/StreamingText.tsx | 74 +++++++++++++++---------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/components/Chat/StreamingText.tsx b/components/Chat/StreamingText.tsx index 2efa4a5..2dee56b 100644 --- a/components/Chat/StreamingText.tsx +++ b/components/Chat/StreamingText.tsx @@ -8,56 +8,56 @@ interface StreamingTextProps { isStreaming: boolean; } +interface TextChunk { + id: number; + text: string; +} + /** * Component that renders text with a smooth fade-in animation for new chunks. - * Splits content into "committed" (already visible) and "new" (fading in) portions. + * Each chunk gets its own span element with a fade-in animation. */ export function StreamingText({ content, isStreaming }: StreamingTextProps) { - const [committedLength, setCommittedLength] = useState(0); - const animationTimeoutRef = useRef(null); + const [chunks, setChunks] = useState([]); + const prevLengthRef = useRef(0); + const chunkIdRef = useRef(0); - // When content grows, schedule the new content to become "committed" after animation useEffect(() => { - if (content.length > committedLength) { - // Clear any pending timeout - if (animationTimeoutRef.current) { - clearTimeout(animationTimeoutRef.current); - } + const prevLength = prevLengthRef.current; + const currentLength = content.length; - // After the fade-in animation completes, commit the new content - animationTimeoutRef.current = setTimeout(() => { - setCommittedLength(content.length); - }, 300); // Match CSS animation duration - } - - return () => { - if (animationTimeoutRef.current) { - clearTimeout(animationTimeoutRef.current); - } - }; - }, [content, committedLength]); - - // When streaming stops, immediately commit all content - useEffect(() => { - if (!isStreaming) { - setCommittedLength(content.length); - } - }, [isStreaming, content.length]); - - // Reset when content is cleared (new message) - useEffect(() => { - if (content.length === 0) { - setCommittedLength(0); + if (currentLength > prevLength) { + // New content arrived - create a new chunk for it + const newText = content.slice(prevLength); + const newChunk: TextChunk = { + id: chunkIdRef.current++, + text: newText, + }; + setChunks((prev) => [...prev, newChunk]); + prevLengthRef.current = currentLength; + } else if (currentLength < prevLength) { + // Content was reset (new message) + setChunks([]); + prevLengthRef.current = 0; + chunkIdRef.current = 0; } }, [content]); - const committedText = content.slice(0, committedLength); - const newText = content.slice(committedLength); + // When streaming stops, consolidate all chunks into one (removes animations) + useEffect(() => { + if (!isStreaming && content.length > 0) { + setChunks([{ id: 0, text: content }]); + prevLengthRef.current = content.length; + } + }, [isStreaming, content]); return ( - {committedText} - {newText && {newText}} + {chunks.map((chunk) => ( + + {chunk.text} + + ))} ); }