1
- import React , { useMemo , useState } from 'react'
1
+ import React , { useEffect , useMemo , useRef , useState } from 'react'
2
2
import cn from "classnames" ;
3
3
import ReactMarkdown , { Components } from "react-markdown" ;
4
4
import rehypeRaw from "rehype-raw" ;
@@ -9,8 +9,9 @@ import { icons } from "@postgres.ai/shared/styles/icons";
9
9
import { DebugDialog } from "../../DebugDialog/DebugDialog" ;
10
10
import { CodeBlock } from "./CodeBlock" ;
11
11
import { disallowedHtmlTagsForMarkdown , permalinkLinkBuilder } from "../../utils" ;
12
- import { StateMessage } from "../../../../types/api/entities/bot" ;
12
+ import { MessageStatus , StateMessage } from "../../../../types/api/entities/bot" ;
13
13
import { MermaidDiagram } from "./MermaidDiagram" ;
14
+ import { useAiBot } from "../../hooks" ;
14
15
15
16
16
17
type BaseMessageProps = {
@@ -20,17 +21,19 @@ type BaseMessageProps = {
20
21
name ?: string ;
21
22
isLoading ?: boolean ;
22
23
formattedTime ?: string ;
23
- aiModel ?: string
24
- stateMessage ?: StateMessage | null
25
- isCurrentStreamMessage ?: boolean
24
+ aiModel ?: string ;
25
+ stateMessage ?: StateMessage | null ;
26
+ isCurrentStreamMessage ?: boolean ;
26
27
isPublic ?: boolean ;
28
+ threadId ?: string ;
29
+ status ?: MessageStatus
27
30
}
28
31
29
32
type AiMessageProps = BaseMessageProps & {
30
33
isAi : true ;
31
34
content : string ;
32
- aiModel : string
33
- isCurrentStreamMessage ?: boolean
35
+ aiModel : string ;
36
+ isCurrentStreamMessage ?: boolean ;
34
37
}
35
38
36
39
type HumanMessageProps = BaseMessageProps & {
@@ -42,8 +45,8 @@ type HumanMessageProps = BaseMessageProps & {
42
45
type LoadingMessageProps = BaseMessageProps & {
43
46
isLoading : true ;
44
47
isAi : true ;
45
- content ?: undefined
46
- stateMessage : StateMessage | null
48
+ content ?: undefined ;
49
+ stateMessage : StateMessage | null ;
47
50
}
48
51
49
52
type MessageProps = AiMessageProps | HumanMessageProps | LoadingMessageProps ;
@@ -261,14 +264,44 @@ export const Message = React.memo((props: MessageProps) => {
261
264
aiModel,
262
265
stateMessage,
263
266
isCurrentStreamMessage,
264
- isPublic
267
+ isPublic,
268
+ threadId,
269
+ status
265
270
} = props ;
266
271
272
+ const { updateMessageStatus } = useAiBot ( )
273
+
274
+ const elementRef = useRef < HTMLDivElement | null > ( null ) ;
275
+
276
+
267
277
const [ isDebugVisible , setDebugVisible ] = useState ( false ) ;
268
278
269
279
270
280
const classes = useStyles ( ) ;
271
281
282
+ useEffect ( ( ) => {
283
+ if ( ! isAi || isCurrentStreamMessage || status === 'read' ) return ;
284
+
285
+ const observer = new IntersectionObserver (
286
+ ( entries ) => {
287
+ const entry = entries [ 0 ] ;
288
+ if ( entry . isIntersecting && threadId && id ) {
289
+ updateMessageStatus ( threadId , id , 'read' ) ;
290
+ observer . disconnect ( ) ;
291
+ }
292
+ } ,
293
+ { threshold : 0.1 }
294
+ ) ;
295
+
296
+ if ( elementRef . current ) {
297
+ observer . observe ( elementRef . current ) ;
298
+ }
299
+
300
+ return ( ) => {
301
+ observer . disconnect ( ) ;
302
+ } ;
303
+ } , [ id , updateMessageStatus , isCurrentStreamMessage , isAi , threadId , status ] ) ;
304
+
272
305
const contentToRender : string = content ?. replace ( / \n / g, ' \n' ) || ''
273
306
274
307
const toggleDebugDialog = ( ) => {
@@ -301,7 +334,7 @@ export const Message = React.memo((props: MessageProps) => {
301
334
onClose = { toggleDebugDialog }
302
335
messageId = { id }
303
336
/> }
304
- < div className = { classes . message } >
337
+ < div ref = { elementRef } className = { classes . message } >
305
338
< div className = { classes . messageAvatar } >
306
339
{ isAi
307
340
? < img
0 commit comments