stream-chat-react 12.6.2 → 12.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/components/AIStateIndicator/AIStateIndicator.d.ts +7 -0
  2. package/dist/components/AIStateIndicator/AIStateIndicator.js +15 -0
  3. package/dist/components/AIStateIndicator/hooks/useAIState.d.ts +17 -0
  4. package/dist/components/AIStateIndicator/hooks/useAIState.js +39 -0
  5. package/dist/components/AIStateIndicator/index.d.ts +2 -0
  6. package/dist/components/AIStateIndicator/index.js +2 -0
  7. package/dist/components/Channel/Channel.d.ts +1 -1
  8. package/dist/components/Channel/Channel.js +4 -0
  9. package/dist/components/ChannelList/ChannelList.d.ts +2 -1
  10. package/dist/components/ChannelPreview/ChannelPreview.js +2 -2
  11. package/dist/components/ChannelPreview/utils.d.ts +2 -1
  12. package/dist/components/ChannelPreview/utils.js +4 -2
  13. package/dist/components/Chat/Chat.d.ts +2 -1
  14. package/dist/components/Chat/Chat.js +2 -1
  15. package/dist/components/Chat/hooks/useChat.js +1 -1
  16. package/dist/components/Chat/hooks/useCreateChatContext.js +3 -1
  17. package/dist/components/Message/Message.js +2 -1
  18. package/dist/components/Message/MessageSimple.js +10 -5
  19. package/dist/components/Message/StreamedMessageText.d.ts +8 -0
  20. package/dist/components/Message/StreamedMessageText.js +16 -0
  21. package/dist/components/Message/hooks/index.d.ts +1 -0
  22. package/dist/components/Message/hooks/index.js +1 -0
  23. package/dist/components/Message/hooks/useMessageTextStreaming.d.ts +16 -0
  24. package/dist/components/Message/hooks/useMessageTextStreaming.js +31 -0
  25. package/dist/components/Message/index.d.ts +1 -0
  26. package/dist/components/Message/index.js +1 -0
  27. package/dist/components/Message/renderText/renderText.js +7 -0
  28. package/dist/components/MessageInput/MessageInputFlat.js +16 -3
  29. package/dist/components/MessageInput/StopAIGenerationButton.d.ts +3 -0
  30. package/dist/components/MessageInput/StopAIGenerationButton.js +6 -0
  31. package/dist/components/index.d.ts +1 -0
  32. package/dist/components/index.js +1 -0
  33. package/dist/context/ChatContext.d.ts +1 -1
  34. package/dist/context/ComponentContext.d.ts +4 -1
  35. package/dist/context/MessageContext.d.ts +4 -0
  36. package/dist/css/v2/index.css +2 -2
  37. package/dist/css/v2/index.layout.css +2 -2
  38. package/dist/experimental/index.browser.cjs.map +2 -2
  39. package/dist/experimental/index.node.cjs.map +2 -2
  40. package/dist/i18n/Streami18n.d.ts +3 -0
  41. package/dist/i18n/de.json +3 -0
  42. package/dist/i18n/en.json +3 -0
  43. package/dist/i18n/es.json +3 -0
  44. package/dist/i18n/fr.json +3 -0
  45. package/dist/i18n/hi.json +3 -0
  46. package/dist/i18n/it.json +3 -0
  47. package/dist/i18n/ja.json +3 -0
  48. package/dist/i18n/ko.json +3 -0
  49. package/dist/i18n/nl.json +3 -0
  50. package/dist/i18n/pt.json +3 -0
  51. package/dist/i18n/ru.json +3 -0
  52. package/dist/i18n/tr.json +3 -0
  53. package/dist/index.browser.cjs +1291 -1088
  54. package/dist/index.browser.cjs.map +4 -4
  55. package/dist/index.node.cjs +1209 -1001
  56. package/dist/index.node.cjs.map +4 -4
  57. package/dist/scss/v2/AIStateIndicator/AIStateIndicator-layout.scss +3 -0
  58. package/dist/scss/v2/AIStateIndicator/AIStateIndicator-theme.scss +7 -0
  59. package/dist/scss/v2/MessageInput/MessageInput-layout.scss +7 -1
  60. package/dist/scss/v2/MessageInput/MessageInput-theme.scss +9 -3
  61. package/dist/scss/v2/_icons.scss +1 -0
  62. package/dist/scss/v2/index.layout.scss +1 -0
  63. package/dist/scss/v2/index.scss +1 -0
  64. package/package.json +4 -4
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import { Channel } from 'stream-chat';
3
+ import type { DefaultStreamChatGenerics } from '../../types/types';
4
+ export type AIStateIndicatorProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
5
+ channel?: Channel<StreamChatGenerics>;
6
+ };
7
+ export declare const AIStateIndicator: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ channel: channelFromProps, }: AIStateIndicatorProps<StreamChatGenerics>) => React.JSX.Element | null;
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import { AIStates, useAIState } from './hooks/useAIState';
3
+ import { useChannelStateContext, useTranslationContext } from '../../context';
4
+ export const AIStateIndicator = ({ channel: channelFromProps, }) => {
5
+ const { t } = useTranslationContext();
6
+ const { channel: channelFromContext } = useChannelStateContext('AIStateIndicator');
7
+ const channel = channelFromProps || channelFromContext;
8
+ const { aiState } = useAIState(channel);
9
+ const allowedStates = {
10
+ [AIStates.Thinking]: t('Thinking...'),
11
+ [AIStates.Generating]: t('Generating...'),
12
+ };
13
+ return aiState in allowedStates ? (React.createElement("div", { className: 'str-chat__ai-state-indicator-container' },
14
+ React.createElement("p", { className: 'str-chat__ai-state-indicator-text' }, allowedStates[aiState]))) : null;
15
+ };
@@ -0,0 +1,17 @@
1
+ import { AIState, Channel } from 'stream-chat';
2
+ import type { DefaultStreamChatGenerics } from '../../../types/types';
3
+ export declare const AIStates: {
4
+ Error: string;
5
+ ExternalSources: string;
6
+ Generating: string;
7
+ Idle: string;
8
+ Thinking: string;
9
+ };
10
+ /**
11
+ * A hook that returns the current state of the AI.
12
+ * @param {Channel} channel - The channel for which we want to know the AI state.
13
+ * @returns {{ aiState: AIState }} The current AI state for the given channel.
14
+ */
15
+ export declare const useAIState: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(channel?: Channel<StreamChatGenerics>) => {
16
+ aiState: AIState;
17
+ };
@@ -0,0 +1,39 @@
1
+ import { useEffect, useState } from 'react';
2
+ export const AIStates = {
3
+ Error: 'AI_STATE_ERROR',
4
+ ExternalSources: 'AI_STATE_EXTERNAL_SOURCES',
5
+ Generating: 'AI_STATE_GENERATING',
6
+ Idle: 'AI_STATE_IDLE',
7
+ Thinking: 'AI_STATE_THINKING',
8
+ };
9
+ /**
10
+ * A hook that returns the current state of the AI.
11
+ * @param {Channel} channel - The channel for which we want to know the AI state.
12
+ * @returns {{ aiState: AIState }} The current AI state for the given channel.
13
+ */
14
+ export const useAIState = (channel) => {
15
+ const [aiState, setAiState] = useState(AIStates.Idle);
16
+ useEffect(() => {
17
+ if (!channel) {
18
+ return;
19
+ }
20
+ const indicatorChangedListener = channel.on('ai_indicator.update', (event) => {
21
+ const { cid } = event;
22
+ const state = event.ai_state;
23
+ if (channel.cid === cid) {
24
+ setAiState(state);
25
+ }
26
+ });
27
+ const indicatorClearedListener = channel.on('ai_indicator.clear', (event) => {
28
+ const { cid } = event;
29
+ if (channel.cid === cid) {
30
+ setAiState(AIStates.Idle);
31
+ }
32
+ });
33
+ return () => {
34
+ indicatorChangedListener.unsubscribe();
35
+ indicatorClearedListener.unsubscribe();
36
+ };
37
+ }, [channel]);
38
+ return { aiState };
39
+ };
@@ -0,0 +1,2 @@
1
+ export * from './AIStateIndicator';
2
+ export * from './hooks/useAIState';
@@ -0,0 +1,2 @@
1
+ export * from './AIStateIndicator';
2
+ export * from './hooks/useAIState';
@@ -6,7 +6,7 @@ import { ComponentContextValue, StreamMessage } from '../../context';
6
6
  import type { MessageInputProps } from '../MessageInput';
