@@ -18,7 +18,15 @@ interface MessageListProps {
1818 messages : ( Message | DraftMessage ) [ ] ;
1919}
2020
21- export default function MessageList ( { messages } : MessageListProps ) {
21+ interface ProcessedMessageProps {
22+ messageContent : string ;
23+ index : number ;
24+ modifierPressed : boolean ;
25+ urlRegex : RegExp ;
26+ handleClick : ( e : React . MouseEvent < HTMLAnchorElement > , url : string ) => void ;
27+ }
28+
29+ export default function MessageList ( { messages} : MessageListProps ) {
2230 const scrollAreaRef = useRef < HTMLDivElement > ( null ) ;
2331 // Avoid the message list to change its height all the time. It causes some
2432 // flickering in the screen because some messages, as the ones displaying
@@ -34,7 +42,7 @@ export default function MessageList({ messages }: MessageListProps) {
3442
3543 const checkIfAtBottom = useCallback ( ( ) => {
3644 if ( ! scrollAreaRef . current ) return false ;
37- const { scrollTop, scrollHeight, clientHeight } = scrollAreaRef . current ;
45+ const { scrollTop, scrollHeight, clientHeight} = scrollAreaRef . current ;
3846 return scrollTop + clientHeight >= scrollHeight - 10 ; // 10px tolerance
3947 } , [ ] ) ;
4048
@@ -117,39 +125,13 @@ export default function MessageList({ messages }: MessageListProps) {
117125 lastScrollHeightRef . current = currentScrollHeight ;
118126 } , [ messages ] ) ;
119127
120- const handleClick = ( e : React . MouseEvent < HTMLAnchorElement > , url : string ) => {
128+ const handleClick = useCallback ( ( ) => ( e : React . MouseEvent < HTMLAnchorElement > , url : string ) => {
121129 if ( e . metaKey || e . ctrlKey ) {
122130 window . open ( url , "_blank" ) ;
123131 } else {
124132 e . preventDefault ( ) ; // disable normal click to emulate terminal behaviour
125133 }
126- } ;
127-
128- const buildClickableLinks = useCallback ( ( message : string , msg_index : number ) => {
129- const linkedContent = message . split ( urlRegex ) . map ( ( content , index ) => {
130- if ( urlRegex . test ( content ) ) {
131- return (
132- < a
133- key = { `${ msg_index } -${ index } ` }
134- href = { content }
135- onClick = { ( e ) => handleClick ( e , content ) }
136- className = { `${
137- modifierPressed ? "hover:underline cursor-pointer" : "cursor-default"
138- } `}
139- >
140- { content }
141- </ a >
142- ) ;
143- }
144- return < span key = { `${ msg_index } -${ index } ` } > { content } </ span > ;
145- } )
146-
147- return < >
148- { linkedContent }
149- </ >
150- } , [ modifierPressed , urlRegex ] )
151-
152-
134+ } , [ ] ) ;
153135
154136 // If no messages, show a placeholder
155137 if ( messages . length === 0 ) {
@@ -164,7 +146,7 @@ export default function MessageList({ messages }: MessageListProps) {
164146 < div className = "overflow-y-auto flex-1" ref = { scrollAreaRef } >
165147 < div
166148 className = "p-4 flex flex-col gap-4 max-w-4xl mx-auto"
167- style = { { minHeight : contentMinHeight . current } }
149+ style = { { minHeight : contentMinHeight . current } }
168150 >
169151 { messages . map ( ( message , index ) => (
170152 < div
@@ -184,9 +166,15 @@ export default function MessageList({ messages }: MessageListProps) {
184166 } `}
185167 >
186168 { message . role !== "user" && message . content === "" ? (
187- < LoadingDots />
169+ < LoadingDots />
188170 ) : (
189- buildClickableLinks ( message . content . trimEnd ( ) , index )
171+ < ProcessedMessage
172+ messageContent = { message . content }
173+ index = { index }
174+ modifierPressed = { modifierPressed }
175+ urlRegex = { urlRegex }
176+ handleClick = { handleClick }
177+ />
190178 ) }
191179 </ div >
192180 </ div >
@@ -214,3 +202,34 @@ const LoadingDots = () => (
214202 < span className = "sr-only" > Loading...</ span >
215203 </ div >
216204) ;
205+
206+
207+ const ProcessedMessage = React . memo ( function ProcessedMessage ( {
208+ messageContent,
209+ index,
210+ modifierPressed,
211+ urlRegex,
212+ handleClick
213+ } : ProcessedMessageProps ) {
214+ const linkedContent = useMemo ( ( ) => {
215+ return messageContent . split ( urlRegex ) . map ( ( content , idx ) => {
216+ if ( urlRegex . test ( content ) ) {
217+ return (
218+ < a
219+ key = { `${ index } -${ idx } ` }
220+ href = { content }
221+ onClick = { ( e ) => handleClick ( e , content ) }
222+ className = { `${
223+ modifierPressed ? "hover:underline cursor-pointer" : "cursor-default"
224+ } `}
225+ >
226+ { content }
227+ </ a >
228+ ) ;
229+ }
230+ return < span key = { `${ index } -${ idx } ` } > { content } </ span > ;
231+ } ) ;
232+ } , [ handleClick , index , messageContent , modifierPressed , urlRegex ] ) ;
233+
234+ return < > { linkedContent } </ > ;
235+ } ) ;
0 commit comments