@@ -18,7 +18,15 @@ interface MessageListProps {
18
18
messages : ( Message | DraftMessage ) [ ] ;
19
19
}
20
20
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 ) {
22
30
const scrollAreaRef = useRef < HTMLDivElement > ( null ) ;
23
31
// Avoid the message list to change its height all the time. It causes some
24
32
// flickering in the screen because some messages, as the ones displaying
@@ -34,7 +42,7 @@ export default function MessageList({ messages }: MessageListProps) {
34
42
35
43
const checkIfAtBottom = useCallback ( ( ) => {
36
44
if ( ! scrollAreaRef . current ) return false ;
37
- const { scrollTop, scrollHeight, clientHeight } = scrollAreaRef . current ;
45
+ const { scrollTop, scrollHeight, clientHeight} = scrollAreaRef . current ;
38
46
return scrollTop + clientHeight >= scrollHeight - 10 ; // 10px tolerance
39
47
} , [ ] ) ;
40
48
@@ -117,39 +125,13 @@ export default function MessageList({ messages }: MessageListProps) {
117
125
lastScrollHeightRef . current = currentScrollHeight ;
118
126
} , [ messages ] ) ;
119
127
120
- const handleClick = ( e : React . MouseEvent < HTMLAnchorElement > , url : string ) => {
128
+ const handleClick = useCallback ( ( ) => ( e : React . MouseEvent < HTMLAnchorElement > , url : string ) => {
121
129
if ( e . metaKey || e . ctrlKey ) {
122
130
window . open ( url , "_blank" ) ;
123
131
} else {
124
132
e . preventDefault ( ) ; // disable normal click to emulate terminal behaviour
125
133
}
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
+ } , [ ] ) ;
153
135
154
136
// If no messages, show a placeholder
155
137
if ( messages . length === 0 ) {
@@ -164,7 +146,7 @@ export default function MessageList({ messages }: MessageListProps) {
164
146
< div className = "overflow-y-auto flex-1" ref = { scrollAreaRef } >
165
147
< div
166
148
className = "p-4 flex flex-col gap-4 max-w-4xl mx-auto"
167
- style = { { minHeight : contentMinHeight . current } }
149
+ style = { { minHeight : contentMinHeight . current } }
168
150
>
169
151
{ messages . map ( ( message , index ) => (
170
152
< div
@@ -184,9 +166,15 @@ export default function MessageList({ messages }: MessageListProps) {
184
166
} `}
185
167
>
186
168
{ message . role !== "user" && message . content === "" ? (
187
- < LoadingDots />
169
+ < LoadingDots />
188
170
) : (
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
+ />
190
178
) }
191
179
</ div >
192
180
</ div >
@@ -214,3 +202,34 @@ const LoadingDots = () => (
214
202
< span className = "sr-only" > Loading...</ span >
215
203
</ div >
216
204
) ;
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