1
1
import React , { useEffect , useMemo , useRef , useState } from 'react'
2
- import cn from "classnames" ;
3
2
import ReactMarkdown , { Components } from "react-markdown" ;
4
3
import rehypeRaw from "rehype-raw" ;
5
4
import remarkGfm from "remark-gfm" ;
6
5
import { makeStyles } from "@material-ui/core" ;
7
6
import { colors } from "@postgres.ai/shared/styles/colors" ;
8
7
import { icons } from "@postgres.ai/shared/styles/icons" ;
9
8
import { DebugDialog } from "../../DebugDialog/DebugDialog" ;
10
- import { CodeBlock } from "./CodeBlock" ;
11
- import { disallowedHtmlTagsForMarkdown , permalinkLinkBuilder } from "../../utils" ;
9
+ import { CodeBlock } from "./CodeBlock/CodeBlock " ;
10
+ import { disallowedHtmlTagsForMarkdown } from "../../utils" ;
12
11
import { MessageStatus , StateMessage } from "../../../../types/api/entities/bot" ;
13
- import { MermaidDiagram } from "./MermaidDiagram" ;
12
+ import { MermaidDiagram } from "./MermaidDiagram/MermaidDiagram " ;
14
13
import { useAiBot } from "../../hooks" ;
14
+ import { ToolCallRenderer } from "./ToolCallRenderer/ToolCallRenderer" ;
15
+ import { transformAllCustomTags } from "../utils" ;
16
+ import { ThinkBlockRenderer } from './ThinkingCard/ThinkingCard' ;
17
+ import { MessageHeader } from "./MessageHeader/MessageHeader" ;
15
18
16
19
17
- type BaseMessageProps = {
20
+ export type BaseMessageProps = {
18
21
id : string | null ;
19
22
created_at ?: string ;
20
23
content ?: string ;
@@ -249,7 +252,6 @@ const useStyles = makeStyles(
249
252
'50%' : { borderRightColor : 'black' } ,
250
253
} ,
251
254
} ) ,
252
-
253
255
)
254
256
255
257
export const Message = React . memo ( ( props : MessageProps ) => {
@@ -302,12 +304,16 @@ export const Message = React.memo((props: MessageProps) => {
302
304
} ;
303
305
} , [ id , updateMessageStatus , isCurrentStreamMessage , isAi , threadId , status ] ) ;
304
306
305
- const contentToRender : string = content ?. replace ( / \n / g, ' \n' ) || ''
307
+ const contentToRender = useMemo ( ( ) => {
308
+ if ( ! content ) return '' ;
309
+ return transformAllCustomTags ( content ?. replace ( / \n / g, ' \n' ) ) ;
310
+ } , [ content ] ) ;
306
311
307
312
const toggleDebugDialog = ( ) => {
308
313
setDebugVisible ( prevState => ! prevState )
309
314
}
310
315
316
+
311
317
const renderers = useMemo < Components > ( ( ) => ( {
312
318
p : ( { node, ...props } ) => < div { ...props } /> ,
313
319
img : ( { node, ...props } ) => < img style = { { maxWidth : '60%' } } { ...props } /> ,
@@ -325,6 +331,8 @@ export const Message = React.memo((props: MessageProps) => {
325
331
return < code { ...props } > { children } </ code >
326
332
}
327
333
} ,
334
+ toolcall : ToolCallRenderer ,
335
+ thinkblock : ThinkBlockRenderer ,
328
336
} ) , [ ] ) ;
329
337
330
338
return (
@@ -344,51 +352,17 @@ export const Message = React.memo((props: MessageProps) => {
344
352
/>
345
353
: icons . userChatIcon }
346
354
</ div >
347
- < div className = { classes . messageHeader } >
348
- < span className = { classes . messageAuthor } >
349
- { isAi ? 'Postgres.AI' : name }
350
- </ span >
351
- { created_at && formattedTime &&
352
- < span
353
- className = { cn ( classes . messageInfo ) }
354
- title = { created_at }
355
- >
356
- { formattedTime }
357
- </ span > }
358
- < div className = { classes . additionalInfo } >
359
- { id && isPublic && < >
360
- < span className = { classes . messageInfo } > |</ span >
361
- < a
362
- className = { cn ( classes . messageInfo , classes . messageInfoActive ) }
363
- href = { permalinkLinkBuilder ( id ) }
364
- target = "_blank"
365
- rel = "noreferrer"
366
- >
367
- permalink
368
- </ a >
369
- </ > }
370
- { ! isLoading && isAi && id && < >
371
- < span className = { classes . messageInfo } > |</ span >
372
- < button
373
- className = { cn ( classes . messageInfo , classes . messageInfoActive ) }
374
- onClick = { toggleDebugDialog }
375
- >
376
- debug info
377
- </ button >
378
- </ > }
379
- {
380
- aiModel && isAi && < >
381
- < span className = { classes . messageInfo } > |</ span >
382
- < span
383
- className = { cn ( classes . messageInfo ) }
384
- title = { aiModel }
385
- >
386
- { aiModel }
387
- </ span >
388
- </ >
389
- }
390
- </ div >
391
- </ div >
355
+ < MessageHeader
356
+ name = { name }
357
+ createdAt = { created_at }
358
+ formattedTime = { formattedTime }
359
+ id = { id }
360
+ isPublic = { isPublic }
361
+ isAi = { isAi }
362
+ isLoading = { isLoading }
363
+ toggleDebugDialog = { toggleDebugDialog }
364
+ aiModel = { aiModel }
365
+ />
392
366
< div >
393
367
{ isLoading
394
368
?
@@ -397,16 +371,21 @@ export const Message = React.memo((props: MessageProps) => {
397
371
{ stateMessage && stateMessage . state ? stateMessage . state : 'Thinking' }
398
372
</ div >
399
373
</ div >
400
- : < ReactMarkdown
401
- className = { classes . markdown }
402
- children = { contentToRender || '' }
403
- rehypePlugins = { isAi ? [ rehypeRaw ] : [ ] }
404
- remarkPlugins = { [ remarkGfm ] }
405
- linkTarget = '_blank'
406
- components = { renderers }
407
- disallowedElements = { disallowedHtmlTagsForMarkdown }
408
- unwrapDisallowed
409
- />
374
+ : < >
375
+ < ReactMarkdown
376
+ className = { classes . markdown }
377
+ children = { contentToRender || '' }
378
+ rehypePlugins = { isAi ? [ rehypeRaw ] : [ ] }
379
+ remarkPlugins = { [ remarkGfm ] }
380
+ linkTarget = '_blank'
381
+ components = { renderers }
382
+ disallowedElements = { disallowedHtmlTagsForMarkdown }
383
+ unwrapDisallowed
384
+ />
385
+ { stateMessage && stateMessage . state && < div className = { classes . loading } >
386
+ { stateMessage . state }
387
+ </ div > }
388
+ </ >
410
389
}
411
390
</ div >
412
391
</ div >
0 commit comments