7
7
  import type { ChannelUnreadUiState, CustomTrigger, DefaultStreamChatGenerics, GiphyVersions, ImageAttachmentSizeHandler, SendMessageOptions, UpdateMessageOptions, VideoAttachmentSizeHandler } from '../../types/types';
8
8
  import type { URLEnrichmentConfig } from '../MessageInput/hooks/useLinkPreviews';
9
- type ChannelPropsForwardedToComponentContext<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = Pick<ComponentContextValue<StreamChatGenerics>, 'Attachment' | 'AttachmentPreviewList' | 'AttachmentSelector' | 'AttachmentSelectorInitiationButtonContents' | 'AudioRecorder' | 'AutocompleteSuggestionItem' | 'AutocompleteSuggestionList' | 'Avatar' | 'BaseImage' | 'CooldownTimer' | 'CustomMessageActionsList' | 'DateSeparator' | 'EditMessageInput' | 'EmojiPicker' | 'emojiSearchIndex' | 'EmptyStateIndicator' | 'FileUploadIcon' | 'GiphyPreviewMessage' | 'HeaderComponent' | 'Input' | 'LinkPreviewList' | 'LoadingIndicator' | 'Message' | 'MessageActions' | 'MessageBouncePrompt' | 'MessageDeleted' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' | 'MessageOptions' | 'MessageRepliesCountButton' | 'MessageStatus' | 'MessageSystem' | 'MessageTimestamp' | 'ModalGallery' | 'PinIndicator' | 'PollActions' | 'PollContent' | 'PollCreationDialog' | 'PollHeader' | 'PollOptionSelector' | 'QuotedMessage' | 'QuotedMessagePreview' | 'QuotedPoll' | 'reactionOptions' | 'ReactionSelector' | 'ReactionsList' | 'SendButton' | 'StartRecordingAudioButton' | 'ThreadHead' | 'ThreadHeader' | 'ThreadStart' | 'Timestamp' | 'TriggerProvider' | 'TypingIndicator' | 'UnreadMessagesNotification' | 'UnreadMessagesSeparator' | 'VirtualMessage'>;
9
+ type ChannelPropsForwardedToComponentContext<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = Pick<ComponentContextValue<StreamChatGenerics>, 'Attachment' | 'AttachmentPreviewList' | 'AttachmentSelector' | 'AttachmentSelectorInitiationButtonContents' | 'AudioRecorder' | 'AutocompleteSuggestionItem' | 'AutocompleteSuggestionList' | 'Avatar' | 'BaseImage' | 'CooldownTimer' | 'CustomMessageActionsList' | 'DateSeparator' | 'EditMessageInput' | 'EmojiPicker' | 'emojiSearchIndex' | 'EmptyStateIndicator' | 'FileUploadIcon' | 'GiphyPreviewMessage' | 'HeaderComponent' | 'Input' | 'LinkPreviewList' | 'LoadingIndicator' | 'Message' | 'MessageActions' | 'MessageBouncePrompt' | 'MessageDeleted' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' | 'MessageOptions' | 'MessageRepliesCountButton' | 'MessageStatus' | 'MessageSystem' | 'MessageTimestamp' | 'ModalGallery' | 'PinIndicator' | 'PollActions' | 'PollContent' | 'PollCreationDialog' | 'PollHeader' | 'PollOptionSelector' | 'QuotedMessage' | 'QuotedMessagePreview' | 'QuotedPoll' | 'reactionOptions' | 'ReactionSelector' | 'ReactionsList' | 'SendButton' | 'StartRecordingAudioButton' | 'ThreadHead' | 'ThreadHeader' | 'ThreadStart' | 'Timestamp' | 'TriggerProvider' | 'TypingIndicator' | 'UnreadMessagesNotification' | 'UnreadMessagesSeparator' | 'VirtualMessage' | 'StopAIGenerationButton' | 'StreamedMessageText'>;
10
10
  export type ChannelProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, V extends CustomTrigger = CustomTrigger> = ChannelPropsForwardedToComponentContext<StreamChatGenerics> & {
11
11
  /** List of accepted file types */
12
12
  acceptedFiles?: string[];
@@ -782,6 +782,8 @@ const ChannelInner = (props) => {
782
782
  ReactionsList: props.ReactionsList,
783
783
  SendButton: props.SendButton,
784
784
  StartRecordingAudioButton: props.StartRecordingAudioButton,
785
+ StopAIGenerationButton: props.StopAIGenerationButton,
786
+ StreamedMessageText: props.StreamedMessageText,
785
787
  ThreadHead: props.ThreadHead,
786
788
  ThreadHeader: props.ThreadHeader,
787
789
  ThreadStart: props.ThreadStart,
@@ -847,6 +849,8 @@ const ChannelInner = (props) => {
847
849
  props.UnreadMessagesNotification,
848
850
  props.UnreadMessagesSeparator,
849
851
  props.VirtualMessage,
852
+ props.StopAIGenerationButton,
853
+ props.StreamedMessageText,
850
854
  props.emojiSearchIndex,
851
855
  props.reactionOptions,
852
856
  ]);
@@ -5,6 +5,7 @@ import { ChannelPreviewUIComponentProps } from '../ChannelPreview/ChannelPreview
5
5
  import { ChannelSearchProps } from '../ChannelSearch/ChannelSearch';
6
6
  import { EmptyStateIndicatorProps } from '../EmptyStateIndicator';
7
7
  import { LoadMorePaginatorProps } from '../LoadMore/LoadMorePaginator';
8
+ import { ChatContextValue } from '../../context';
8
9
  import type { Channel, ChannelFilters, ChannelOptions, ChannelSort, Event } from 'stream-chat';
9
10
  import type { ChannelAvatarProps } from '../Avatar';
10
11
  import type { TranslationContextValue } from '../../context/TranslationContext';
@@ -34,7 +35,7 @@ export type ChannelListProps<StreamChatGenerics extends DefaultStreamChatGeneric
34
35
  /** An object containing channel query filters */
35
36
  filters?: ChannelFilters<StreamChatGenerics>;
36
37
  /** Custom function that generates the message preview in ChannelPreview component */
37
- getLatestMessagePreview?: (channel: Channel<StreamChatGenerics>, t: TranslationContextValue['t'], userLanguage: TranslationContextValue['userLanguage']) => string | JSX.Element;
38
+ getLatestMessagePreview?: (channel: Channel<StreamChatGenerics>, t: TranslationContextValue['t'], userLanguage: TranslationContextValue['userLanguage'], isMessageAIGenerated?: ChatContextValue['isMessageAIGenerated']) => string | JSX.Element;
38
39
  /** 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) */
39
40
  List?: React.ComponentType<ChannelListMessengerProps<StreamChatGenerics>>;
40
41
  /** Custom UI component to display the loading error indicator, defaults to component that renders null */
@@ -9,7 +9,7 @@ import { useTranslationContext } from '../../context/TranslationContext';
9
9
  import { useMessageDeliveryStatus } from './hooks/useMessageDeliveryStatus';
10
10
  export const ChannelPreview = (props) => {
11
11
  const { channel, Preview = ChannelPreviewMessenger, channelUpdateCount, getLatestMessagePreview = defaultGetLatestMessagePreview, } = props;
12
- const { channel: activeChannel, client, setActiveChannel } = useChatContext('ChannelPreview');
12
+ const { channel: activeChannel, client, isMessageAIGenerated, setActiveChannel, } = useChatContext('ChannelPreview');
13
13
  const { t, userLanguage } = useTranslationContext('ChannelPreview');
14
14
  const { displayImage, displayTitle, groupChannelDisplayInfo } = useChannelPreviewInfo({
15
15
  channel,
@@ -75,6 +75,6 @@ export const ChannelPreview = (props) => {
75
75
  }, [channel, refreshUnreadCount, channelUpdateCount]);
76
76
  if (!Preview)
77
77
  return null;
78
- const latestMessagePreview = getLatestMessagePreview(channel, t, userLanguage);
78
+ const latestMessagePreview = getLatestMessagePreview(channel, t, userLanguage, isMessageAIGenerated);
79
79
  return (React.createElement(Preview, { ...props, active: isActive, displayImage: displayImage, displayTitle: displayTitle, groupChannelDisplayInfo: groupChannelDisplayInfo, lastMessage: lastMessage, latestMessage: latestMessagePreview, latestMessagePreview: latestMessagePreview, messageDeliveryStatus: messageDeliveryStatus, setActiveChannel: setActiveChannel, unread: unread }));
80
80
  };
@@ -2,8 +2,9 @@ import React from 'react';
2
2
  import type { Channel, UserResponse } from 'stream-chat';
3
3
  import type { TranslationContextValue } from '../../context/TranslationContext';
4
4
  import type { DefaultStreamChatGenerics } from '../../types/types';
5
+ import { ChatContextValue } from '../../context';
5
6
  export declare const renderPreviewText: (text: string) => React.JSX.Element;
6
- export declare const getLatestMessagePreview: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(channel: Channel<StreamChatGenerics>, t: TranslationContextValue['t'], userLanguage?: TranslationContextValue['userLanguage']) => string | JSX.Element;
7
+ export declare const getLatestMessagePreview: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(channel: Channel<StreamChatGenerics>, t: TranslationContextValue['t'], userLanguage?: TranslationContextValue['userLanguage'], isMessageAIGenerated?: ChatContextValue<StreamChatGenerics>['isMessageAIGenerated']) => string | JSX.Element;
7
8
  export type GroupChannelDisplayInfo = {
8
9
  image?: string;
9
10
  name?: string;
@@ -12,7 +12,7 @@ const getLatestPollVote = (latestVotesByOption) => {
12
12
  }
13
13
  return latestVote;
14
14
  };
15
- export const getLatestMessagePreview = (channel, t, userLanguage = 'en') => {
15
+ export const getLatestMessagePreview = (channel, t, userLanguage = 'en', isMessageAIGenerated) => {
16
16
  const latestMessage = channel.state.latestMessages[channel.state.latestMessages.length - 1];
17
17
  const previewTextToRender = latestMessage?.i18n?.[`${userLanguage}_text`] ||
18
18
  latestMessage?.text;
@@ -47,7 +47,9 @@ export const getLatestMessagePreview = (channel, t, userLanguage = 'en') => {
47
47
  }
48
48
  }
49
49
  if (previewTextToRender) {
50
- return renderPreviewText(previewTextToRender);
50
+ return isMessageAIGenerated?.(latestMessage)
51
+ ? previewTextToRender
52
+ : renderPreviewText(previewTextToRender);
51
53
  }
52
54
  if (latestMessage.command) {
53
55
  return `/${latestMessage.command}`;
@@ -4,6 +4,7 @@ import type { StreamChat } from 'stream-chat';
4
4
  import type { SupportedTranslations } from '../../i18n/types';
5
5
  import type { Streami18n } from '../../i18n/Streami18n';
6
6
  import type { DefaultStreamChatGenerics } from '../../types/types';
7
+ import type { MessageContextValue } from '../../context';
7
8
  export type ChatProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
8
9
  /** The StreamChat client object */
9
10
  client: StreamChat<StreamChatGenerics>;
@@ -25,7 +26,7 @@ export type ChatProps<StreamChatGenerics extends DefaultStreamChatGenerics = Def
25
26
  * Note: requires importing `stream-chat-react/css/v2/emoji-replacement.css` style sheet
26
27
  */
27
28
  useImageFlagEmojisOnWindows?: boolean;
28
- };
29
+ } & Partial<Pick<MessageContextValue<StreamChatGenerics>, 'isMessageAIGenerated'>>;
29
30
  /**
30
31
  * Wrapper component for a StreamChat application. Chat needs to be placed around any other chat components
31
32
  * as it provides the ChatContext.
@@ -9,7 +9,7 @@ import { TranslationProvider } from '../../context/TranslationContext';
9
9
  * as it provides the ChatContext.
10
10
  */
11
11
  export const Chat = (props) => {
12
- const { children, client, customClasses, defaultLanguage, i18nInstance, initialNavOpen = true, theme = 'messaging light', useImageFlagEmojisOnWindows = false, } = props;
12
+ const { children, client, customClasses, defaultLanguage, i18nInstance, initialNavOpen = true, isMessageAIGenerated, theme = 'messaging light', useImageFlagEmojisOnWindows = false, } = props;
13
13
  const { channel, closeMobileNav, getAppSettings, latestMessageDatesByChannels, mutes, navOpen, openMobileNav, setActiveChannel, translators, } = useChat({ client, defaultLanguage, i18nInstance, initialNavOpen });
14
14
  const channelsQueryState = useChannelsQueryState();
15
15
  const chatContextValue = useCreateChatContext({
@@ -19,6 +19,7 @@ export const Chat = (props) => {
19
19
  closeMobileNav,
20
20
  customClasses,
21
21
  getAppSettings,
22
+ isMessageAIGenerated,
22
23
  latestMessageDatesByChannels,
23
24
  mutes,
24
25
  navOpen,
@@ -28,7 +28,7 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
28
28
  if (!userAgent.includes('stream-chat-react')) {
29
29
  // result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
30
30
  // the upper-case text between double underscores is replaced with the actual semantic version of the library
31
- client.setUserAgent(`stream-chat-react-12.6.2-${userAgent}`);
31
+ client.setUserAgent(`stream-chat-react-12.7.1-${userAgent}`);
32
32
  }
33
33
  client.threads.registerSubscriptions();
34
34
  client.polls.registerSubscriptions();
@@ -1,6 +1,6 @@
1
1
  import { useMemo } from 'react';
2
2
  export const useCreateChatContext = (value) => {
3
- const { channel, channelsQueryState, client, closeMobileNav, customClasses, getAppSettings, latestMessageDatesByChannels, mutes, navOpen, openMobileNav, setActiveChannel, theme, useImageFlagEmojisOnWindows, } = value;
3
+ const { channel, channelsQueryState, client, closeMobileNav, customClasses, getAppSettings, isMessageAIGenerated, latestMessageDatesByChannels, mutes, navOpen, openMobileNav, setActiveChannel, theme, useImageFlagEmojisOnWindows, } = value;
4
4
  const channelCid = channel?.cid;
5
5
  const channelsQueryError = channelsQueryState.error;
6
6
  const channelsQueryInProgress = channelsQueryState.queryInProgress;
@@ -14,6 +14,7 @@ export const useCreateChatContext = (value) => {
14
14
  closeMobileNav,
15
15
  customClasses,
16
16
  getAppSettings,
17
+ isMessageAIGenerated,
17
18
  latestMessageDatesByChannels,
18
19
  mutes,
19
20
  navOpen,
@@ -31,6 +32,7 @@ export const useCreateChatContext = (value) => {
31
32
  getAppSettings,
32
33
  mutedUsersLength,
33
34
  navOpen,
35
+ isMessageAIGenerated,
34
36
  ]);
35
37
  return chatContext;
36
38
  };
@@ -5,7 +5,7 @@ import { MessageProvider, useChannelActionContext, useChannelStateContext, useCh
5
5
  import { MessageSimple as DefaultMessage } from './MessageSimple';
6
6
  const MessageWithContext = (props) => {
7
7
  const { canPin, groupedByUser, Message: propMessage, message, messageActions = Object.keys(MESSAGE_ACTIONS), onUserClick: propOnUserClick, onUserHover: propOnUserHover, userRoles, } = props;
8
- const { client } = useChatContext('Message');
8
+ const { client, isMessageAIGenerated } = useChatContext('Message');
9
9
  const { read } = useChannelStateContext('Message');
10
10
  const { Message: contextMessage } = useComponentContext('Message');
11
11
  const actionsEnabled = message.type === 'regular' && message.status === 'received';
@@ -58,6 +58,7 @@ const MessageWithContext = (props) => {
58
58
  editing,
59
59
  getMessageActions: messageActionsHandler,
60
60
  handleEdit: setEdit,
61
+ isMessageAIGenerated,
61
62
  isMyMessage: () => isMyMessage,
62
63
  messageIsUnread,
63
64
  onUserClick,
@@ -1,4 +1,4 @@
1
- import React, { useState } from 'react';
1
+ import React, { useMemo, useState } from 'react';
2
2
  import clsx from 'clsx';
3
3
  import { MessageErrorIcon } from './icons';
4
4
  import { MessageBouncePrompt as DefaultMessageBouncePrompt } from '../MessageBounce';
@@ -22,8 +22,9 @@ import { useComponentContext } from '../../context/ComponentContext';
22
22
  import { useMessageContext } from '../../context/MessageContext';
23
23
  import { useChatContext, useTranslationContext } from '../../context';
24
24
  import { MessageEditedTimestamp } from './MessageEditedTimestamp';
25
+ import { StreamedMessageText as DefaultStreamedMessageText } from './StreamedMessageText';
25
26
  const MessageSimpleWithContext = (props) => {
26
- const { additionalMessageInputProps, clearEditingState, editing, endOfGroup, firstOfGroup, groupedByUser, handleAction, handleOpenThread, handleRetry, highlighted, isMyMessage, message, onUserClick, onUserHover, renderText, threadList, } = props;
27
+ const { additionalMessageInputProps, clearEditingState, editing, endOfGroup, firstOfGroup, groupedByUser, handleAction, handleOpenThread, handleRetry, highlighted, isMessageAIGenerated, isMyMessage, message, onUserClick, onUserHover, renderText, threadList, } = props;
27
28
  const { client } = useChatContext('MessageSimple');
28
29
  const { t } = useTranslationContext('MessageSimple');
29
30
  const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false);
@@ -31,9 +32,13 @@ const MessageSimpleWithContext = (props) => {
31
32
  const { Attachment = DefaultAttachment, Avatar = DefaultAvatar, EditMessageInput = DefaultEditMessageForm, MessageOptions = DefaultMessageOptions,
32
33
  // TODO: remove this "passthrough" in the next
33
34
  // major release and use the new default instead
34
- MessageActions = MessageOptions, MessageDeleted = DefaultMessageDeleted, MessageBouncePrompt = DefaultMessageBouncePrompt, MessageRepliesCountButton = DefaultMessageRepliesCountButton, MessageStatus = DefaultMessageStatus, MessageTimestamp = DefaultMessageTimestamp, ReactionsList = DefaultReactionList, PinIndicator, } = useComponentContext('MessageSimple');
35
+ MessageActions = MessageOptions, MessageDeleted = DefaultMessageDeleted, MessageBouncePrompt = DefaultMessageBouncePrompt, MessageRepliesCountButton = DefaultMessageRepliesCountButton, MessageStatus = DefaultMessageStatus, MessageTimestamp = DefaultMessageTimestamp, ReactionsList = DefaultReactionList, StreamedMessageText = DefaultStreamedMessageText, PinIndicator, } = useComponentContext('MessageSimple');
35
36
  const hasAttachment = messageHasAttachments(message);
36
37
  const hasReactions = messageHasReactions(message);
38
+ const isAIGenerated = useMemo(() => isMessageAIGenerated?.(message), [
39
+ isMessageAIGenerated,
40
+ message,
41
+ ]);
37
42
  if (message.customType === CUSTOM_MESSAGE_TYPE.date) {
38
43
  return null;
39
44
  }
@@ -44,7 +49,7 @@ const MessageSimpleWithContext = (props) => {
44
49
  const showReplyCountButton = !threadList && !!message.reply_count;
45
50
  const allowRetry = message.status === 'failed' && message.errorStatusCode !== 403;
46
51
  const isBounced = isMessageBounced(message);
47
- const isEdited = isMessageEdited(message);
52
+ const isEdited = isMessageEdited(message) && !isAIGenerated;
48
53
  let handleClick = undefined;
49
54
  if (allowRetry) {
50
55
  handleClick = () => handleRetry(message);
@@ -84,7 +89,7 @@ const MessageSimpleWithContext = (props) => {
84
89
  React.createElement("div", { className: 'str-chat__message-bubble' },
85
90
  poll && React.createElement(Poll, { poll: poll }),
86
91
  message.attachments?.length && !message.quoted_message ? (React.createElement(Attachment, { actionHandler: handleAction, attachments: message.attachments })) : null,
87
- React.createElement(MessageText, { message: message, renderText: renderText }),
92
+ isAIGenerated ? (React.createElement(StreamedMessageText, { message: message, renderText: renderText })) : (React.createElement(MessageText, { message: message, renderText: renderText })),
88
93
  message.mml && (React.createElement(MML, { actionHandler: handleAction, align: isMyMessage() ? 'right' : 'left', source: message.mml })),
89
94
  React.createElement(MessageErrorIcon, null))),
90
95
  showReplyCountButton && (React.createElement(MessageRepliesCountButton, { onClick: handleOpenThread, reply_count: message.reply_count })),
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { MessageTextProps } from './MessageText';
3
+ import type { DefaultStreamChatGenerics } from '../../types/types';
4
+ export type StreamedMessageTextProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = Pick<MessageTextProps<StreamChatGenerics>, 'message' | 'renderText'> & {
5
+ renderingLetterCount?: number;
6
+ streamingLetterIntervalMs?: number;
7
+ };
8
+ export declare const StreamedMessageText: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(props: StreamedMessageTextProps<StreamChatGenerics>) => React.JSX.Element;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { MessageText } from './MessageText';
3
+ import { useMessageContext } from '../../context';
4
+ import { useMessageTextStreaming } from './hooks';
5
+ export const StreamedMessageText = (props) => {
6
+ const { message: messageFromProps, renderingLetterCount, renderText, streamingLetterIntervalMs, } = props;
7
+ const { message: messageFromContext } = useMessageContext('StreamedMessageText');
8
+ const message = messageFromProps || messageFromContext;
9
+ const { text = '' } = message;
10
+ const { streamedMessageText } = useMessageTextStreaming({
11
+ renderingLetterCount,
12
+ streamingLetterIntervalMs,
13
+ text,
14
+ });
15
+ return (React.createElement(MessageText, { message: { ...message, text: streamedMessageText }, renderText: renderText }));
16
+ };
@@ -12,3 +12,4 @@ export * from './useRetryHandler';
12
12
  export * from './useUserHandler';
13
13
  export * from './useUserRole';
14
14
  export * from './useReactionsFetcher';
15
+ export * from './useMessageTextStreaming';
@@ -12,3 +12,4 @@ export * from './useRetryHandler';
12
12
  export * from './useUserHandler';
13
13
  export * from './useUserRole';
14
14
  export * from './useReactionsFetcher';
15
+ export * from './useMessageTextStreaming';
@@ -0,0 +1,16 @@
1
+ import type { DefaultStreamChatGenerics } from '../../../types/types';
2
+ import type { StreamedMessageTextProps } from '../StreamedMessageText';
3
+ export type UseMessageTextStreamingProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = Pick<StreamedMessageTextProps<StreamChatGenerics>, 'streamingLetterIntervalMs' | 'renderingLetterCount'> & {
4
+ text: string;
5
+ };
6
+ /**
7
+ * A hook that returns text in a streamed, typewriter fashion. The speed of streaming is
8
+ * configurable.
9
+ * @param {number} [streamingLetterIntervalMs=30] - The timeout between each typing animation in milliseconds.
10
+ * @param {number} [renderingLetterCount=2] - The number of letters to be rendered each time we update.
11
+ * @param {string} text - The text that we want to render in a typewriter fashion.
12
+ * @returns {{ streamedMessageText: string }} - A substring of the text property, up until we've finished rendering the typewriter animation.
13
+ */
14
+ export declare const useMessageTextStreaming: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ streamingLetterIntervalMs, renderingLetterCount, text, }: UseMessageTextStreamingProps<StreamChatGenerics>) => {
15
+ streamedMessageText: string;
16
+ };
@@ -0,0 +1,31 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ const DEFAULT_LETTER_INTERVAL = 30;
3
+ const DEFAULT_RENDERING_LETTER_COUNT = 2;
4
+ /**
5
+ * A hook that returns text in a streamed, typewriter fashion. The speed of streaming is
6
+ * configurable.
7
+ * @param {number} [streamingLetterIntervalMs=30] - The timeout between each typing animation in milliseconds.
8
+ * @param {number} [renderingLetterCount=2] - The number of letters to be rendered each time we update.
9
+ * @param {string} text - The text that we want to render in a typewriter fashion.
10
+ * @returns {{ streamedMessageText: string }} - A substring of the text property, up until we've finished rendering the typewriter animation.
11
+ */
12
+ export const useMessageTextStreaming = ({ streamingLetterIntervalMs = DEFAULT_LETTER_INTERVAL, renderingLetterCount = DEFAULT_RENDERING_LETTER_COUNT, text, }) => {
13
+ const [streamedMessageText, setStreamedMessageText] = useState(text);
14
+ const textCursor = useRef(text.length);
15
+ useEffect(() => {
16
+ const textLength = text.length;
17
+ const interval = setInterval(() => {
18
+ if (!text || textCursor.current >= textLength) {
19
+ clearInterval(interval);
20
+ }
21
+ const newCursorValue = textCursor.current + renderingLetterCount;
22
+ const newText = text.substring(0, newCursorValue);
23
+ textCursor.current += newText.length - textCursor.current;
24
+ setStreamedMessageText(newText);
25
+ }, streamingLetterIntervalMs);
26
+ return () => {
27
+ clearInterval(interval);
28
+ };
29
+ }, [streamingLetterIntervalMs, renderingLetterCount, text]);
30
+ return { streamedMessageText };
31
+ };
@@ -13,4 +13,5 @@ export * from './QuotedMessage';
13
13
  export * from './renderText';
14
14
  export * from './types';
15
15
  export * from './utils';
16
+ export * from './StreamedMessageText';
16
17
  export type { TimestampProps } from './Timestamp';
@@ -13,3 +13,4 @@ export * from './QuotedMessage';
13
13
  export * from './renderText';
14
14
  export * from './types';
15
15
  export * from './utils';
16
+ export * from './StreamedMessageText';
@@ -23,6 +23,13 @@ export const defaultAllowedTagNames = [
23
23
  'pre',
24
24
  'blockquote',
25
25
  'del',
26
+ 'table',
27
+ 'thead',
28
+ 'tbody',
29
+ 'th',
30
+ 'tr',
31
+ 'td',
32
+ 'tfoot',
26
33
  // custom types (tagNames)
27
34
  'emoji',
28
35
  'mention',
@@ -5,6 +5,7 @@ import { AttachmentSelector as DefaultAttachmentSelector, SimpleAttachmentSelect
5
5
  import { AttachmentPreviewList as DefaultAttachmentPreviewList } from './AttachmentPreviewList';
6
6
  import { CooldownTimer as DefaultCooldownTimer } from './CooldownTimer';
7
7
  import { SendButton as DefaultSendButton } from './SendButton';
8
+ import { StopAIGenerationButton as DefaultStopAIGenerationButton } from './StopAIGenerationButton';
8
9
  import { AudioRecorder as DefaultAudioRecorder, RecordingPermissionDeniedNotification as DefaultRecordingPermissionDeniedNotification, StartRecordingAudioButton as DefaultStartRecordingAudioButton, RecordingPermission, } from '../MediaRecorder';
9
10
  import { QuotedMessagePreview as DefaultQuotedMessagePreview, QuotedMessagePreviewHeader, } from './QuotedMessagePreview';
10
11
  import { LinkPreviewList as DefaultLinkPreviewList } from './LinkPreviewList';
@@ -16,13 +17,16 @@ import { useChannelStateContext } from '../../context/ChannelStateContext';
16
17
  import { useTranslationContext } from '../../context/TranslationContext';
17
18
  import { useMessageInputContext } from '../../context/MessageInputContext';
18
19
  import { useComponentContext } from '../../context/ComponentContext';
20
+ import { AIStates, useAIState } from '../AIStateIndicator';
19
21
  export const MessageInputFlat = () => {
20
22
  const { t } = useTranslationContext('MessageInputFlat');
21
23
  const { asyncMessagesMultiSendEnabled, attachments, cooldownRemaining, findAndEnqueueURLsToEnrich, handleSubmit, hideSendButton, isUploadEnabled, linkPreviews, maxFilesLeft, message, numberOfUploads, parent, recordingController, setCooldownRemaining, text, uploadNewFiles, } = useMessageInputContext('MessageInputFlat');
22
- const { AudioRecorder = DefaultAudioRecorder, AttachmentPreviewList = DefaultAttachmentPreviewList, AttachmentSelector = message ? SimpleAttachmentSelector : DefaultAttachmentSelector, CooldownTimer = DefaultCooldownTimer, LinkPreviewList = DefaultLinkPreviewList, QuotedMessagePreview = DefaultQuotedMessagePreview, RecordingPermissionDeniedNotification = DefaultRecordingPermissionDeniedNotification, SendButton = DefaultSendButton, StartRecordingAudioButton = DefaultStartRecordingAudioButton, EmojiPicker, } = useComponentContext('MessageInputFlat');
24
+ const { AudioRecorder = DefaultAudioRecorder, AttachmentPreviewList = DefaultAttachmentPreviewList, AttachmentSelector = message ? SimpleAttachmentSelector : DefaultAttachmentSelector, CooldownTimer = DefaultCooldownTimer, LinkPreviewList = DefaultLinkPreviewList, QuotedMessagePreview = DefaultQuotedMessagePreview, RecordingPermissionDeniedNotification = DefaultRecordingPermissionDeniedNotification, SendButton = DefaultSendButton, StartRecordingAudioButton = DefaultStartRecordingAudioButton, StopAIGenerationButton: StopAIGenerationButtonOverride, EmojiPicker, } = useComponentContext('MessageInputFlat');
23
25
  const { acceptedFiles = [], multipleUploads, quotedMessage, } = useChannelStateContext('MessageInputFlat');
24
26
  const { setQuotedMessage } = useChannelActionContext('MessageInputFlat');
25
27
  const { channel } = useChatContext('MessageInputFlat');
28
+ const { aiState } = useAIState(channel);
29
+ const stopGenerating = useCallback(() => channel?.stopAIResponse(), [channel]);
26
30
  const [showRecordingPermissionDeniedNotification, setShowRecordingPermissionDeniedNotification,] = useState(false);
27
31
  const closePermissionDeniedNotification = useCallback(() => {
28
32
  setShowRecordingPermissionDeniedNotification(false);
@@ -64,6 +68,15 @@ export const MessageInputFlat = () => {
64
68
  const displayQuotedMessage = !message && quotedMessage && quotedMessage.parent_id === parent?.id;
65
69
  const recordingEnabled = !!(recordingController.recorder && navigator.mediaDevices); // account for requirement on iOS as per this bug report: https://bugs.webkit.org/show_bug.cgi?id=252303
66
70
  const isRecording = !!recordingController.recordingState;
71
+ /* This bit here is needed to make sure that we can get rid of the default behaviour
72
+ * if need be. Essentially this allows us to pass StopAIGenerationButton={null} and
73
+ * completely circumvent the default logic if it's not what we want. We need it as a
74
+ * prop because there is no other trivial way to override the SendMessage button otherwise.
75
+ */
76
+ const StopAIGenerationButton = StopAIGenerationButtonOverride === undefined
77
+ ? DefaultStopAIGenerationButton
78
+ : StopAIGenerationButtonOverride;
79
+ const shouldDisplayStopAIGeneration = [AIStates.Thinking, AIStates.Generating].includes(aiState) && !!StopAIGenerationButton;
67
80
  return (React.createElement(React.Fragment, null,
68
81
  React.createElement("div", { ...getRootProps({ className: 'str-chat__message-input' }) },
69
82
  recordingEnabled &&
@@ -85,7 +98,7 @@ export const MessageInputFlat = () => {
85
98
  React.createElement("div", { className: 'str-chat__message-textarea-with-emoji-picker' },
86
99
  React.createElement(ChatAutoComplete, null),
87
100
  EmojiPicker && React.createElement(EmojiPicker, null))),
88
- !hideSendButton && (React.createElement(React.Fragment, null, cooldownRemaining ? (React.createElement(CooldownTimer, { cooldownInterval: cooldownRemaining, setCooldownRemaining: setCooldownRemaining })) : (React.createElement(React.Fragment, null,
101
+ shouldDisplayStopAIGeneration ? (React.createElement(StopAIGenerationButton, { onClick: stopGenerating })) : (!hideSendButton && (React.createElement(React.Fragment, null, cooldownRemaining ? (React.createElement(CooldownTimer, { cooldownInterval: cooldownRemaining, setCooldownRemaining: setCooldownRemaining })) : (React.createElement(React.Fragment, null,
89
102
  React.createElement(SendButton, { disabled: !numberOfUploads &&
90
103
  !text.length &&
91
104
  attachments.length - failedUploadsCount === 0, sendMessage: handleSubmit }),
@@ -94,5 +107,5 @@ export const MessageInputFlat = () => {
94
107
  attachments.some((a) => a.type === RecordingAttachmentType.VOICE_RECORDING)), onClick: () => {
95
108
  recordingController.recorder?.start();
96
109
  setShowRecordingPermissionDeniedNotification(true);
97
- } }))))))))));
110
+ } })))))))))));
98
111
  };
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ export type StopAIGenerationButtonProps = React.ComponentProps<'button'>;
3
+ export declare const StopAIGenerationButton: ({ onClick, ...restProps }: StopAIGenerationButtonProps) => React.JSX.Element;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import { useTranslationContext } from '../../context';
3
+ export const StopAIGenerationButton = ({ onClick, ...restProps }) => {
4
+ const { t } = useTranslationContext();
5
+ return (React.createElement("button", { "aria-label": t('aria/Stop AI Generation'), className: 'str-chat__stop-ai-generation-button', "data-testid": 'stop-ai-generation-button', onClick: onClick, ...restProps }));
6
+ };
@@ -36,5 +36,6 @@ export * from './UserItem';
36
36
  export * from './Window';
37
37
  export * from './Threads';
38
38
  export * from './ChatView';
39
+ export * from './AIStateIndicator';
39
40
  export { UploadButton } from './ReactFileUtilities';
40
41
  export type { UploadButtonProps } from './ReactFileUtilities';
@@ -35,4 +35,5 @@ export * from './UserItem';
35
35
  export * from './Window';
36
36
  export * from './Threads';
37
37
  export * from './ChatView';
38
+ export * from './AIStateIndicator';
38
39
  export { UploadButton } from './ReactFileUtilities';
@@ -36,7 +36,7 @@ export type ChatContextValue<StreamChatGenerics extends DefaultStreamChatGeneric
36
36
  */
37
37
  customClasses?: CustomClasses;
38
38
  navOpen?: boolean;
39
- } & Required<Pick<ChatProps<StreamChatGenerics>, 'theme' | 'client'>>;
39
+ } & Partial<Pick<ChatProps<StreamChatGenerics>, 'isMessageAIGenerated'>> & Required<Pick<ChatProps<StreamChatGenerics>, 'theme' | 'client'>>;
40
40
  export declare const ChatContext: React.Context<ChatContextValue<DefaultStreamChatGenerics> | undefined>;
