@@ -189,59 +189,65 @@ export const ChatThread = ({
189189 hasSubmittedInputMessage . current = true ;
190190 } , [ inputMessage , sendMessage ] ) ;
191191
192- // @todo : this need to be optimized to avoid excessive re-renders
193192 // Track scroll position changes.
194- // useEffect(() => {
195- // const scrollElement = scrollAreaRef.current?.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement;
196- // if (!scrollElement) return;
197-
198- // let timeout: NodeJS.Timeout | null = null;
199-
200- // const handleScroll = () => {
201- // const scrollOffset = scrollElement.scrollTop;
202-
203- // const threshold = 50; // pixels from bottom to consider "at bottom"
204- // const { scrollHeight, clientHeight } = scrollElement;
205- // const isAtBottom = scrollHeight - scrollOffset - clientHeight <= threshold;
206- // setIsAutoScrollEnabled(isAtBottom);
207-
208- // // Debounce the history state update
209- // if (timeout) {
210- // clearTimeout(timeout);
211- // }
212-
213- // timeout = setTimeout(() => {
214- // history.replaceState(
215- // {
216- // scrollOffset,
217- // } satisfies ChatHistoryState,
218- // '',
219- // window.location.href
220- // );
221- // }, 300);
222- // };
223-
224- // scrollElement.addEventListener('scroll', handleScroll, { passive: true });
225-
226- // return () => {
227- // scrollElement.removeEventListener('scroll', handleScroll);
228- // if (timeout) {
229- // clearTimeout(timeout);
230- // }
231- // };
232- // }, []);
193+ useEffect ( ( ) => {
194+ const scrollElement = scrollAreaRef . current ?. querySelector ( '[data-radix-scroll-area-viewport]' ) as HTMLElement ;
195+ if ( ! scrollElement ) return ;
196+
197+ let timeout : NodeJS . Timeout | null = null ;
198+
199+ const handleScroll = ( ) => {
200+ const scrollOffset = scrollElement . scrollTop ;
201+
202+ const threshold = 50 ; // pixels from bottom to consider "at bottom"
203+ const { scrollHeight, clientHeight } = scrollElement ;
204+ const isAtBottom = scrollHeight - scrollOffset - clientHeight <= threshold ;
205+ setIsAutoScrollEnabled ( isAtBottom ) ;
206+
207+ // Debounce the history state update
208+ if ( timeout ) {
209+ clearTimeout ( timeout ) ;
210+ }
211+
212+ timeout = setTimeout ( ( ) => {
213+ console . log ( `scrollOffset: ${ scrollOffset } ` ) ;
214+ history . replaceState (
215+ {
216+ scrollOffset,
217+ } satisfies ChatHistoryState ,
218+ '' ,
219+ window . location . href
220+ ) ;
221+ } , 500 ) ;
222+ } ;
223+
224+ scrollElement . addEventListener ( 'scroll' , handleScroll , { passive : true } ) ;
225+
226+ return ( ) => {
227+ scrollElement . removeEventListener ( 'scroll' , handleScroll ) ;
228+ if ( timeout ) {
229+ clearTimeout ( timeout ) ;
230+ }
231+ } ;
232+ } , [ ] ) ;
233233
234234 useEffect ( ( ) => {
235235 const scrollElement = scrollAreaRef . current ?. querySelector ( '[data-radix-scroll-area-viewport]' ) as HTMLElement ;
236236 if ( ! scrollElement ) {
237237 return ;
238238 }
239239
240- const { scrollOffset } = ( history . state ?? { } ) as ChatHistoryState ;
241- scrollElement . scrollTo ( {
242- top : scrollOffset ?? 0 ,
243- behavior : 'instant' ,
244- } ) ;
240+ // @hack : without this setTimeout, the scroll position would not be restored
241+ // at the correct position (it was slightly too high). The theory is that the
242+ // content hasn't fully rendered yet, so restoring the scroll position too
243+ // early results in weirdness. Waiting 10ms seems to fix the issue.
244+ setTimeout ( ( ) => {
245+ const { scrollOffset } = ( history . state ?? { } ) as ChatHistoryState ;
246+ scrollElement . scrollTo ( {
247+ top : scrollOffset ?? 0 ,
248+ behavior : 'instant' ,
249+ } ) ;
250+ } , 10 ) ;
245251 } , [ ] ) ;
246252
247253 // When messages are being streamed, scroll to the latest message
0 commit comments