@@ -156,37 +156,75 @@ export type PartComponent = Component<MessagePartProps>
156156
157157export const PART_MAPPING : Record < string , PartComponent | undefined > = { }
158158
159- const TEXT_RENDER_THROTTLE_MS = 100
159+ const TEXT_RENDER_PACE_MS = 24
160+ const TEXT_RENDER_SNAP = / [ \s . , ! ? ; : ) \] ] /
161+
162+ function step ( size : number ) {
163+ if ( size <= 12 ) return 2
164+ if ( size <= 48 ) return 4
165+ if ( size <= 96 ) return 8
166+ return Math . min ( 24 , Math . ceil ( size / 8 ) )
167+ }
168+
169+ function next ( text : string , start : number ) {
170+ const end = Math . min ( text . length , start + step ( text . length - start ) )
171+ const max = Math . min ( text . length , end + 8 )
172+ for ( let i = end ; i < max ; i ++ ) {
173+ if ( TEXT_RENDER_SNAP . test ( text [ i ] ?? "" ) ) return i + 1
174+ }
175+ return end
176+ }
160177
161- function createThrottledValue ( getValue : ( ) => string ) {
178+ function createPacedValue ( getValue : ( ) => string , live ?: ( ) => boolean ) {
162179 const [ value , setValue ] = createSignal ( getValue ( ) )
180+ let shown = getValue ( )
163181 let timeout : ReturnType < typeof setTimeout > | undefined
164- let last = 0
182+
183+ const clear = ( ) => {
184+ if ( ! timeout ) return
185+ clearTimeout ( timeout )
186+ timeout = undefined
187+ }
188+
189+ const sync = ( text : string ) => {
190+ shown = text
191+ setValue ( text )
192+ }
193+
194+ const run = ( ) => {
195+ timeout = undefined
196+ const text = getValue ( )
197+ if ( ! live ?.( ) ) {
198+ sync ( text )
199+ return
200+ }
201+ if ( ! text . startsWith ( shown ) || text . length <= shown . length ) {
202+ sync ( text )
203+ return
204+ }
205+ const end = next ( text , shown . length )
206+ sync ( text . slice ( 0 , end ) )
207+ if ( end < text . length ) timeout = setTimeout ( run , TEXT_RENDER_PACE_MS )
208+ }
165209
166210 createEffect ( ( ) => {
167- const next = getValue ( )
168- const now = Date . now ( )
169-
170- const remaining = TEXT_RENDER_THROTTLE_MS - ( now - last )
171- if ( remaining <= 0 ) {
172- if ( timeout ) {
173- clearTimeout ( timeout )
174- timeout = undefined
175- }
176- last = now
177- setValue ( next )
211+ const text = getValue ( )
212+ if ( ! live ?.( ) ) {
213+ clear ( )
214+ sync ( text )
215+ return
216+ }
217+ if ( ! text . startsWith ( shown ) || text . length < shown . length ) {
218+ clear ( )
219+ sync ( text )
178220 return
179221 }
180- if ( timeout ) clearTimeout ( timeout )
181- timeout = setTimeout ( ( ) => {
182- last = Date . now ( )
183- setValue ( next )
184- timeout = undefined
185- } , remaining )
222+ if ( text . length === shown . length || timeout ) return
223+ timeout = setTimeout ( run , TEXT_RENDER_PACE_MS )
186224 } )
187225
188226 onCleanup ( ( ) => {
189- if ( timeout ) clearTimeout ( timeout )
227+ clear ( )
190228 } )
191229
192230 return value
@@ -1332,11 +1370,11 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
13321370 return items . filter ( ( x ) => ! ! x ) . join ( " \u00B7 " )
13331371 } )
13341372
1335- const displayText = ( ) => ( part ( ) . text ?? "" ) . trim ( )
1336- const throttledText = createThrottledValue ( displayText )
13371373 const streaming = createMemo (
13381374 ( ) => props . message . role === "assistant" && typeof ( props . message as AssistantMessage ) . time . completed !== "number" ,
13391375 )
1376+ const displayText = ( ) => ( part ( ) . text ?? "" ) . trim ( )
1377+ const throttledText = createPacedValue ( displayText , streaming )
13401378 const isLastTextPart = createMemo ( ( ) => {
13411379 const last = ( data . store . part ?. [ props . message . id ] ?? [ ] )
13421380 . filter ( ( item ) : item is TextPart => item ?. type === "text" && ! ! item . text ?. trim ( ) )
@@ -1395,11 +1433,11 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
13951433
13961434PART_MAPPING [ "reasoning" ] = function ReasoningPartDisplay ( props ) {
13971435 const part = ( ) => props . part as ReasoningPart
1398- const text = ( ) => part ( ) . text . trim ( )
1399- const throttledText = createThrottledValue ( text )
14001436 const streaming = createMemo (
14011437 ( ) => props . message . role === "assistant" && typeof ( props . message as AssistantMessage ) . time . completed !== "number" ,
14021438 )
1439+ const text = ( ) => part ( ) . text . trim ( )
1440+ const throttledText = createPacedValue ( text , streaming )
14031441
14041442 return (
14051443 < Show when = { throttledText ( ) } >
0 commit comments