const isMessageAIGenerated = (message) => !!message.ai_generated;
const MyAIStateIndicator = () => {
const { aiState } = useAIState();
return aiState === AIStates.Thinking ? (
<div>
<p>The chat-bot is currently thinking...</p>
</div>
) : null;
};
const App = () => (
<Chat client={client} isMessageAIGenerated={isMessageAIGenerated}>
<Channel channel={channel}>
<MessageList />
<AIStateIndicator />
<MessageInput />
</Channel>
</Chat>
);Hooks
The SDK includes two hooks that make it easier to build custom AI UI components.
Best Practices
- Use
useAIStateto avoid duplicating backend state logic. - Keep streaming speeds readable; avoid overly fast typewriter effects.
- Prefer
useMessageTextStreamingfor AI messages only. - Keep custom indicators simple and state-driven.
- Reset streaming when message IDs change to avoid stale text.
useAIState
Returns the current AI state for the active channel.
Example Usage
In the example above, the custom indicator renders only when the AI is in the AI_STATE_THINKING state.
useMessageTextStreaming
Returns text in a streamed, typewriter fashion. Control the speed with streamingLetterIntervalMs and renderingLetterCount.
Example usage
const isMessageAIGenerated = (message) => !!message.ai_generated;
const MyStreamedMessageText = ({ message: messageFromProps, renderText }) => {
const { message: messageFromContext } = useMessageContext(
"StreamedMessageText",
);
const message = messageFromProps || messageFromContext;
const { text = "" } = message || {};
const { streamedMessageText } = useMessageTextStreaming({
renderingLetterCount: 1,
streamingLetterIntervalMs: 10,
text,
});
return (
<MessageText
message={{ ...message, text: streamedMessageText }}
renderText={renderText}
/>
);
};
const App = () => (
<Chat client={client} isMessageAIGenerated={isMessageAIGenerated}>
<Channel channel={channel} StreamedMessageText={MyStreamedMessageText}>
<MessageList />
<MessageInput />
</Channel>
</Chat>
);This override uses useMessageTextStreaming to speed up the typewriter animation.