1- import React , { useMemo , useState } from 'react'
1+ import React , { useEffect , useMemo , useRef , useState } from 'react'
22import cn from "classnames" ;
33import ReactMarkdown , { Components } from "react-markdown" ;
44import rehypeRaw from "rehype-raw" ;
@@ -9,8 +9,9 @@ import { icons } from "@postgres.ai/shared/styles/icons";
99import { DebugDialog } from "../../DebugDialog/DebugDialog" ;
1010import { CodeBlock } from "./CodeBlock" ;
1111import { disallowedHtmlTagsForMarkdown , permalinkLinkBuilder } from "../../utils" ;
12- import { StateMessage } from "../../../../types/api/entities/bot" ;
12+ import { MessageStatus , StateMessage } from "../../../../types/api/entities/bot" ;
1313import { MermaidDiagram } from "./MermaidDiagram" ;
14+ import { useAiBot } from "../../hooks" ;
1415
1516
1617type BaseMessageProps = {
@@ -20,17 +21,19 @@ type BaseMessageProps = {
2021 name ?: string ;
2122 isLoading ?: boolean ;
2223 formattedTime ?: string ;
23- aiModel ?: string
24- stateMessage ?: StateMessage | null
25- isCurrentStreamMessage ?: boolean
24+ aiModel ?: string ;
25+ stateMessage ?: StateMessage | null ;
26+ isCurrentStreamMessage ?: boolean ;
2627 isPublic ?: boolean ;
28+ threadId ?: string ;
29+ status ?: MessageStatus
2730}
2831
2932type AiMessageProps = BaseMessageProps & {
3033 isAi : true ;
3134 content : string ;
32- aiModel : string
33- isCurrentStreamMessage ?: boolean
35+ aiModel : string ;
36+ isCurrentStreamMessage ?: boolean ;
3437}
3538
3639type HumanMessageProps = BaseMessageProps & {
@@ -42,8 +45,8 @@ type HumanMessageProps = BaseMessageProps & {
4245type LoadingMessageProps = BaseMessageProps & {
4346 isLoading : true ;
4447 isAi : true ;
45- content ?: undefined
46- stateMessage : StateMessage | null
48+ content ?: undefined ;
49+ stateMessage : StateMessage | null ;
4750}
4851
4952type MessageProps = AiMessageProps | HumanMessageProps | LoadingMessageProps ;
@@ -261,14 +264,44 @@ export const Message = React.memo((props: MessageProps) => {
261264 aiModel,
262265 stateMessage,
263266 isCurrentStreamMessage,
264- isPublic
267+ isPublic,
268+ threadId,
269+ status
265270 } = props ;
266271
272+ const { updateMessageStatus } = useAiBot ( )
273+
274+ const elementRef = useRef < HTMLDivElement | null > ( null ) ;
275+
276+
267277 const [ isDebugVisible , setDebugVisible ] = useState ( false ) ;
268278
269279
270280 const classes = useStyles ( ) ;
271281
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+
272305 const contentToRender : string = content ?. replace ( / \n / g, ' \n' ) || ''
273306
274307 const toggleDebugDialog = ( ) => {
@@ -301,7 +334,7 @@ export const Message = React.memo((props: MessageProps) => {
301334 onClose = { toggleDebugDialog }
302335 messageId = { id }
303336 /> }
304- < div className = { classes . message } >
337+ < div ref = { elementRef } className = { classes . message } >
305338 < div className = { classes . messageAvatar } >
306339 { isAi
307340 ? < img
0 commit comments