mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-25 00:16:29 +02:00
added smooth streaming
This commit is contained in:
parent
a4febb09c0
commit
4a331c7e5c
2 changed files with 55 additions and 1 deletions
|
|
@ -33,6 +33,7 @@ import {
|
|||
} from '@/components/ai-elements/prompt-input';
|
||||
|
||||
import { Shimmer } from '@/components/ai-elements/shimmer';
|
||||
import { useSmoothedText } from './hooks/useSmoothedText';
|
||||
import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from '@/components/ai-elements/tool';
|
||||
import { WebSearchResult } from '@/components/ai-elements/web-search-result';
|
||||
import { AppActionCard } from '@/components/ai-elements/app-action-card';
|
||||
|
|
@ -93,6 +94,11 @@ interface TreeNode extends DirEntry {
|
|||
|
||||
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 wikiLinkRegex = /\[\[([^[\]]+)\]\]/g
|
||||
const graphPalette = [
|
||||
|
|
@ -4237,7 +4243,7 @@ function App() {
|
|||
{tabState.currentAssistantMessage && (
|
||||
<Message from="assistant">
|
||||
<MessageContent>
|
||||
<MessageResponse components={streamdownComponents}>{tabState.currentAssistantMessage.replace(/<\/?voice>/g, '')}</MessageResponse>
|
||||
<SmoothStreamingMessage text={tabState.currentAssistantMessage.replace(/<\/?voice>/g, '')} components={streamdownComponents} />
|
||||
</MessageContent>
|
||||
</Message>
|
||||
)}
|
||||
|
|
|
|||
48
apps/x/apps/renderer/src/hooks/useSmoothedText.ts
Normal file
48
apps/x/apps/renderer/src/hooks/useSmoothedText.ts
Normal 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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue