-
Notifications
You must be signed in to change notification settings - Fork 281
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for ai generated messages (#2570)
- Loading branch information
1 parent
36c34cb
commit fb1bfdd
Showing
32 changed files
with
342 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import React from 'react'; | ||
|
||
import { Channel } from 'stream-chat'; | ||
|
||
import { AIStates, useAIState } from './hooks/useAIState'; | ||
|
||
import { useChannelStateContext, useTranslationContext } from '../../context'; | ||
import type { DefaultStreamChatGenerics } from '../../types/types'; | ||
|
||
export type AIStateIndicatorProps< | ||
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics | ||
> = { | ||
channel?: Channel<StreamChatGenerics>; | ||
}; | ||
|
||
export const AIStateIndicator = < | ||
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics | ||
>({ | ||
channel: channelFromProps, | ||
}: AIStateIndicatorProps<StreamChatGenerics>) => { | ||
const { t } = useTranslationContext(); | ||
const { channel: channelFromContext } = useChannelStateContext<StreamChatGenerics>( | ||
'AIStateIndicator', | ||
); | ||
const channel = channelFromProps || channelFromContext; | ||
const { aiState } = useAIState(channel); | ||
const allowedStates = { | ||
[AIStates.Thinking]: t('Thinking...'), | ||
[AIStates.Generating]: t('Generating...'), | ||
}; | ||
|
||
return aiState in allowedStates ? ( | ||
<div className='str-chat__ai-state-indicator-container'> | ||
<p className='str-chat__ai-state-indicator-text'>{allowedStates[aiState]}</p> | ||
</div> | ||
) : null; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { useEffect, useState } from 'react'; | ||
|
||
import { AIState, Channel, Event } from 'stream-chat'; | ||
|
||
import type { DefaultStreamChatGenerics } from '../../../types/types'; | ||
|
||
export const AIStates = { | ||
Error: 'AI_STATE_ERROR', | ||
ExternalSources: 'AI_STATE_EXTERNAL_SOURCES', | ||
Generating: 'AI_STATE_GENERATING', | ||
Idle: 'AI_STATE_IDLE', | ||
Thinking: 'AI_STATE_THINKING', | ||
}; | ||
|
||
/** | ||
* A hook that returns the current state of the AI. | ||
* @param {Channel} channel - The channel for which we want to know the AI state. | ||
* @returns {{ aiState: AIState }} The current AI state for the given channel. | ||
*/ | ||
export const useAIState = < | ||
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics | ||
>( | ||
channel?: Channel<StreamChatGenerics>, | ||
): { aiState: AIState } => { | ||
const [aiState, setAiState] = useState<AIState>(AIStates.Idle); | ||
|
||
useEffect(() => { | ||
if (!channel) { | ||
return; | ||
} | ||
|
||
const indicatorChangedListener = channel.on( | ||
'ai_indicator.update', | ||
(event: Event<StreamChatGenerics>) => { | ||
const { cid } = event; | ||
const state = event.ai_state as AIState; | ||
if (channel.cid === cid) { | ||
setAiState(state); | ||
} | ||
}, | ||
); | ||
|
||
const indicatorClearedListener = channel.on('ai_indicator.clear', (event) => { | ||
const { cid } = event; | ||
if (channel.cid === cid) { | ||
setAiState(AIStates.Idle); | ||
} | ||
}); | ||
|
||
return () => { | ||
indicatorChangedListener.unsubscribe(); | ||
indicatorClearedListener.unsubscribe(); | ||
}; | ||
}, [channel]); | ||
|
||
return { aiState }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './AIStateIndicator'; | ||
export * from './hooks/useAIState'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import React from 'react'; | ||
|
||
import { MessageText, MessageTextProps } from './MessageText'; | ||
import type { DefaultStreamChatGenerics } from '../../types/types'; | ||
import { useMessageContext } from '../../context'; | ||
import { useMessageTextStreaming } from './hooks'; | ||
|
||
export type StreamedMessageTextProps< | ||
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics | ||
> = Pick<MessageTextProps<StreamChatGenerics>, 'message' | 'renderText'> & { | ||
renderingLetterCount?: number; | ||
streamingLetterIntervalMs?: number; | ||
}; | ||
|
||
export const StreamedMessageText = < | ||
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics | ||
>( | ||
props: StreamedMessageTextProps<StreamChatGenerics>, | ||
) => { | ||
const { | ||
message: messageFromProps, | ||
renderingLetterCount, | ||
renderText, | ||
streamingLetterIntervalMs, | ||
} = props; | ||
const { message: messageFromContext } = useMessageContext<StreamChatGenerics>( | ||
'StreamedMessageText', | ||
); | ||
const message = messageFromProps || messageFromContext; | ||
const { text = '' } = message; | ||
const { streamedMessageText } = useMessageTextStreaming({ | ||
renderingLetterCount, | ||
streamingLetterIntervalMs, | ||
text, | ||
}); | ||
|
||
return ( | ||
<MessageText message={{ ...message, text: streamedMessageText }} renderText={renderText} /> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { useEffect, useRef, useState } from 'react'; | ||
|
||
import type { DefaultStreamChatGenerics } from '../../../types/types'; | ||
import type { StreamedMessageTextProps } from '../StreamedMessageText'; | ||
|
||
export type UseMessageTextStreamingProps< | ||
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics | ||
> = Pick< | ||
StreamedMessageTextProps<StreamChatGenerics>, | ||
'streamingLetterIntervalMs' | 'renderingLetterCount' | ||
> & { text: string }; | ||
|
||
const DEFAULT_LETTER_INTERVAL = 30; | ||
const DEFAULT_RENDERING_LETTER_COUNT = 2; | ||
|
||
/** | ||
* A hook that returns text in a streamed, typewriter fashion. The speed of streaming is | ||
* configurable. | ||
* @param {number} [streamingLetterIntervalMs=30] - The timeout between each typing animation in milliseconds. | ||
* @param {number} [renderingLetterCount=2] - The number of letters to be rendered each time we update. | ||
* @param {string} text - The text that we want to render in a typewriter fashion. | ||
* @returns {{ streamedMessageText: string }} - A substring of the text property, up until we've finished rendering the typewriter animation. | ||
*/ | ||
export const useMessageTextStreaming = < | ||
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics | ||
>({ | ||
streamingLetterIntervalMs = DEFAULT_LETTER_INTERVAL, | ||
renderingLetterCount = DEFAULT_RENDERING_LETTER_COUNT, | ||
text, | ||
}: UseMessageTextStreamingProps<StreamChatGenerics>): { streamedMessageText: string } => { | ||
const [streamedMessageText, setStreamedMessageText] = useState<string>(text); | ||
const textCursor = useRef<number>(text.length); | ||
|
||
useEffect(() => { | ||
const textLength = text.length; | ||
const interval = setInterval(() => { | ||
if (!text || textCursor.current >= textLength) { | ||
clearInterval(interval); | ||
} | ||
const newCursorValue = textCursor.current + renderingLetterCount; | ||
const newText = text.substring(0, newCursorValue); | ||
textCursor.current += newText.length - textCursor.current; | ||
setStreamedMessageText(newText); | ||
}, streamingLetterIntervalMs); | ||
|
||
return () => { | ||
clearInterval(interval); | ||
}; | ||
}, [streamingLetterIntervalMs, renderingLetterCount, text]); | ||
|
||
return { streamedMessageText }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.