const isMessageAIGenerated = (message: LocalMessage) => !!message.ai_generated;SDK Integration
The AI components integrate directly with the React Native SDK.
Best Practices
- Keep AI-specific rendering gated by a stable
isMessageAIGeneratedcheck. - Use the SDK composer APIs to preserve uploads, drafts, and optimistic UI.
- Stream AI state through a single channel source to avoid conflicting UI signals.
- Render typing indicators only for active AI states to reduce noise.
- Ensure
stopGeneratingis always available when AI is producing output.
StreamingMessageView
The SDK exposes an isMessageAIGenerated factory that tells contexts when to render a streaming view.
Example: treat message.ai_generated === true as AI-generated.
In that case, our factory function would look something like this:
Then pass your custom StreamingMessageView:
import { StreamingMessageView } from "@stream-io/chat-react-native-ai";
const CustomStreamingMessageView = () => {
const { message } = useMessageContext();
return (
<View style={{ width: "100%", paddingHorizontal: 16 }}>
<StreamingMessageView text={message.text ?? ""} />
</View>
);
};
// ...
<Channel
{...otherChannelProps}
StreamingMessageView={CustomStreamingMessageView}
/>;This renders a full-width message using the AI StreamingMessageView instead of the default.
ComposerView
To use ComposerView, create a send resolver and use it instead of MessageInput:
import { ComposerView } from "@stream-io/chat-react-native-ai";
const CustomComposerView = () => {
const messageComposer = useMessageComposer();
const { sendMessage } = useMessageInputContext();
const { channel } = useChannelContext();
const { aiState } = useAIState(channel);
const stopGenerating = useCallback(
() => channel?.stopAIResponse(),
[channel],
);
const isGenerating = [AIStates.Thinking, AIStates.Generating].includes(
aiState,
);
const serializeToMessage = useStableCallback(
async ({ text, attachments }: { text: string; attachments?: any[] }) => {
messageComposer.textComposer.setText(text);
if (attachments && attachments.length > 0) {
const localAttachments = await Promise.all(
attachments.map((a) =>
messageComposer.attachmentManager.fileToLocalUploadAttachment(a),
),
);
messageComposer.attachmentManager.upsertAttachments(localAttachments);
}
await sendMessage();
},
);
return (
<ComposerView
onSendMessage={serializeToMessage}
isGenerating={isGenerating}
stopGenerating={stopGenerating}
/>
);
};What this does:
- Sets
textandattachmentson the composer state. - Calls
sendMessageafter the composer is updated. - Passes
isGeneratingandstopGeneratingto control the stop button.
AITypingIndicatorView
You can render a thinking indicator below MessageList and above ComposerView:
import { AITypingIndicatorView } from "@stream-io/chat-react-native-ai";
const CustomAIThinkingIndicatorView = () => {
const { channel } = useChannelContext();
const { aiState } = useAIState(channel);
const allowedStates = {
[AIStates.Thinking]: "Thinking about the question...",
[AIStates.Generating]: "Generating a response...",
[AIStates.ExternalSources]: "Checking external sources...",
};
if (aiState === AIStates.Idle || aiState === AIStates.Error) {
return null;
}
return (
<View
style={{
paddingHorizontal: 24,
paddingVertical: 12,
}}
>
<AITypingIndicatorView text={allowedStates[aiState]} />
</View>
);
};
// ...
<Channel {...channelProps}>
<MessageList />
<CustomAIThinkingIndicatorView />
<CustomComposerView />
</Channel>;