Skip to content

Commit 4fbc2f4

Browse files
authored
feat: ai chat bot poc (#2822)
* feat: ai-bot integration poc (#2819) * feat: add StreamingMessageView to kick off ai feature * fix: issues with message view * feat: add AITypingIndicatorView * feat: make send message button react to ai state * fix: improve typewriter animation * fix: improvements in ui and typewriter * chore: add customizations to StreamingMessageView * fix: hook deps * chore: extract logic in hook * fix: custom events * fix: revert the type change in favor of changes in the LLC * feat: codeblock scrollable view * feat: table initial reimpl * feat: finish table impl * fix: horizontal scroll list performance issues * feat: add markdown parsing fixes, optimistic code capture and various improvements * fix: theme prop and theming in general * fix: remove edited lalbel for ai messages * fix: bug with stop streaming button and types * fix: colors in md rendering * fix: rename custom scrollview * chore: translations * fix: remove TODO * chore: extract indicator styles in theme * fix: safeguard if channel does not exist * fix: get rid of enum and introduce proper type * fix: allow only message overrides * chore: update event names as per the changes * fix: bump stream-chat-js version to v8.46.0 * fix: use channel method for sending events * fix: cover background mode case * fix: use type from LLC * chore: add jsdocs * fix: move check to checker fn * fix: add overrides for StreamingMessageView * chore: add override for stop streaming button * fix: scrollviews breaking message * chore: bump stream-chat version * fix: message overlay * fix: linter issues
1 parent 1651c16 commit 4fbc2f4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+636
-39
lines changed

examples/SampleApp/src/hooks/useStreamChatTheme.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const getChatStyle = (colorScheme: ColorSchemeName): DeepPartial<Theme> => ({
1717
border: '#141924',
1818
button_background: '#FFFFFF',
1919
button_text: '#005FFF',
20+
code_block: '#222222',
2021
grey: '#7A7A7A',
2122
grey_gainsboro: '#2D2F2F',
2223
grey_whisper: '#1C1E22',

examples/SampleApp/src/screens/ChannelScreen.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
useChatContext,
1313
useTheme,
1414
useTypingString,
15+
AITypingIndicatorView,
1516
} from 'stream-chat-react-native';
1617
import { Platform, StyleSheet, View } from 'react-native';
1718
import type { StackNavigationProp } from '@react-navigation/stack';
@@ -168,6 +169,7 @@ export const ChannelScreen: React.FC<ChannelScreenProps> = ({
168169
});
169170
}}
170171
/>
172+
<AITypingIndicatorView channel={channel} />
171173
<MessageInput />
172174
</Channel>
173175
</View>

examples/SampleApp/yarn.lock

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6837,10 +6837,10 @@ statuses@~1.5.0:
68376837
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
68386838
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
68396839

6840-
6841-
version "5.43.1"
6842-
resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.43.1.tgz#ec0d5a06e329c8991c46cff5bd0211bc94d2b26f"
6843-
integrity sha512-qj/WfjFeCCP2wcp1YZGFJgRYGdSWXd0maG3hn3oURgFR6p/BmO6lDL2g3jnLq0SEkD8x+KZeNBS9cs1gW5Gblw==
6840+
6841+
version "5.43.2"
6842+
resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.43.2.tgz#b16add60d231509f864d7301ae281c4b4681ff7a"
6843+
integrity sha512-pdSaqw1aPHtxH0md7nnw/TLWPIqsb5TKFzGoWYd/cKRAw4pVDvxolSlha8vYSCwz7MmlarfsJPB0TlS9WcGsDQ==
68446844
dependencies:
68456845
"@gorhom/bottom-sheet" "^4.6.4"
68466846
dayjs "1.10.5"
@@ -6878,6 +6878,21 @@ [email protected]:
68786878
jsonwebtoken "~9.0.0"
68796879
ws "^7.5.10"
68806880

6881+
6882+
version "8.46.0"
6883+
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.46.0.tgz#416b325e05b144d0937a3527d1e622463113d605"
6884+
integrity sha512-HQVCRVldrfQFAvsBOHiHR0TKYf+wpsg/cAzRojeZY+buy1vG6eoqk09h6Fl4k2eG3zFLoA0G9W6o7o45jyFE1g==
6885+
dependencies:
6886+
"@babel/runtime" "^7.16.3"
6887+
"@types/jsonwebtoken" "~9.0.0"
6888+
"@types/ws" "^7.4.0"
6889+
axios "^1.6.0"
6890+
base64-js "^1.5.1"
6891+
form-data "^4.0.0"
6892+
isomorphic-ws "^4.0.1"
6893+
jsonwebtoken "~9.0.0"
6894+
ws "^7.5.10"
6895+
68816896
strict-uri-encode@^2.0.0:
68826897
version "2.0.0"
68836898
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"

package/expo-package/yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2929,10 +2929,10 @@ [email protected]:
29292929
resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4"
29302930
integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==
29312931

2932-
2933-
version "5.43.1"
2934-
resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.43.1.tgz#ec0d5a06e329c8991c46cff5bd0211bc94d2b26f"
2935-
integrity sha512-qj/WfjFeCCP2wcp1YZGFJgRYGdSWXd0maG3hn3oURgFR6p/BmO6lDL2g3jnLq0SEkD8x+KZeNBS9cs1gW5Gblw==
2932+
2933+
version "5.43.2"
2934+
resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.43.2.tgz#b16add60d231509f864d7301ae281c4b4681ff7a"
2935+
integrity sha512-pdSaqw1aPHtxH0md7nnw/TLWPIqsb5TKFzGoWYd/cKRAw4pVDvxolSlha8vYSCwz7MmlarfsJPB0TlS9WcGsDQ==
29362936
dependencies:
29372937
"@gorhom/bottom-sheet" "^4.6.4"
29382938
dayjs "1.10.5"

package/native-package/yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4244,10 +4244,10 @@ statuses@~1.5.0:
42444244
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
42454245
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
42464246

4247-
4248-
version "5.43.1"
4249-
resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.43.1.tgz#ec0d5a06e329c8991c46cff5bd0211bc94d2b26f"
4250-
integrity sha512-qj/WfjFeCCP2wcp1YZGFJgRYGdSWXd0maG3hn3oURgFR6p/BmO6lDL2g3jnLq0SEkD8x+KZeNBS9cs1gW5Gblw==
4247+
4248+
version "5.43.2"
4249+
resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.43.2.tgz#b16add60d231509f864d7301ae281c4b4681ff7a"
4250+
integrity sha512-pdSaqw1aPHtxH0md7nnw/TLWPIqsb5TKFzGoWYd/cKRAw4pVDvxolSlha8vYSCwz7MmlarfsJPB0TlS9WcGsDQ==
42514251
dependencies:
42524252
"@gorhom/bottom-sheet" "^4.6.4"
42534253
dayjs "1.10.5"

package/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
"path": "0.12.7",
7979
"react-native-markdown-package": "1.8.2",
8080
"react-native-url-polyfill": "^1.3.0",
81-
"stream-chat": "8.45.1"
81+
"stream-chat": "8.46.0"
8282
},
8383
"peerDependencies": {
8484
"react-native-quick-sqlite": ">=5.1.0",
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from 'react';
2+
3+
import { StyleSheet, Text, View } from 'react-native';
4+
5+
import { Channel } from 'stream-chat';
6+
7+
import { AIStates, useAIState } from './hooks/useAIState';
8+
9+
import { useChannelContext, useTheme, useTranslationContext } from '../../contexts';
10+
import type { DefaultStreamChatGenerics } from '../../types/types';
11+
12+
export type AITypingIndicatorViewProps<
13+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
14+
> = {
15+
channel?: Channel<StreamChatGenerics>;
16+
};
17+
18+
export const AITypingIndicatorView = <
19+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
20+
>({
21+
channel: channelFromProps,
22+
}: AITypingIndicatorViewProps<StreamChatGenerics>) => {
23+
const { t } = useTranslationContext();
24+
const { channel: channelFromContext } = useChannelContext<StreamChatGenerics>();
25+
const channel = channelFromProps || channelFromContext;
26+
const { aiState } = useAIState(channel);
27+
const allowedStates = {
28+
[AIStates.Thinking]: t('Thinking...'),
29+
[AIStates.Generating]: t('Generating...'),
30+
};
31+
32+
const {
33+
theme: {
34+
aiTypingIndicatorView: { container, text },
35+
colors: { black, grey_gainsboro },
36+
},
37+
} = useTheme();
38+
39+
return aiState in allowedStates ? (
40+
<View style={[styles.container, { backgroundColor: grey_gainsboro }, container]}>
41+
<Text style={[{ color: black }, text]}>{allowedStates[aiState]}</Text>
42+
</View>
43+
) : null;
44+
};
45+
46+
AITypingIndicatorView.displayName = 'AITypingIndicatorView{messageSimple{content}}';
47+
48+
const styles = StyleSheet.create({
49+
container: { paddingHorizontal: 16, paddingVertical: 18 },
50+
});
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { useEffect, useState } from 'react';
2+
3+
import { AIState, Channel, Event } from 'stream-chat';
4+
5+
import { useChatContext } from '../../../contexts';
6+
import type { DefaultStreamChatGenerics } from '../../../types/types';
7+
import { useIsOnline } from '../../Chat/hooks/useIsOnline';
8+
9+
export const AIStates = {
10+
Error: 'AI_STATE_ERROR',
11+
ExternalSources: 'AI_STATE_EXTERNAL_SOURCES',
12+
Generating: 'AI_STATE_GENERATING',
13+
Idle: 'AI_STATE_IDLE',
14+
Thinking: 'AI_STATE_THINKING',
15+
};
16+
17+
/**
18+
* A hook that returns the current state of the AI.
19+
* @param {Channel} channel - The channel for which we want to know the AI state.
20+
* @returns {{ aiState: AIState }} The current AI state for the given channel.
21+
*/
22+
export const useAIState = <
23+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
24+
>(
25+
channel?: Channel<StreamChatGenerics>,
26+
): { aiState: AIState } => {
27+
const { client } = useChatContext<StreamChatGenerics>();
28+
const { isOnline } = useIsOnline<StreamChatGenerics>(client);
29+
30+
const [aiState, setAiState] = useState<AIState>(AIStates.Idle);
31+
32+
useEffect(() => {
33+
if (!isOnline) {
34+
setAiState(AIStates.Idle);
35+
}
36+
}, [isOnline]);
37+
38+
useEffect(() => {
39+
if (!channel) {
40+
return;
41+
}
42+
43+
const indicatorChangedListener = channel.on(
44+
'ai_indicator.update',
45+
(event: Event<StreamChatGenerics>) => {
46+
const { cid } = event;
47+
const state = event.ai_state as AIState;
48+
if (channel.cid === cid) {
49+
setAiState(state);
50+
}
51+
},
52+
);
53+
54+
const indicatorClearedListener = channel.on('ai_indicator.clear', (event) => {
55+
const { cid } = event;
56+
if (channel.cid === cid) {
57+
setAiState(AIStates.Idle);
58+
}
59+
});
60+
61+
return () => {
62+
indicatorChangedListener.unsubscribe();
63+
indicatorClearedListener.unsubscribe();
64+
};
65+
}, [channel]);
66+
67+
return { aiState };
68+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './AITypingIndicatorView';
2+
export * from './hooks/useAIState';

package/src/components/Channel/Channel.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ import { MessageSimple as MessageSimpleDefault } from '../Message/MessageSimple/
135135
import { MessageStatus as MessageStatusDefault } from '../Message/MessageSimple/MessageStatus';
136136
import { MessageTimestamp as MessageTimestampDefault } from '../Message/MessageSimple/MessageTimestamp';
137137
import { ReactionList as ReactionListDefault } from '../Message/MessageSimple/ReactionList';
138+
import { StreamingMessageView as DefaultStreamingMessageView } from '../Message/MessageSimple/StreamingMessageView';
138139
import { AttachButton as AttachButtonDefault } from '../MessageInput/AttachButton';
139140
import { CommandsButton as CommandsButtonDefault } from '../MessageInput/CommandsButton';
140141
import { AudioRecorder as AudioRecorderDefault } from '../MessageInput/components/AudioRecorder/AudioRecorder';
@@ -154,6 +155,7 @@ import { MoreOptionsButton as MoreOptionsButtonDefault } from '../MessageInput/M
154155
import { SendButton as SendButtonDefault } from '../MessageInput/SendButton';
155156
import { SendMessageDisallowedIndicator as SendMessageDisallowedIndicatorDefault } from '../MessageInput/SendMessageDisallowedIndicator';
156157
import { ShowThreadMessageInChannelButton as ShowThreadMessageInChannelButtonDefault } from '../MessageInput/ShowThreadMessageInChannelButton';
158+
import { StopMessageStreamingButton as DefaultStopMessageStreamingButton } from '../MessageInput/StopMessageStreamingButton';
157159
import { UploadProgressIndicator as UploadProgressIndicatorDefault } from '../MessageInput/UploadProgressIndicator';
158160
import { DateHeader as DateHeaderDefault } from '../MessageList/DateHeader';
159161
import type { MessageType } from '../MessageList/hooks/useMessageList';
@@ -333,6 +335,7 @@ export type ChannelPropsWithContext<
333335
| 'VideoThumbnail'
334336
| 'PollContent'
335337
| 'hasCreatePoll'
338+
| 'StreamingMessageView'
336339
>
337340
> &
338341
Partial<Pick<ThreadContextValue<StreamChatGenerics>, 'allowThreadMessagesInChannel'>> & {
@@ -420,7 +423,12 @@ export type ChannelPropsWithContext<
420423
* Tells if channel is rendering a thread list
421424
*/
422425
threadList?: boolean;
423-
} & Partial<Pick<InputMessageInputContextValue, 'openPollCreationDialog' | 'CreatePollContent'>>;
426+
} & Partial<
427+
Pick<
428+
InputMessageInputContextValue,
429+
'openPollCreationDialog' | 'CreatePollContent' | 'StopMessageStreamingButton'
430+
>
431+
>;
424432

425433
const ChannelWithContext = <
426434
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
@@ -544,7 +552,15 @@ const ChannelWithContext = <
544552
MessageAvatar = MessageAvatarDefault,
545553
MessageBounce = MessageBounceDefault,
546554
MessageContent = MessageContentDefault,
547-
messageContentOrder = ['quoted_reply', 'gallery', 'files', 'poll', 'text', 'attachments'],
555+
messageContentOrder = [
556+
'quoted_reply',
557+
'gallery',
558+
'files',
559+
'poll',
560+
'ai_text',
561+
'text',
562+
'attachments',
563+
],
548564
MessageDeleted = MessageDeletedDefault,
549565
MessageEditedTimestamp = MessageEditedTimestampDefault,
550566
MessageError = MessageErrorDefault,
@@ -596,6 +612,8 @@ const ChannelWithContext = <
596612
StartAudioRecordingButton = AudioRecordingButtonDefault,
597613
stateUpdateThrottleInterval = defaultThrottleInterval,
598614
StickyHeader = StickyHeaderDefault,
615+
StopMessageStreamingButton: StopMessageStreamingButtonOverride,
616+
StreamingMessageView = DefaultStreamingMessageView,
599617
supportedReactions = reactionData,
600618
t,
601619
thread: threadFromProps,
@@ -612,6 +630,10 @@ const ChannelWithContext = <
612630
} = props;
613631

614632
const { thread: threadProps, threadInstance } = threadFromProps;
633+
const StopMessageStreamingButton =
634+
StopMessageStreamingButtonOverride === undefined
635+
? DefaultStopMessageStreamingButton
636+
: StopMessageStreamingButtonOverride;
615637

616638
const {
617639
theme: {
@@ -2338,6 +2360,7 @@ const ChannelWithContext = <
23382360
setQuotedMessageState,
23392361
ShowThreadMessageInChannelButton,
23402362
StartAudioRecordingButton,
2363+
StopMessageStreamingButton,
23412364
UploadProgressIndicator,
23422365
});
23432366

@@ -2439,6 +2462,7 @@ const ChannelWithContext = <
24392462
setEditingState,
24402463
setQuotedMessageState,
24412464
shouldShowUnreadUnderlay,
2465+
StreamingMessageView,
24422466
supportedReactions,
24432467
targetedMessage,
24442468
TypingIndicator,

0 commit comments

Comments
 (0)