11"use client" ;
22
3+ import { useLayoutEffect , useRef } from "react" ;
4+
35interface Message {
46 role : string ;
57 content : string ;
@@ -17,6 +19,28 @@ interface MessageListProps {
1719}
1820
1921export default function MessageList ( { messages } : MessageListProps ) {
22+ const scrollAreaRef = useRef < HTMLDivElement > ( null ) ;
23+ // Avoid the message list to change its height all the time. It causes some
24+ // flickering in the screen because some messages, as the ones displaying
25+ // progress statuses, are changing the content(the number of lines) and size
26+ // constantily. To minimize it, we keep track of the biggest scroll height of
27+ // the content, and use that as the min height of the scroll area.
28+ const contentMinHeight = useRef ( 0 ) ;
29+
30+ useLayoutEffect ( ( ) => {
31+ if (
32+ scrollAreaRef . current &&
33+ scrollAreaRef . current . scrollHeight > contentMinHeight . current
34+ ) {
35+ const isFirstScroll = contentMinHeight . current === 0 ;
36+ scrollAreaRef . current . scrollTo ( {
37+ top : scrollAreaRef . current . scrollHeight ,
38+ behavior : isFirstScroll ? "instant" : "smooth" ,
39+ } ) ;
40+ contentMinHeight . current = scrollAreaRef . current . scrollHeight ;
41+ }
42+ } , [ messages ] ) ;
43+
2044 // If no messages, show a placeholder
2145 if ( messages . length === 0 ) {
2246 return (
@@ -26,53 +50,12 @@ export default function MessageList({ messages }: MessageListProps) {
2650 ) ;
2751 }
2852
29- // Define a component for the animated dots
30- const LoadingDots = ( ) => (
31- < div className = "flex space-x-1" >
32- < div
33- aria-hidden = "true"
34- className = { `size-2 rounded-full bg-foreground animate-pulse [animation-delay:0ms]` }
35- />
36- < div
37- aria-hidden = "true"
38- className = { `size-2 rounded-full bg-foreground animate-pulse [animation-delay:300ms]` }
39- />
40- < div
41- aria-hidden = "true"
42- className = { `size-2 rounded-full bg-foreground animate-pulse [animation-delay:600ms]` }
43- />
44- < span className = "sr-only" > Loading...</ span >
45- </ div >
46- ) ;
47-
4853 return (
49- < div
50- className = "overflow-y-auto flex-1"
51- ref = { ( scrollAreaRef ) => {
52- if ( ! scrollAreaRef ) {
53- return ;
54- }
55-
56- scrollAreaRef . scrollTo ( {
57- top : scrollAreaRef . scrollHeight ,
58- } ) ;
59-
60- const callback : MutationCallback = ( mutationsList ) => {
61- for ( const mutation of mutationsList ) {
62- if ( mutation . type === "childList" ) {
63- scrollAreaRef . scrollTo ( {
64- top : scrollAreaRef . scrollHeight ,
65- behavior : "smooth" ,
66- } ) ;
67- }
68- }
69- } ;
70-
71- const observer = new MutationObserver ( callback ) ;
72- observer . observe ( scrollAreaRef , { childList : true , subtree : false } ) ;
73- } }
74- >
75- < div className = "p-4 flex flex-col gap-4 max-w-4xl mx-auto" >
54+ < div className = "overflow-y-auto flex-1" ref = { scrollAreaRef } >
55+ < div
56+ className = "p-4 flex flex-col gap-4 max-w-4xl mx-auto"
57+ style = { { minHeight : contentMinHeight . current } }
58+ >
7659 { messages . map ( ( message ) => (
7760 < div
7861 key = { message . id ?? "draft" }
@@ -93,7 +76,7 @@ export default function MessageList({ messages }: MessageListProps) {
9376 { message . role !== "user" && message . content === "" ? (
9477 < LoadingDots />
9578 ) : (
96- message . content
79+ message . content . trim ( )
9780 ) }
9881 </ div >
9982 </ div >
@@ -103,3 +86,21 @@ export default function MessageList({ messages }: MessageListProps) {
10386 </ div >
10487 ) ;
10588}
89+
90+ const LoadingDots = ( ) => (
91+ < div className = "flex space-x-1" >
92+ < div
93+ aria-hidden = "true"
94+ className = { `size-2 rounded-full bg-foreground animate-pulse [animation-delay:0ms]` }
95+ />
96+ < div
97+ aria-hidden = "true"
98+ className = { `size-2 rounded-full bg-foreground animate-pulse [animation-delay:300ms]` }
99+ />
100+ < div
101+ aria-hidden = "true"
102+ className = { `size-2 rounded-full bg-foreground animate-pulse [animation-delay:600ms]` }
103+ />
104+ < span className = "sr-only" > Loading...</ span >
105+ </ div >
106+ ) ;
0 commit comments