stream-chat-react 12.6.1 → 12.7.0

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 (57) 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/ChannelPreview/utils.js +3 -1
  10. package/dist/components/Chat/hooks/useChat.js +2 -2
  11. package/dist/components/Message/MessageSimple.js +3 -2
  12. package/dist/components/Message/StreamedMessageText.d.ts +8 -0
  13. package/dist/components/Message/StreamedMessageText.js +16 -0
  14. package/dist/components/Message/hooks/index.d.ts +1 -0
  15. package/dist/components/Message/hooks/index.js +1 -0
  16. package/dist/components/Message/hooks/useMessageTextStreaming.d.ts +16 -0
  17. package/dist/components/Message/hooks/useMessageTextStreaming.js +31 -0
  18. package/dist/components/Message/index.d.ts +1 -0
  19. package/dist/components/Message/index.js +1 -0
  20. package/dist/components/Message/renderText/renderText.js +7 -0
  21. package/dist/components/Message/utils.d.ts +1 -1
  22. package/dist/components/Message/utils.js +1 -1
  23. package/dist/components/MessageInput/MessageInputFlat.js +16 -3
  24. package/dist/components/MessageInput/StopAIGenerationButton.d.ts +3 -0
  25. package/dist/components/MessageInput/StopAIGenerationButton.js +6 -0
  26. package/dist/components/index.d.ts +1 -0
  27. package/dist/components/index.js +1 -0
  28. package/dist/context/ComponentContext.d.ts +4 -1
  29. package/dist/css/v2/index.css +2 -2
  30. package/dist/css/v2/index.layout.css +2 -2
  31. package/dist/experimental/index.browser.cjs.map +2 -2
  32. package/dist/experimental/index.node.cjs.map +2 -2
  33. package/dist/i18n/Streami18n.d.ts +3 -0
  34. package/dist/i18n/de.json +3 -0
  35. package/dist/i18n/en.json +3 -0
  36. package/dist/i18n/es.json +3 -0
  37. package/dist/i18n/fr.json +3 -0
  38. package/dist/i18n/hi.json +3 -0
  39. package/dist/i18n/it.json +3 -0
  40. package/dist/i18n/ja.json +3 -0
  41. package/dist/i18n/ko.json +3 -0
  42. package/dist/i18n/nl.json +3 -0
  43. package/dist/i18n/pt.json +3 -0
  44. package/dist/i18n/ru.json +3 -0
  45. package/dist/i18n/tr.json +3 -0
  46. package/dist/index.browser.cjs +1265 -1081
  47. package/dist/index.browser.cjs.map +4 -4
  48. package/dist/index.node.cjs +1183 -994
  49. package/dist/index.node.cjs.map +4 -4
  50. package/dist/scss/v2/AIStateIndicator/AIStateIndicator-layout.scss +3 -0
  51. package/dist/scss/v2/AIStateIndicator/AIStateIndicator-theme.scss +7 -0
  52. package/dist/scss/v2/MessageInput/MessageInput-layout.scss +7 -1
  53. package/dist/scss/v2/MessageInput/MessageInput-theme.scss +9 -3
  54. package/dist/scss/v2/_icons.scss +1 -0
  55. package/dist/scss/v2/index.layout.scss +1 -0
  56. package/dist/scss/v2/index.scss +1 -0
  57. package/package.json +5 -5
@@ -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
  ]);
@@ -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 latestMessage.ai_generated
51
+ ? previewTextToRender
52
+ : renderPreviewText(previewTextToRender);
51
53
  }
52
54
  if (latestMessage.command) {
53
55
  return `/${latestMessage.command}`;
@@ -27,8 +27,8 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
27
27
  const userAgent = client.getUserAgent();
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
- // __STREAM_CHAT_REACT_VERSION__ is replaced during the build process with the current version
31
- client.setUserAgent(`stream-chat-react-__STREAM_CHAT_REACT_VERSION__-${userAgent}`);
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.7.0-${userAgent}`);
32
32
  }
33
33
  client.threads.registerSubscriptions();
34
34
  client.polls.registerSubscriptions();
@@ -22,6 +22,7 @@ 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
27
  const { additionalMessageInputProps, clearEditingState, editing, endOfGroup, firstOfGroup, groupedByUser, handleAction, handleOpenThread, handleRetry, highlighted, isMyMessage, message, onUserClick, onUserHover, renderText, threadList, } = props;
27
28
  const { client } = useChatContext('MessageSimple');
@@ -31,7 +32,7 @@ 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);
37
38
  if (message.customType === CUSTOM_MESSAGE_TYPE.date) {
@@ -84,7 +85,7 @@ const MessageSimpleWithContext = (props) => {
84
85
  React.createElement("div", { className: 'str-chat__message-bubble' },
85
86
  poll && React.createElement(Poll, { poll: poll }),
86
87
  message.attachments?.length && !message.quoted_message ? (React.createElement(Attachment, { actionHandler: handleAction, attachments: message.attachments })) : null,
87
- React.createElement(MessageText, { message: message, renderText: renderText }),
88
+ message.ai_generated ? (React.createElement(StreamedMessageText, { message: message, renderText: renderText })) : (React.createElement(MessageText, { message: message, renderText: renderText })),
88
89
  message.mml && (React.createElement(MML, { actionHandler: handleAction, align: isMyMessage() ? 'right' : 'left', source: message.mml })),
89
90
  React.createElement(MessageErrorIcon, null))),
90
91
  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',
@@ -77,4 +77,4 @@ export declare const mapToUserNameOrId: TooltipUsernameMapper;
77
77
  export declare const getReadByTooltipText: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(users: UserResponse<StreamChatGenerics>[], t: TFunction, client: StreamChat<StreamChatGenerics>, tooltipUserNameMapper: TooltipUsernameMapper) => string;
78
78
  export declare const isOnlyEmojis: (text?: string) => boolean;
79
79
  export declare const isMessageBounced: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(message: Pick<StreamMessage<StreamChatGenerics>, 'type' | 'moderation' | 'moderation_details'>) => boolean;
80
- export declare const isMessageEdited: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(message: Pick<StreamMessage<StreamChatGenerics>, 'message_text_updated_at'>) => boolean;
80
+ export declare const isMessageEdited: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(message: Pick<StreamMessage<StreamChatGenerics>, 'message_text_updated_at'> & Partial<Pick<StreamMessage<StreamChatGenerics>, 'ai_generated'>>) => boolean;
@@ -310,4 +310,4 @@ export const isOnlyEmojis = (text) => {
310
310
  export const isMessageBounced = (message) => message.type === 'error' &&
311
311
  (message.moderation_details?.action === 'MESSAGE_RESPONSE_ACTION_BOUNCE' ||
312
312
  message.moderation?.action === 'bounce');
313
- export const isMessageEdited = (message) => !!message.message_text_updated_at;
313
+ export const isMessageEdited = (message) => !!message.message_text_updated_at && !message.ai_generated;
@@ -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';
@@ -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) */