stream-chat-react 12.6.2 → 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.
- package/dist/components/AIStateIndicator/AIStateIndicator.d.ts +7 -0
- package/dist/components/AIStateIndicator/AIStateIndicator.js +15 -0
- package/dist/components/AIStateIndicator/hooks/useAIState.d.ts +17 -0
- package/dist/components/AIStateIndicator/hooks/useAIState.js +39 -0
- package/dist/components/AIStateIndicator/index.d.ts +2 -0
- package/dist/components/AIStateIndicator/index.js +2 -0
- package/dist/components/Channel/Channel.d.ts +1 -1
- package/dist/components/Channel/Channel.js +4 -0
- package/dist/components/ChannelPreview/utils.js +3 -1
- package/dist/components/Chat/hooks/useChat.js +1 -1
- package/dist/components/Message/MessageSimple.js +3 -2
- package/dist/components/Message/StreamedMessageText.d.ts +8 -0
- package/dist/components/Message/StreamedMessageText.js +16 -0
- package/dist/components/Message/hooks/index.d.ts +1 -0
- package/dist/components/Message/hooks/index.js +1 -0
- package/dist/components/Message/hooks/useMessageTextStreaming.d.ts +16 -0
- package/dist/components/Message/hooks/useMessageTextStreaming.js +31 -0
- package/dist/components/Message/index.d.ts +1 -0
- package/dist/components/Message/index.js +1 -0
- package/dist/components/Message/renderText/renderText.js +7 -0
- package/dist/components/Message/utils.d.ts +1 -1
- package/dist/components/Message/utils.js +1 -1
- package/dist/components/MessageInput/MessageInputFlat.js +16 -3
- package/dist/components/MessageInput/StopAIGenerationButton.d.ts +3 -0
- package/dist/components/MessageInput/StopAIGenerationButton.js +6 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/context/ComponentContext.d.ts +4 -1
- package/dist/css/v2/index.css +2 -2
- package/dist/css/v2/index.layout.css +2 -2
- package/dist/experimental/index.browser.cjs.map +2 -2
- package/dist/experimental/index.node.cjs.map +2 -2
- package/dist/i18n/Streami18n.d.ts +3 -0
- package/dist/i18n/de.json +3 -0
- package/dist/i18n/en.json +3 -0
- package/dist/i18n/es.json +3 -0
- package/dist/i18n/fr.json +3 -0
- package/dist/i18n/hi.json +3 -0
- package/dist/i18n/it.json +3 -0
- package/dist/i18n/ja.json +3 -0
- package/dist/i18n/ko.json +3 -0
- package/dist/i18n/nl.json +3 -0
- package/dist/i18n/pt.json +3 -0
- package/dist/i18n/ru.json +3 -0
- package/dist/i18n/tr.json +3 -0
- package/dist/index.browser.cjs +1265 -1081
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +1183 -994
- package/dist/index.node.cjs.map +4 -4
- package/dist/scss/v2/AIStateIndicator/AIStateIndicator-layout.scss +3 -0
- package/dist/scss/v2/AIStateIndicator/AIStateIndicator-theme.scss +7 -0
- package/dist/scss/v2/MessageInput/MessageInput-layout.scss +7 -1
- package/dist/scss/v2/MessageInput/MessageInput-theme.scss +9 -3
- package/dist/scss/v2/_icons.scss +1 -0
- package/dist/scss/v2/index.layout.scss +1 -0
- package/dist/scss/v2/index.scss +1 -0
- 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
|
+
};
|
|
@@ -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
|
|
50
|
+
return latestMessage.ai_generated
|
|
51
|
+
? previewTextToRender
|
|
52
|
+
: renderPreviewText(previewTextToRender);
|
|
51
53
|
}
|
|
52
54
|
if (latestMessage.command) {
|
|
53
55
|
return `/${latestMessage.command}`;
|
|
@@ -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.
|
|
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
|
+
};
|
|
@@ -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
|
+
};
|
|
@@ -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,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';
|
package/dist/components/index.js
CHANGED
|
@@ -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) */
|