import { useEffect, useRef, memo } from "react"; import { renderMarkdown } from "../lib/markdown"; import "./StreamingBubble.css"; interface Props { content: string; reasoning: string; thinkingOpen: boolean; raw?: string; } const StreamingBubble = memo(function StreamingBubble({ content, reasoning, thinkingOpen, raw = "", }: Props) { const reasoningRef = useRef(null); const bottomRef = useRef(null); const hasReasoning = reasoning.trim().length > 0 || thinkingOpen; const hasContent = content.trim().length > 0; const fallbackVisible = !hasContent && !hasReasoning && raw.trim().length > 0 ? raw : ""; useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest" }); }, [content, reasoning, raw]); return (
{(hasReasoning || (!hasContent && !fallbackVisible)) && (
{reasoning.trim().length > 0 ? ( ) : ( Generating reasoning… )}
)} {!hasReasoning && !hasContent && !fallbackVisible && (
)} {hasContent && (
)} {fallbackVisible && !hasContent && (
)}
); }); export default StreamingBubble; interface WordStreamProps { text: string; } const WordStream = memo(function WordStream({ text }: WordStreamProps) { const tokens = text.split(/(\s+)/); return ( {tokens.map((token, i) => { if (/^\s+$/.test(token)) return {token}; return ( {token} ); })} ); });