Skip to content

Commit 26de263

Browse files
committed
feat: implement StreamingMessageView and add hook
1 parent db95e34 commit 26de263

File tree

8 files changed

+119
-1458
lines changed

8 files changed

+119
-1458
lines changed

examples/vite/src/index.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ body,
3030
flex-shrink: 0;
3131
box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
3232

33+
.str-chat__channel-preview-messenger {
34+
max-width: 1000px;
35+
}
36+
3337
&--open {
3438
width: 30%;
3539
position: fixed;

examples/vite/yarn.lock

Lines changed: 17 additions & 1456 deletions
Large diffs are not rendered by default.

src/components/ChannelPreview/utils.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ export const getLatestMessagePreview = <
7777
}
7878

7979
if (previewTextToRender) {
80-
return renderPreviewText(previewTextToRender);
80+
return latestMessage.ai_generated
81+
? previewTextToRender
82+
: renderPreviewText(previewTextToRender);
8183
}
8284

8385
if (latestMessage.command) {

src/components/Message/MessageSimple.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { MessageEditedTimestamp } from './MessageEditedTimestamp';
3434

3535
import type { MessageUIComponentProps } from './types';
3636
import type { DefaultStreamChatGenerics } from '../../types/types';
37+
import { StreamingMessageView } from './StreamingMessageView';
3738

3839
type MessageSimpleWithContextProps<
3940
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
@@ -185,7 +186,11 @@ const MessageSimpleWithContext = <
185186
{message.attachments?.length && !message.quoted_message ? (
186187
<Attachment actionHandler={handleAction} attachments={message.attachments} />
187188
) : null}
188-
<MessageText message={message} renderText={renderText} />
189+
{message.ai_generated ? (
190+
<StreamingMessageView message={message} renderText={renderText} />
191+
) : (
192+
<MessageText message={message} renderText={renderText} />
193+
)}
189194
{message.mml && (
190195
<MML
191196
actionHandler={handleAction}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
3+
import { MessageText, MessageTextProps } from './MessageText';
4+
import type { DefaultStreamChatGenerics } from '../../types/types';
5+
import { useMessageContext } from '../../context';
6+
import { useStreamingMessage } from './hooks';
7+
8+
export type StreamingMessageViewProps<
9+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
10+
> = Pick<MessageTextProps<StreamChatGenerics>, 'message' | 'renderText'> & {
11+
letterInterval?: number;
12+
renderingLetterCount?: number;
13+
};
14+
15+
export const StreamingMessageView = <
16+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
17+
>(
18+
props: StreamingMessageViewProps<StreamChatGenerics>,
19+
) => {
20+
const { letterInterval, message: messageFromProps, renderingLetterCount, renderText } = props;
21+
const { message: messageFromContext } = useMessageContext<StreamChatGenerics>('MessageText');
22+
const message = messageFromProps || messageFromContext;
23+
const { text = '' } = message;
24+
const { streamedMessageText } = useStreamingMessage({
25+
letterInterval,
26+
renderingLetterCount,
27+
text,
28+
});
29+
30+
return (
31+
<MessageText message={{ ...message, text: streamedMessageText }} renderText={renderText} />
32+
);
33+
};
34+
35+
StreamingMessageView.displayName = 'StreamingMessageView{messageSimple{content}}';

src/components/Message/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export * from './useRetryHandler';
1212
export * from './useUserHandler';
1313
export * from './useUserRole';
1414
export * from './useReactionsFetcher';
15+
export * from './useStreamingMessage';
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
3+
import type { DefaultStreamChatGenerics } from '../../../types/types';
4+
import { StreamingMessageViewProps } from '../StreamingMessageView';
5+
6+
export type UseStreamingMessageProps<
7+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
8+
> = Pick<
9+
StreamingMessageViewProps<StreamChatGenerics>,
10+
'letterInterval' | 'renderingLetterCount'
11+
> & { text: string };
12+
13+
const DEFAULT_LETTER_INTERVAL = 30;
14+
const DEFAULT_RENDERING_LETTER_COUNT = 2;
15+
16+
export const useStreamingMessage = <
17+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
18+
>({
19+
letterInterval = DEFAULT_LETTER_INTERVAL,
20+
renderingLetterCount = DEFAULT_RENDERING_LETTER_COUNT,
21+
text,
22+
}: UseStreamingMessageProps<StreamChatGenerics>) => {
23+
const [streamedMessageText, setStreamedMessageText] = useState<string>(text);
24+
const textCursor = useRef<number>(text.length);
25+
26+
useEffect(() => {
27+
const textLength = text.length;
28+
const interval = setInterval(() => {
29+
if (!text || textCursor.current >= textLength) {
30+
clearInterval(interval);
31+
}
32+
const newCursorValue = textCursor.current + renderingLetterCount;
33+
const newText = text.substring(0, newCursorValue);
34+
textCursor.current += newText.length - textCursor.current;
35+
const codeBlockCounts = (newText.match(/```/g) || []).length;
36+
const shouldOptimisticallyCloseCodeBlock = codeBlockCounts > 0 && codeBlockCounts % 2 > 0;
37+
setStreamedMessageText(shouldOptimisticallyCloseCodeBlock ? newText + '```' : newText);
38+
}, letterInterval);
39+
40+
return () => {
41+
clearInterval(interval);
42+
};
43+
}, [letterInterval, renderingLetterCount, text]);
44+
45+
return { streamedMessageText };
46+
};

src/components/Message/renderText/renderText.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ export const defaultAllowedTagNames: Array<keyof JSX.IntrinsicElements | 'emoji'
3535
// custom types (tagNames)
3636
'emoji',
3737
'mention',
38+
'table',
39+
'thead',
40+
'tbody',
41+
'th',
42+
'tr',
43+
'td',
44+
'tfoot',
3845
];
3946

4047
function formatUrlForDisplay(url: string) {

0 commit comments

Comments
 (0)