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 = {
2021name ?:string ;
2122isLoading ?:boolean ;
2223formattedTime ?:string ;
23- aiModel ?:string
24- stateMessage ?:StateMessage | null
25- isCurrentStreamMessage ?:boolean
24+ aiModel ?:string ;
25+ stateMessage ?:StateMessage | null ;
26+ isCurrentStreamMessage ?:boolean ;
2627isPublic ?:boolean ;
28+ threadId ?:string ;
29+ status ?:MessageStatus
2730}
2831
2932type AiMessageProps = BaseMessageProps & {
3033isAi :true ;
3134content :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 & {
4346isLoading :true ;
4447isAi :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+
267277const [ isDebugVisible , setDebugVisible ] = useState ( false ) ;
268278
269279
270280const 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+
272305const contentToRender :string = content ?. replace ( / \n / g, ' \n' ) || ''
273306
274307const toggleDebugDialog = ( ) => {
@@ -301,7 +334,7 @@ export const Message = React.memo((props: MessageProps) => {
301334onClose = { toggleDebugDialog }
302335messageId = { id }
303336/> }
304- < div className = { classes . message } >
337+ < div ref = { elementRef } className = { classes . message } >
305338< div className = { classes . messageAvatar } >
306339{ isAi
307340 ?< img