From 5aae3f8523128047a34c0a502641e17a4b09feba Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Thu, 5 Dec 2024 12:55:50 +0100 Subject: [PATCH 1/4] fix: add custom way to find if message is ai generated --- examples/vite/src/App.tsx | 2 +- src/components/ChannelList/ChannelList.tsx | 3 ++- src/components/ChannelPreview/ChannelPreview.tsx | 16 ++++++++++++---- src/components/ChannelPreview/utils.tsx | 4 +++- src/components/Chat/Chat.tsx | 5 ++++- .../Chat/hooks/useCreateChatContext.ts | 2 ++ src/components/Message/Message.tsx | 3 ++- src/components/Message/MessageSimple.tsx | 5 +++-- src/components/Message/utils.tsx | 5 ++--- src/context/ChatContext.tsx | 2 +- src/context/MessageContext.tsx | 4 ++++ 11 files changed, 36 insertions(+), 15 deletions(-) diff --git a/examples/vite/src/App.tsx b/examples/vite/src/App.tsx index f617e1f1c..ced37ffe8 100644 --- a/examples/vite/src/App.tsx +++ b/examples/vite/src/App.tsx @@ -73,7 +73,7 @@ const App = () => { if (!chatClient) return <>Loading...; return ( - + !!message?.ai_generated}> diff --git a/src/components/ChannelList/ChannelList.tsx b/src/components/ChannelList/ChannelList.tsx index 6b7ba41eb..73fd8564e 100644 --- a/src/components/ChannelList/ChannelList.tsx +++ b/src/components/ChannelList/ChannelList.tsx @@ -30,7 +30,7 @@ import { LoadingChannels } from '../Loading/LoadingChannels'; import { LoadMorePaginator, LoadMorePaginatorProps } from '../LoadMore/LoadMorePaginator'; import { NullComponent } from '../UtilityComponents'; -import { ChannelListContextProvider } from '../../context'; +import { ChannelListContextProvider, ChatContextValue } from '../../context'; import { useChatContext } from '../../context/ChatContext'; import type { Channel, ChannelFilters, ChannelOptions, ChannelSort, Event } from 'stream-chat'; @@ -75,6 +75,7 @@ export type ChannelListProps< channel: Channel, t: TranslationContextValue['t'], userLanguage: TranslationContextValue['userLanguage'], + isMessageAIGenerated?: ChatContextValue['isMessageAIGenerated'], ) => string | JSX.Element; /** Custom UI component to display the container for the queried channels, defaults to and accepts same props as: [ChannelListMessenger](https://github.com/GetStream/stream-chat-react/blob/master/src/components/ChannelList/ChannelListMessenger.tsx) */ List?: React.ComponentType>; diff --git a/src/components/ChannelPreview/ChannelPreview.tsx b/src/components/ChannelPreview/ChannelPreview.tsx index 836805935..a25bd333e 100644 --- a/src/components/ChannelPreview/ChannelPreview.tsx +++ b/src/components/ChannelPreview/ChannelPreview.tsx @@ -82,9 +82,12 @@ export const ChannelPreview = < channelUpdateCount, getLatestMessagePreview = defaultGetLatestMessagePreview, } = props; - const { channel: activeChannel, client, setActiveChannel } = useChatContext( - 'ChannelPreview', - ); + const { + channel: activeChannel, + client, + isMessageAIGenerated, + setActiveChannel, + } = useChatContext('ChannelPreview'); const { t, userLanguage } = useTranslationContext('ChannelPreview'); const { displayImage, displayTitle, groupChannelDisplayInfo } = useChannelPreviewInfo({ channel, @@ -162,7 +165,12 @@ export const ChannelPreview = < if (!Preview) return null; - const latestMessagePreview = getLatestMessagePreview(channel, t, userLanguage); + const latestMessagePreview = getLatestMessagePreview( + channel, + t, + userLanguage, + isMessageAIGenerated, + ); return ( {text}; @@ -32,6 +33,7 @@ export const getLatestMessagePreview = < channel: Channel, t: TranslationContextValue['t'], userLanguage: TranslationContextValue['userLanguage'] = 'en', + isMessageAIGenerated?: ChatContextValue['isMessageAIGenerated'], ): string | JSX.Element => { const latestMessage = channel.state.latestMessages[channel.state.latestMessages.length - 1]; @@ -77,7 +79,7 @@ export const getLatestMessagePreview = < } if (previewTextToRender) { - return latestMessage.ai_generated + return isMessageAIGenerated?.(latestMessage) ? previewTextToRender : renderPreviewText(previewTextToRender); } diff --git a/src/components/Chat/Chat.tsx b/src/components/Chat/Chat.tsx index 6e997cd44..e6751265b 100644 --- a/src/components/Chat/Chat.tsx +++ b/src/components/Chat/Chat.tsx @@ -12,6 +12,7 @@ import type { StreamChat } from 'stream-chat'; import type { SupportedTranslations } from '../../i18n/types'; import type { Streami18n } from '../../i18n/Streami18n'; import type { DefaultStreamChatGenerics } from '../../types/types'; +import { MessageContextValue } from '../../context'; export type ChatProps< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics @@ -36,7 +37,7 @@ export type ChatProps< * Note: requires importing `stream-chat-react/css/v2/emoji-replacement.css` style sheet */ useImageFlagEmojisOnWindows?: boolean; -}; +} & Partial, 'isMessageAIGenerated'>>; /** * Wrapper component for a StreamChat application. Chat needs to be placed around any other chat components @@ -54,6 +55,7 @@ export const Chat = < defaultLanguage, i18nInstance, initialNavOpen = true, + isMessageAIGenerated = () => false, theme = 'messaging light', useImageFlagEmojisOnWindows = false, } = props; @@ -79,6 +81,7 @@ export const Chat = < closeMobileNav, customClasses, getAppSettings, + isMessageAIGenerated, latestMessageDatesByChannels, mutes, navOpen, diff --git a/src/components/Chat/hooks/useCreateChatContext.ts b/src/components/Chat/hooks/useCreateChatContext.ts index 31924d9c7..ca168246f 100644 --- a/src/components/Chat/hooks/useCreateChatContext.ts +++ b/src/components/Chat/hooks/useCreateChatContext.ts @@ -15,6 +15,7 @@ export const useCreateChatContext = < closeMobileNav, customClasses, getAppSettings, + isMessageAIGenerated, latestMessageDatesByChannels, mutes, navOpen, @@ -41,6 +42,7 @@ export const useCreateChatContext = < closeMobileNav, customClasses, getAppSettings, + isMessageAIGenerated, latestMessageDatesByChannels, mutes, navOpen, diff --git a/src/components/Message/Message.tsx b/src/components/Message/Message.tsx index 1ed4a60ce..786c9046a 100644 --- a/src/components/Message/Message.tsx +++ b/src/components/Message/Message.tsx @@ -76,7 +76,7 @@ const MessageWithContext = < userRoles, } = props; - const { client } = useChatContext('Message'); + const { client, isMessageAIGenerated } = useChatContext('Message'); const { read } = useChannelStateContext('Message'); const { Message: contextMessage } = useComponentContext('Message'); @@ -159,6 +159,7 @@ const MessageWithContext = < editing, getMessageActions: messageActionsHandler, handleEdit: setEdit, + isMessageAIGenerated, isMyMessage: () => isMyMessage, messageIsUnread, onUserClick, diff --git a/src/components/Message/MessageSimple.tsx b/src/components/Message/MessageSimple.tsx index fdd3cfff0..dd16ca1d2 100644 --- a/src/components/Message/MessageSimple.tsx +++ b/src/components/Message/MessageSimple.tsx @@ -56,6 +56,7 @@ const MessageSimpleWithContext = < handleOpenThread, handleRetry, highlighted, + isMessageAIGenerated, isMyMessage, message, onUserClick, @@ -101,7 +102,7 @@ const MessageSimpleWithContext = < const showReplyCountButton = !threadList && !!message.reply_count; const allowRetry = message.status === 'failed' && message.errorStatusCode !== 403; const isBounced = isMessageBounced(message); - const isEdited = isMessageEdited(message); + const isEdited = isMessageEdited(message) && !isMessageAIGenerated(message); let handleClick: (() => void) | undefined = undefined; @@ -187,7 +188,7 @@ const MessageSimpleWithContext = < {message.attachments?.length && !message.quoted_message ? ( ) : null} - {message.ai_generated ? ( + {isMessageAIGenerated(message) ? ( ) : ( diff --git a/src/components/Message/utils.tsx b/src/components/Message/utils.tsx index d8132d8b7..2a01bb31f 100644 --- a/src/components/Message/utils.tsx +++ b/src/components/Message/utils.tsx @@ -494,6 +494,5 @@ export const isMessageBounced = < export const isMessageEdited = < StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics >( - message: Pick, 'message_text_updated_at'> & - Partial, 'ai_generated'>>, -) => !!message.message_text_updated_at && !message.ai_generated; + message: Pick, 'message_text_updated_at'>, +) => !!message.message_text_updated_at; diff --git a/src/context/ChatContext.tsx b/src/context/ChatContext.tsx index fecc05e07..a95365f91 100644 --- a/src/context/ChatContext.tsx +++ b/src/context/ChatContext.tsx @@ -56,7 +56,7 @@ export type ChatContextValue< */ customClasses?: CustomClasses; navOpen?: boolean; -} & Required, 'theme' | 'client'>>; +} & Required, 'theme' | 'client' | 'isMessageAIGenerated'>>; export const ChatContext = React.createContext(undefined); diff --git a/src/context/MessageContext.tsx b/src/context/MessageContext.tsx index 3cd6bbfdf..10f5b2d0d 100644 --- a/src/context/MessageContext.tsx +++ b/src/context/MessageContext.tsx @@ -68,6 +68,10 @@ export type MessageContextValue< handleReaction: (reactionType: string, event: React.BaseSyntheticEvent) => Promise; /** Function to retry sending a Message */ handleRetry: ChannelActionContextValue['retrySendMessage']; + /** + * A factory function that determines whether a message is AI generated or not. + */ + isMessageAIGenerated: (message: StreamMessage) => boolean; /** Function that returns whether the Message belongs to the current user */ isMyMessage: () => boolean; /** The message object */ From 222713bc24171801507b6e8692aaa25cea7e07cb Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Thu, 5 Dec 2024 13:21:59 +0100 Subject: [PATCH 2/4] fix: refactor to fix tests --- src/components/Chat/Chat.tsx | 2 +- src/components/Chat/hooks/useCreateChatContext.ts | 1 + src/components/Message/MessageSimple.tsx | 4 ++-- src/context/ChatContext.tsx | 3 ++- src/context/MessageContext.tsx | 8 ++++---- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/components/Chat/Chat.tsx b/src/components/Chat/Chat.tsx index e6751265b..777a7d9d6 100644 --- a/src/components/Chat/Chat.tsx +++ b/src/components/Chat/Chat.tsx @@ -55,7 +55,7 @@ export const Chat = < defaultLanguage, i18nInstance, initialNavOpen = true, - isMessageAIGenerated = () => false, + isMessageAIGenerated, theme = 'messaging light', useImageFlagEmojisOnWindows = false, } = props; diff --git a/src/components/Chat/hooks/useCreateChatContext.ts b/src/components/Chat/hooks/useCreateChatContext.ts index ca168246f..d9cec2b0b 100644 --- a/src/components/Chat/hooks/useCreateChatContext.ts +++ b/src/components/Chat/hooks/useCreateChatContext.ts @@ -60,6 +60,7 @@ export const useCreateChatContext = < getAppSettings, mutedUsersLength, navOpen, + isMessageAIGenerated, ], ); diff --git a/src/components/Message/MessageSimple.tsx b/src/components/Message/MessageSimple.tsx index dd16ca1d2..2da429f1c 100644 --- a/src/components/Message/MessageSimple.tsx +++ b/src/components/Message/MessageSimple.tsx @@ -102,7 +102,7 @@ const MessageSimpleWithContext = < const showReplyCountButton = !threadList && !!message.reply_count; const allowRetry = message.status === 'failed' && message.errorStatusCode !== 403; const isBounced = isMessageBounced(message); - const isEdited = isMessageEdited(message) && !isMessageAIGenerated(message); + const isEdited = isMessageEdited(message) && !isMessageAIGenerated?.(message); let handleClick: (() => void) | undefined = undefined; @@ -188,7 +188,7 @@ const MessageSimpleWithContext = < {message.attachments?.length && !message.quoted_message ? ( ) : null} - {isMessageAIGenerated(message) ? ( + {isMessageAIGenerated?.(message) ? ( ) : ( diff --git a/src/context/ChatContext.tsx b/src/context/ChatContext.tsx index a95365f91..514942a5d 100644 --- a/src/context/ChatContext.tsx +++ b/src/context/ChatContext.tsx @@ -56,7 +56,8 @@ export type ChatContextValue< */ customClasses?: CustomClasses; navOpen?: boolean; -} & Required, 'theme' | 'client' | 'isMessageAIGenerated'>>; +} & Partial, 'isMessageAIGenerated'>> & + Required, 'theme' | 'client'>>; export const ChatContext = React.createContext(undefined); diff --git a/src/context/MessageContext.tsx b/src/context/MessageContext.tsx index 10f5b2d0d..74e6d7a77 100644 --- a/src/context/MessageContext.tsx +++ b/src/context/MessageContext.tsx @@ -68,10 +68,6 @@ export type MessageContextValue< handleReaction: (reactionType: string, event: React.BaseSyntheticEvent) => Promise; /** Function to retry sending a Message */ handleRetry: ChannelActionContextValue['retrySendMessage']; - /** - * A factory function that determines whether a message is AI generated or not. - */ - isMessageAIGenerated: (message: StreamMessage) => boolean; /** Function that returns whether the Message belongs to the current user */ isMyMessage: () => boolean; /** The message object */ @@ -110,6 +106,10 @@ export type MessageContextValue< highlighted?: boolean; /** Whether the threaded message is the first in the thread list */ initialMessage?: boolean; + /** + * A factory function that determines whether a message is AI generated or not. + */ + isMessageAIGenerated?: (message: StreamMessage) => boolean; /** Latest message id on current channel */ lastReceivedId?: string | null; /** DOMRect object for parent MessageList component */ From 8fd9e9713b22f6b17afddca62a133e71d3b31a79 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Thu, 5 Dec 2024 13:22:55 +0100 Subject: [PATCH 3/4] fix: add import type --- src/components/Chat/Chat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Chat/Chat.tsx b/src/components/Chat/Chat.tsx index 777a7d9d6..b908ee47b 100644 --- a/src/components/Chat/Chat.tsx +++ b/src/components/Chat/Chat.tsx @@ -12,7 +12,7 @@ import type { StreamChat } from 'stream-chat'; import type { SupportedTranslations } from '../../i18n/types'; import type { Streami18n } from '../../i18n/Streami18n'; import type { DefaultStreamChatGenerics } from '../../types/types'; -import { MessageContextValue } from '../../context'; +import type { MessageContextValue } from '../../context'; export type ChatProps< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics From d3397d6bb06b716511909f1255fa9415495fad93 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Thu, 5 Dec 2024 13:36:50 +0100 Subject: [PATCH 4/4] fix: memoize response from isMessageAIGenerated --- src/components/Message/MessageSimple.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/Message/MessageSimple.tsx b/src/components/Message/MessageSimple.tsx index 2da429f1c..e82200fed 100644 --- a/src/components/Message/MessageSimple.tsx +++ b/src/components/Message/MessageSimple.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import clsx from 'clsx'; import { MessageErrorIcon } from './icons'; @@ -89,6 +89,10 @@ const MessageSimpleWithContext = < const hasAttachment = messageHasAttachments(message); const hasReactions = messageHasReactions(message); + const isAIGenerated = useMemo(() => isMessageAIGenerated?.(message), [ + isMessageAIGenerated, + message, + ]); if (message.customType === CUSTOM_MESSAGE_TYPE.date) { return null; @@ -102,7 +106,7 @@ const MessageSimpleWithContext = < const showReplyCountButton = !threadList && !!message.reply_count; const allowRetry = message.status === 'failed' && message.errorStatusCode !== 403; const isBounced = isMessageBounced(message); - const isEdited = isMessageEdited(message) && !isMessageAIGenerated?.(message); + const isEdited = isMessageEdited(message) && !isAIGenerated; let handleClick: (() => void) | undefined = undefined; @@ -188,7 +192,7 @@ const MessageSimpleWithContext = < {message.attachments?.length && !message.quoted_message ? ( ) : null} - {isMessageAIGenerated?.(message) ? ( + {isAIGenerated ? ( ) : (