Skip to content

Commit 5e01b57

Browse files
committed
feat: add stop generating functionality as well
1 parent b971c5b commit 5e01b57

File tree

5 files changed

+71
-33
lines changed

5 files changed

+71
-33
lines changed

examples/vite/src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {
1414
ThreadList,
1515
ChatView,
1616
} from 'stream-chat-react';
17-
import 'stream-chat-react/css/v2/index.css';
17+
// import 'stream-chat-react/css/v2/index.css';
18+
import '@stream-io/stream-chat-css/dist/v2/css/index.css';
1819

1920
const params = (new Proxy(new URLSearchParams(window.location.search), {
2021
get: (searchParams, property) => searchParams.get(property as string),

src/components/AITypingIndicatorView/hooks/useAIState.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@ export const AIStates = {
2323
export const useAIState = <
2424
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
2525
>(
26-
channel: Channel<StreamChatGenerics>,
26+
channel?: Channel<StreamChatGenerics>,
2727
) => {
2828
const [aiState, setAiState] = useState<AIStateType>(AIStates.Idle);
2929

3030
useEffect(() => {
31+
if (!channel) {
32+
return;
33+
}
34+
3135
const indicatorChangedListener = channel.on(
3236
// @ts-ignore
3337
'ai_indicator_changed',

src/components/MessageInput/MessageInputFlat.tsx

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { AttachmentPreviewList as DefaultAttachmentPreviewList } from './AttachmentPreviewList';
1010
import { CooldownTimer as DefaultCooldownTimer } from './CooldownTimer';
1111
import { SendButton as DefaultSendButton } from './SendButton';
12+
import { StopGeneratingButton as DefaultStopGeneratingButton } from './StopGeneratingButton';
1213
import {
1314
AudioRecorder as DefaultAudioRecorder,
1415
RecordingPermissionDeniedNotification as DefaultRecordingPermissionDeniedNotification,
@@ -32,6 +33,7 @@ import { useMessageInputContext } from '../../context/MessageInputContext';
3233
import { useComponentContext } from '../../context/ComponentContext';
3334

3435
import type { DefaultStreamChatGenerics } from '../../types/types';
36+
import { AIStates, useAIState } from '../AITypingIndicatorView';
3537

3638
export const MessageInputFlat = <
3739
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
@@ -66,6 +68,7 @@ export const MessageInputFlat = <
6668
RecordingPermissionDeniedNotification = DefaultRecordingPermissionDeniedNotification,
6769
SendButton = DefaultSendButton,
6870
StartRecordingAudioButton = DefaultStartRecordingAudioButton,
71+
StopGeneratingButton = DefaultStopGeneratingButton,
6972
EmojiPicker,
7073
} = useComponentContext<StreamChatGenerics>('MessageInputFlat');
7174
const {
@@ -76,6 +79,13 @@ export const MessageInputFlat = <
7679
const { setQuotedMessage } = useChannelActionContext('MessageInputFlat');
7780
const { channel } = useChatContext<StreamChatGenerics>('MessageInputFlat');
7881

82+
const { aiState } = useAIState(channel);
83+
84+
// @ts-ignore
85+
const stopGenerating = useCallback(() => channel?.sendEvent({ type: 'stop_generating' }), [
86+
channel,
87+
]);
88+
7989
const [
8090
showRecordingPermissionDeniedNotification,
8191
setShowRecordingPermissionDeniedNotification,
@@ -133,6 +143,8 @@ export const MessageInputFlat = <
133143
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
134144
const isRecording = !!recordingController.recordingState;
135145

146+
const shouldDisplayStopAIGeneration = [AIStates.Thinking, AIStates.Generating].includes(aiState);
147+
136148
return (
137149
<>
138150
<div {...getRootProps({ className: 'str-chat__message-input' })}>
@@ -174,41 +186,45 @@ export const MessageInputFlat = <
174186
{EmojiPicker && <EmojiPicker />}
175187
</div>
176188
</div>
177-
{!hideSendButton && (
178-
<>
179-
{cooldownRemaining ? (
180-
<CooldownTimer
181-
cooldownInterval={cooldownRemaining}
182-
setCooldownRemaining={setCooldownRemaining}
183-
/>
184-
) : (
185-
<>
186-
<SendButton
187-
disabled={
188-
!numberOfUploads &&
189-
!text.length &&
190-
attachments.length - failedUploadsCount === 0
191-
}
192-
sendMessage={handleSubmit}
189+
{shouldDisplayStopAIGeneration ? (
190+
<StopGeneratingButton onClick={stopGenerating} />
191+
) : (
192+
!hideSendButton && (
193+
<>
194+
{cooldownRemaining ? (
195+
<CooldownTimer
196+
cooldownInterval={cooldownRemaining}
197+
setCooldownRemaining={setCooldownRemaining}
193198
/>
194-
{recordingEnabled && (
195-
<StartRecordingAudioButton
199+
) : (
200+
<>
201+
<SendButton
196202
disabled={
197-
isRecording ||
198-
(!asyncMessagesMultiSendEnabled &&
199-
attachments.some(
200-
(a) => a.type === RecordingAttachmentType.VOICE_RECORDING,
201-
))
203+
!numberOfUploads &&
204+
!text.length &&
205+
attachments.length - failedUploadsCount === 0
202206
}
203-
onClick={() => {
204-
recordingController.recorder?.start();
205-
setShowRecordingPermissionDeniedNotification(true);
206-
}}
207+
sendMessage={handleSubmit}
207208
/>
208-
)}
209-
</>
210-
)}
211-
</>
209+
{recordingEnabled && (
210+
<StartRecordingAudioButton
211+
disabled={
212+
isRecording ||
213+
(!asyncMessagesMultiSendEnabled &&
214+
attachments.some(
215+
(a) => a.type === RecordingAttachmentType.VOICE_RECORDING,
216+
))
217+
}
218+
onClick={() => {
219+
recordingController.recorder?.start();
220+
setShowRecordingPermissionDeniedNotification(true);
221+
}}
222+
/>
223+
)}
224+
</>
225+
)}
226+
</>
227+
)
212228
)}
213229
</div>
214230
</div>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react';
2+
3+
export type StopGeneratingButtonProps = {
4+
onClick: () => void;
5+
} & React.ComponentProps<'button'>;
6+
7+
export const StopGeneratingButton = ({ onClick }: StopGeneratingButtonProps) => (
8+
<button
9+
aria-label='Send'
10+
className='str-chat__stop-generating-button'
11+
data-testid='send-button'
12+
onClick={onClick}
13+
type='button'
14+
/>
15+
);

src/context/ComponentContext.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import type {
5454
PropsWithChildrenOnly,
5555
UnknownType,
5656
} from '../types/types';
57+
import { StopGeneratingButtonProps } from '../components/MessageInput/StopGeneratingButton';
5758

5859
export type ComponentContextValue<
5960
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
@@ -164,6 +165,7 @@ export type ComponentContextValue<
164165
SendButton?: React.ComponentType<SendButtonProps<StreamChatGenerics>>;
165166
/** 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) */
166167
StartRecordingAudioButton?: React.ComponentType<StartRecordingAudioButtonProps>;
168+
StopGeneratingButton?: React.ComponentType<StopGeneratingButtonProps>;
167169
/** 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) */
168170
ThreadHead?: React.ComponentType<MessageProps<StreamChatGenerics>>;
169171
/** 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) */

0 commit comments

Comments
 (0)