const isMessageAIGenerated = (message: LocalMessage) => !!message.ai_generated;SDK Integration
The AI components work seamlessly with the React Chat SDK.
StreamingMessage
Integrating the StreamingMessage within the SDK is relatively easy. The SDK already comes prebuilt with a isMessageAIGenerated factory function that lets the underlying contexts know when we consider a message to be AI generated and renders its internal StreamingMessage whenever this is true. Let us say that a message is considered AI generated if the message.ai_generated property is set to true. In that case, our factory function would look something like this:
Then, we can simply use our new StreamingMessage to like so:
import { StreamingMessage } from "@stream-io/chat-react-ai";
const CustomStreamingMessage = () => {
const { message } = useMessageContext();
return <StreamingMessage text={message.text ?? ""} />;
};
// ...
<Channel {...otherChannelProps} StreamedMessageText={CustomStreamingMessage} />;which will render a message that uses the StreamingMessage from @stream-io/chat-react-ai instead of the default one.
Now since this component is still rendered within our MessageSimple component, there are other components rendered alongside (reactions, read status, etc.) which might not suite your needs. For cleaner look, you can drop most of these by building your own custom message component while preserving default styles:
import {
type MessageUIComponentProps,
useMessageContext,
MessageSimple,
Attachment,
MessageErrorIcon,
Channel,
} from "stream-chat-react";
import { StreamingMessage } from "@stream-io/chat-react-ai";
const CustomMessage = (props: MessageUIComponentProps) => {
const { message, isMyMessage, highlighted, handleAction } =
useMessageContext();
const attachments = message.attachments ?? [];
const hasAttachments = attachments.length > 0;
const rootClassName = clsx(
"str-chat__message str-chat__message-simple",
`str-chat__message--${message.type}`,
`str-chat__message--${message.status}`,
{
"str-chat__message--me": isMyMessage(),
"str-chat__message--other": !isMyMessage(),
"str-chat__message--has-attachment": hasAttachments,
},
);
if (!isMessageAIGenerated(message)) return <MessageSimple {...props} />;
return (
<div className={rootClassName}>
<div className="str-chat__message-inner" data-testid="message-inner">
<div className="str-chat__message-bubble">
{hasAttachments && (
<Attachment
actionHandler={handleAction}
attachments={attachments}
/>
)}
<StreamingMessage text={message?.text || ""} />
<MessageErrorIcon />
</div>
</div>
</div>
);
};
// ...
<Channel {...otherChannelProps} Message={CustomMessage} />;AIMessageComposer
To use the AIMessageComposer, you’ll only really need to wire it to our existing MessageComposer utility.
That would look something like this:
import {
useMessageComposer,
useChatContext,
useChannelActionContext,
useChannelStateContext,
useAttachmentsForPreview,
} from "stream-chat-react";
import { AIMessageComposer } from "@stream-io/chat-react-native-ai";
const CustomMessageComposer = () => {
const composer = useMessageComposer();
const { client } = useChatContext();
const { updateMessage, sendMessage } = useChannelActionContext();
const { channel } = useChannelStateContext();
const { attachments } = useAttachmentsForPreview();
return (
<AIMessageComposer
onChange={(event) => {
const input = event.currentTarget.elements.namedItem(
"attachments",
) as HTMLInputElement | null;
const files = input?.files ?? null;
if (files) {
composer.attachmentManager.uploadFiles(files);
}
}}
onSubmit={async (event) => {
event.preventDefault();
const target = event.currentTarget;
const formData = new FormData(target);
const message = formData.get("message");
composer.textComposer.setText(message as string);
const composedData = await composer.compose();
if (!composedData) return;
target.reset();
composer.clear();
await sendMessage(composedData);
}}
>
<AIMessageComposer.AttachmentPreview>
{attachments.map((attachment) => (
<AIMessageComposer.AttachmentPreview.Item
key={attachment.localMetadata.id}
file={attachment.localMetadata.file}
state={attachment.localMetadata.uploadState}
imagePreviewSource={attachment.localMetadata.previewUri as string}
onDelete={() => {
composer.attachmentManager.removeAttachments([
attachment.localMetadata.id,
]);
}}
onRetry={() => {
composer.attachmentManager.uploadAttachment(attachment);
}}
/>
))}
</AIMessageComposer.AttachmentPreview>
<AIMessageComposer.TextInput />
<AIMessageComposer.FileInput />
<AIMessageComposer.SpeechToTextButton />
<AIMessageComposer.ModelSelect />
<AIMessageComposer.SubmitButton />
</AIMessageComposer>
);
};
//...
<MessageInput Input={CustomMessageComposer} />;You can pick and choose individual input components rendered within AIMessageComposer, you can also group and stylize them by providing own wrappers. Most of these are fairly primitive input components with a bunch of default properties applied for easier use. TextInput, FileInput and SpeechToTextButton control internal state which is accessible through useText or useAttachments.