added smooth streaming

This commit is contained in:
Arjun 2026-03-27 17:20:14 +05:30
parent a4febb09c0
commit 4a331c7e5c
2 changed files with 55 additions and 1 deletions

View file

@ -33,6 +33,7 @@ import {
} from '@/components/ai-elements/prompt-input'; } from '@/components/ai-elements/prompt-input';
import { Shimmer } from '@/components/ai-elements/shimmer'; import { Shimmer } from '@/components/ai-elements/shimmer';
import { useSmoothedText } from './hooks/useSmoothedText';
import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from '@/components/ai-elements/tool'; import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from '@/components/ai-elements/tool';
import { WebSearchResult } from '@/components/ai-elements/web-search-result'; import { WebSearchResult } from '@/components/ai-elements/web-search-result';
import { AppActionCard } from '@/components/ai-elements/app-action-card'; import { AppActionCard } from '@/components/ai-elements/app-action-card';
@ -93,6 +94,11 @@ interface TreeNode extends DirEntry {
const streamdownComponents = { pre: MarkdownPreOverride } const streamdownComponents = { pre: MarkdownPreOverride }
function SmoothStreamingMessage({ text, components }: { text: string; components: typeof streamdownComponents }) {
const smoothText = useSmoothedText(text)
return <MessageResponse components={components}>{smoothText}</MessageResponse>
}
const DEFAULT_SIDEBAR_WIDTH = 256 const DEFAULT_SIDEBAR_WIDTH = 256
const wikiLinkRegex = /\[\[([^[\]]+)\]\]/g const wikiLinkRegex = /\[\[([^[\]]+)\]\]/g
const graphPalette = [ const graphPalette = [
@ -4237,7 +4243,7 @@ function App() {
{tabState.currentAssistantMessage && ( {tabState.currentAssistantMessage && (
<Message from="assistant"> <Message from="assistant">
<MessageContent> <MessageContent>
<MessageResponse components={streamdownComponents}>{tabState.currentAssistantMessage.replace(/<\/?voice>/g, '')}</MessageResponse> <SmoothStreamingMessage text={tabState.currentAssistantMessage.replace(/<\/?voice>/g, '')} components={streamdownComponents} />
</MessageContent> </MessageContent>
</Message> </Message>
)} )}

View file

@ -0,0 +1,48 @@
import { useEffect, useRef, useState } from 'react'
/**
* Smoothly reveals streamed text by buffering incoming chunks and releasing
* them gradually via requestAnimationFrame, producing the fluid typing effect
* seen in apps like Claude and ChatGPT.
*/
export function useSmoothedText(targetText: string): string {
const [displayText, setDisplayText] = useState('')
const targetRef = useRef('')
const displayLenRef = useRef(0)
const rafRef = useRef<number>(0)
targetRef.current = targetText
useEffect(() => {
// Target cleared → immediately clear display
if (!targetText) {
displayLenRef.current = 0
setDisplayText('')
cancelAnimationFrame(rafRef.current)
return
}
const tick = () => {
const target = targetRef.current
if (!target) return
const currentLen = displayLenRef.current
if (currentLen < target.length) {
const remaining = target.length - currentLen
// Adaptive speed: reveal faster when buffer is large, slower when small
const step = Math.max(2, Math.ceil(remaining * 0.18))
displayLenRef.current = Math.min(currentLen + step, target.length)
setDisplayText(target.slice(0, displayLenRef.current))
rafRef.current = requestAnimationFrame(tick)
}
// When caught up, stop. New useEffect call restarts when more text arrives.
}
cancelAnimationFrame(rafRef.current)
rafRef.current = requestAnimationFrame(tick)
return () => cancelAnimationFrame(rafRef.current)
}, [targetText])
return displayText
}