41
41
  export declare const ChatProvider: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ children, value, }: PropsWithChildren<{
42
42
  value: ChatContextValue<StreamChatGenerics>;
@@ -1,6 +1,7 @@
1
1
  import React, { PropsWithChildren } from 'react';
2
- import { AttachmentPreviewListProps, AttachmentProps, AvatarProps, BaseImageProps, CooldownTimerProps, CustomMessageActionsListProps, DateSeparatorProps, EmojiSearchIndex, EmptyStateIndicatorProps, EventComponentProps, FixedHeightMessageProps, GiphyPreviewMessageProps, LinkPreviewListProps, LoadingIndicatorProps, MessageBouncePromptProps, MessageDeletedProps, MessageInputProps, MessageListNotificationsProps, MessageNotificationProps, MessageOptionsProps, MessageProps, MessageRepliesCountButtonProps, MessageStatusProps, MessageTimestampProps, MessageUIComponentProps, ModalGalleryProps, PinIndicatorProps, PollCreationDialogProps, PollOptionSelectorProps, QuotedMessagePreviewProps, ReactionOptions, ReactionSelectorProps, ReactionsListProps, RecordingPermissionDeniedNotificationProps, SendButtonProps, StartRecordingAudioButtonProps, SuggestionItemProps, SuggestionListProps, ThreadHeaderProps, ThreadListItemProps, ThreadListItemUIProps, TimestampProps, TypingIndicatorProps, UnreadMessagesNotificationProps, UnreadMessagesSeparatorProps } from '../components';
2
+ import { AttachmentPreviewListProps, AttachmentProps, AvatarProps, BaseImageProps, CooldownTimerProps, CustomMessageActionsListProps, DateSeparatorProps, EmojiSearchIndex, EmptyStateIndicatorProps, EventComponentProps, FixedHeightMessageProps, GiphyPreviewMessageProps, LinkPreviewListProps, LoadingIndicatorProps, MessageBouncePromptProps, MessageDeletedProps, MessageInputProps, MessageListNotificationsProps, MessageNotificationProps, MessageOptionsProps, MessageProps, MessageRepliesCountButtonProps, MessageStatusProps, MessageTimestampProps, MessageUIComponentProps, ModalGalleryProps, PinIndicatorProps, PollCreationDialogProps, PollOptionSelectorProps, QuotedMessagePreviewProps, ReactionOptions, ReactionSelectorProps, ReactionsListProps, RecordingPermissionDeniedNotificationProps, SendButtonProps, StartRecordingAudioButtonProps, StreamedMessageTextProps, SuggestionItemProps, SuggestionListProps, ThreadHeaderProps, ThreadListItemProps, ThreadListItemUIProps, TimestampProps, TypingIndicatorProps, UnreadMessagesNotificationProps, UnreadMessagesSeparatorProps } from '../components';
3
3
  import type { CustomTrigger, DefaultStreamChatGenerics, PropsWithChildrenOnly, UnknownType } from '../types/types';
4
+ import type { StopAIGenerationButtonProps } from '../components/MessageInput/StopAIGenerationButton';
4
5
  export type ComponentContextValue<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, V extends CustomTrigger = CustomTrigger> = {
5
6
  /** Custom UI component to display a message attachment, defaults to and accepts same props as: [Attachment](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Attachment/Attachment.tsx) */
6
7
  Attachment?: React.ComponentType<AttachmentProps<StreamChatGenerics>>;
@@ -107,6 +108,8 @@ export type ComponentContextValue<StreamChatGenerics extends DefaultStreamChatGe
107
108
  SendButton?: React.ComponentType<SendButtonProps<StreamChatGenerics>>;
108
109
  /** Custom UI component button for initiating audio recording, defaults to and accepts same props as: [StartRecordingAudioButton](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MediaRecorder/AudioRecorder/AudioRecordingButtons.tsx) */
109
110
  StartRecordingAudioButton?: React.ComponentType<StartRecordingAudioButtonProps>;
111
+ StopAIGenerationButton?: React.ComponentType<StopAIGenerationButtonProps> | null;
112
+ StreamedMessageText?: React.ComponentType<StreamedMessageTextProps>;
110
113
  /** Custom UI component that displays thread's parent or other message at the top of the `MessageList`, defaults to and accepts same props as [MessageSimple](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageSimple.tsx) */
111
114
  ThreadHead?: React.ComponentType<MessageProps<StreamChatGenerics>>;
112
115
  /** Custom UI component to display the header of a `Thread`, defaults to and accepts same props as: [DefaultThreadHeader](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Thread/Thread.tsx) */
@@ -86,6 +86,10 @@ export type MessageContextValue<StreamChatGenerics extends DefaultStreamChatGene
86
86
  highlighted?: boolean;
87
87
  /** Whether the threaded message is the first in the thread list */
88
88
  initialMessage?: boolean;
89
+ /**
90
+ * A factory function that determines whether a message is AI generated or not.
91
+ */
92
+ isMessageAIGenerated?: (message: StreamMessage<StreamChatGenerics>) => boolean;
89
93
  /** Latest message id on current channel */
90
94
  lastReceivedId?: string | null;
91
95
  /** DOMRect object for parent MessageList component */