mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-06 05:09:09 +08:00
refactor(useSmoothStream): optimize chunk handling and state management (#8514)
* refactor(useSmoothStream): optimize chunk handling and state management - Replaced state management with refs for chunk queue to improve performance and reduce unnecessary re-renders. - Introduced Intl.Segmenter for better character segmentation based on language support. - Updated rendering logic to ensure final text is displayed correctly when the stream ends. - Cleaned up unused effects and comments for improved code clarity. * refactor(useSmoothStream): move segmenter initialization outside of addChunk function - Moved the initialization of Intl.Segmenter to the top of the file for better performance and to avoid redundant instantiation within the addChunk callback. - This change enhances the efficiency of chunk processing in the useSmoothStream hook.
This commit is contained in:
parent
6cc29c5005
commit
84157f7bd8
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef } from 'react'
|
||||||
|
|
||||||
interface UseSmoothStreamOptions {
|
interface UseSmoothStreamOptions {
|
||||||
onUpdate: (text: string) => void
|
onUpdate: (text: string) => void
|
||||||
@ -7,15 +7,18 @@ interface UseSmoothStreamOptions {
|
|||||||
initialText?: string
|
initialText?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const languages = ['en-US', 'es-ES', 'zh-CN', 'zh-TW', 'ja-JP', 'ru-RU', 'el-GR', 'fr-FR', 'pt-PT']
|
||||||
|
const segmenter = new Intl.Segmenter(languages)
|
||||||
|
|
||||||
export const useSmoothStream = ({ onUpdate, streamDone, minDelay = 10, initialText = '' }: UseSmoothStreamOptions) => {
|
export const useSmoothStream = ({ onUpdate, streamDone, minDelay = 10, initialText = '' }: UseSmoothStreamOptions) => {
|
||||||
const [chunkQueue, setChunkQueue] = useState<string[]>([])
|
const chunkQueueRef = useRef<string[]>([])
|
||||||
const animationFrameRef = useRef<number | null>(null)
|
const animationFrameRef = useRef<number | null>(null)
|
||||||
const displayedTextRef = useRef<string>(initialText)
|
const displayedTextRef = useRef<string>(initialText)
|
||||||
const lastUpdateTimeRef = useRef<number>(0)
|
const lastUpdateTimeRef = useRef<number>(0)
|
||||||
|
|
||||||
const addChunk = useCallback((chunk: string) => {
|
const addChunk = useCallback((chunk: string) => {
|
||||||
const chars = Array.from(chunk)
|
const chars = Array.from(segmenter.segment(chunk)).map((s) => s.segment)
|
||||||
setChunkQueue((prev) => [...prev, ...(chars || [])])
|
chunkQueueRef.current = [...chunkQueueRef.current, ...(chars || [])]
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const reset = useCallback(
|
const reset = useCallback(
|
||||||
@ -23,7 +26,7 @@ export const useSmoothStream = ({ onUpdate, streamDone, minDelay = 10, initialTe
|
|||||||
if (animationFrameRef.current) {
|
if (animationFrameRef.current) {
|
||||||
cancelAnimationFrame(animationFrameRef.current)
|
cancelAnimationFrame(animationFrameRef.current)
|
||||||
}
|
}
|
||||||
setChunkQueue([])
|
chunkQueueRef.current = []
|
||||||
displayedTextRef.current = newText
|
displayedTextRef.current = newText
|
||||||
onUpdate(newText)
|
onUpdate(newText)
|
||||||
},
|
},
|
||||||
@ -32,12 +35,16 @@ export const useSmoothStream = ({ onUpdate, streamDone, minDelay = 10, initialTe
|
|||||||
|
|
||||||
const renderLoop = useCallback(
|
const renderLoop = useCallback(
|
||||||
(currentTime: number) => {
|
(currentTime: number) => {
|
||||||
// 1. 如果队列为空,等待下一帧
|
// 1. 如果队列为空
|
||||||
if (chunkQueue.length === 0) {
|
if (chunkQueueRef.current.length === 0) {
|
||||||
// 如果流还没结束但队列空了,就等待下一帧
|
// 如果流已结束,确保显示最终状态并停止循环
|
||||||
if (!streamDone) {
|
if (streamDone) {
|
||||||
animationFrameRef.current = requestAnimationFrame(renderLoop)
|
const finalText = displayedTextRef.current
|
||||||
|
onUpdate(finalText)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
// 如果流还没结束但队列空了,等待下一帧
|
||||||
|
animationFrameRef.current = requestAnimationFrame(renderLoop)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,25 +55,29 @@ export const useSmoothStream = ({ onUpdate, streamDone, minDelay = 10, initialTe
|
|||||||
}
|
}
|
||||||
lastUpdateTimeRef.current = currentTime
|
lastUpdateTimeRef.current = currentTime
|
||||||
|
|
||||||
setChunkQueue((prevQueue) => {
|
// 3. 动态计算本次渲染的字符数
|
||||||
// 3. 动态计算本次渲染的字符数
|
let charsToRenderCount = Math.max(1, Math.floor(chunkQueueRef.current.length / 5))
|
||||||
// 如果队列积压严重,就一次性渲染更多字符来"追赶"
|
|
||||||
const charsToRenderCount = Math.max(1, Math.floor(prevQueue.length / 5)) // 每次至少渲染1个,最多渲染队列的1/5
|
|
||||||
|
|
||||||
const charsToRender = prevQueue.slice(0, charsToRenderCount)
|
// 如果流已结束,一次性渲染所有剩余字符
|
||||||
displayedTextRef.current += charsToRender.join('')
|
if (streamDone) {
|
||||||
|
charsToRenderCount = chunkQueueRef.current.length
|
||||||
|
}
|
||||||
|
|
||||||
// 4. 立即更新UI
|
const charsToRender = chunkQueueRef.current.slice(0, charsToRenderCount)
|
||||||
onUpdate(displayedTextRef.current)
|
displayedTextRef.current += charsToRender.join('')
|
||||||
|
|
||||||
// 返回新的队列
|
// 4. 立即更新UI
|
||||||
return prevQueue.slice(charsToRenderCount)
|
onUpdate(displayedTextRef.current)
|
||||||
})
|
|
||||||
|
|
||||||
// 5. 请求下一帧动画
|
// 5. 更新队列
|
||||||
animationFrameRef.current = requestAnimationFrame(renderLoop)
|
chunkQueueRef.current = chunkQueueRef.current.slice(charsToRenderCount)
|
||||||
|
|
||||||
|
// 6. 如果还有内容需要渲染,继续下一帧
|
||||||
|
if (chunkQueueRef.current.length > 0) {
|
||||||
|
animationFrameRef.current = requestAnimationFrame(renderLoop)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[chunkQueue, streamDone, onUpdate, minDelay]
|
[streamDone, onUpdate, minDelay]
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -81,22 +92,5 @@ export const useSmoothStream = ({ onUpdate, streamDone, minDelay = 10, initialTe
|
|||||||
}
|
}
|
||||||
}, [renderLoop])
|
}, [renderLoop])
|
||||||
|
|
||||||
// 当外部流结束,且队列即将变空时,进行最后一次"瞬移"渲染
|
|
||||||
useEffect(() => {
|
|
||||||
if (streamDone && chunkQueue.length > 0) {
|
|
||||||
const remainingText = chunkQueue.join('')
|
|
||||||
const finalText = displayedTextRef.current + remainingText
|
|
||||||
|
|
||||||
// 取消正在进行的动画循环
|
|
||||||
if (animationFrameRef.current) {
|
|
||||||
cancelAnimationFrame(animationFrameRef.current)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接更新到最终状态
|
|
||||||
onUpdate(finalText)
|
|
||||||
setChunkQueue([]) // 清空队列
|
|
||||||
}
|
|
||||||
}, [streamDone, chunkQueue, onUpdate])
|
|
||||||
|
|
||||||
return { addChunk, reset }
|
return { addChunk, reset }
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user