Info:
This is documentation for Stream Chat React Native SDK v9, which is no longer actively maintained. For up-to-date documentation, see the latest version (v10).

Streaming Messages

StreamingMessageView renders a reply as the agent writes it: Markdown, code with syntax highlighting, tables and charts, typed out as the text grows, as ChatGPT does. AIConversation shows every reply with it, and you can use it on its own in a view of yours:

import { StreamingMessageView } from "stream-chat-react-native/ai-components";

<StreamingMessageView text={reply.text} />;
Info:

StreamingMessageView and AITypingIndicatorView are available from version 9.10.0 of stream-chat-react-native and stream-chat-expo, in the stream-chat-react-native/ai-components and stream-chat-expo/ai-components entry points. See the Quickstart to install them. Render them inside StreamTheme, like the other AI components. Charts need victory-native and @shopify/react-native-skia.

Type the reply out

Pass the reply's text as it grows. StreamingMessageView types out the text that's new since the last update and carries on from where it was. It keeps up with the agent: each new part is typed out within about 0.4 seconds, at least a letter a frame, so a fast model never leaves it behind. Pass letterInterval or renderingLetterCount to type at a fixed pace instead. A code block that the text has opened but not closed yet is closed for the view, so the text on screen is always valid Markdown.

The text there when the view mounts shows at once. That's why a stored reply doesn't type itself out again when the user scrolls back to it. Text that isn't a continuation of what's shown, such as a half-written reply the backend replaces with an error, or another message's text in a view a list reuses, shows at once too.

Name Type Required Description
text string yes The Markdown to render.
letterInterval number no The time between steps of a fixed pace, in milliseconds. Without it and renderingLetterCount, the typing keeps up with the text as it arrives.
renderingLetterCount number no The letters written each step of a fixed pace. Defaults to 2 when letterInterval is given.
onWritingChange (isWriting: boolean) => void no Called with true when the text starts typing itself out and false when it's done, such as to show what follows it once it is.
onLink (url: string) => void no Called when the user presses a link. Without it, the link opens where the device opens it.
rules MarkdownRules no Rules that are deeply merged over the default ones, based on the SimpleMarkdown parsing engine. See Change how it draws.
paragraphNumberOfLines number no The numberOfLines of the Markdown's paragraphs and text, such as to show a message in a cut-off form.
<StreamingMessageView
  text={reply.text}
  letterInterval={5} // a step every 5 ms
  renderingLetterCount={3} // 3 letters a step
/>

To show the rest of the text at once, such as when the user taps a reply that's still typing, call skipAnimation on the view's ref:

import { useRef } from "react";
import { Pressable } from "react-native";
import {
  StreamingMessageView,
  type StreamingMessageViewRef,
} from "stream-chat-react-native/ai-components";

export const Reply = ({ text }: { text: string }) => {
  const ref = useRef<StreamingMessageViewRef>(null);
  return (
    <Pressable onPress={() => ref.current?.skipAnimation()}>
      <StreamingMessageView ref={ref} text={text} />
    </Pressable>
  );
};

With Stream Chat, the reply's text arrives with every message.updated event while the backend streams it. What your backend sends covers how to write it.

In the message list

In the MessageList of the Chat SDK, a message that Chat's isMessageAIGenerated marks renders with the SDK's own StreamingMessageView, which types its text out in the same way, as a regular message bubble. To render it with the one from the AI components instead, which draws Markdown, highlighted code and charts, override the slot through WithComponents. SDK integration shows how.

Type text out your own way

To type text out in a view of your own, use the useStreamingMessage hook, which StreamingMessageView is built on. It takes the text, and optionally a fixed pace as letterInterval and renderingLetterCount, and returns the streamedMessageText typed out so far, isWriting, and skipAnimation to show the rest at once:

import { Text } from "react-native";
import { useStreamingMessage } from "stream-chat-react-native/ai-components";

export const TypedReply = ({ text }: { text: string }) => {
  const { isWriting, streamedMessageText } = useStreamingMessage({
    letterInterval: 15,
    renderingLetterCount: 4,
    text,
  });

  return (
    <Text>
      {streamedMessageText}
      {isWriting ? "|" : ""}
    </Text>
  );
};

The typing follows the text this way:

  1. The text there when the view mounts shows at once. Only text added later is typed out.
  2. When text grows, typing goes on from where it was.
  3. When text isn't a continuation of what's on screen, the new text shows at once.

To type out the message list's AI replies your own way, replace StreamingMessageView with a view built on the hook, as useStreamingMessage shows. Keep the speed readable, and type out only the messages isMessageAIGenerated marks.

Code and charts

Markdown is the renderer StreamingMessageView uses, with the standard Markdown: tables, inline code, headings, lists and links. Render it directly when the text is complete and doesn't need typing out:

import { Markdown } from "stream-chat-react-native/ai-components";

<Markdown>{reply.text}</Markdown>;

Code blocks with a language render with syntax highlighting, and a button that copies the code when the app has a clipboard library. On Android with the New Architecture, highlighted code is drawn by one native text view with color spans, which keeps long code blocks cheap to scroll.

A code block whose language is vega-lite (or vegalite, chart, chartjs, echarts, highcharts or json) and whose content is a Vega-Lite spec renders as a native chart instead, as does a mermaid pie chart. The marks are bar, line, area, point and arc:

```vega-lite
{
  "data": {
    "values": [
      { "month": "Jan", "signups": 120 },
      { "month": "Feb", "signups": 180 },
      { "month": "Mar", "signups": 240 }
    ]
  },
  "mark": "bar",
  "encoding": {
    "x": { "field": "month", "type": "nominal" },
    "y": { "field": "signups", "type": "quantitative" }
  }
}
```
```mermaid
pie title Signups by plan
  "Free" : 120
  "Team" : 60
```

A block that doesn't read as a chart renders as code instead of breaking the message. Charts are drawn with victory-native and Skia, so install both, as the Quickstart describes. To teach your agent to write them, describe the format in its instructions.

Change how it draws

rules changes how the Markdown draws any element, or adds one of your own. They're deeply merged over the default rules, so pass only what you change. The rules are named after the elements: heading, paragraph, link, image, inlineCode, codeBlock, table, list and blockQuote, among others. To draw an element your own way, give its rule a react function, which gets the node, an output function that draws its content, and the parser's state:

import { Text } from "react-native";
import {
  type MarkdownRules,
  type ReactOutput,
  StreamingMessageView,
} from "stream-chat-react-native/ai-components";

// Declared outside the component, so the view gets the same object on every render.
const rules: MarkdownRules = {
  heading: {
    react: (node, output: ReactOutput, state) => (
      <Text key={state.key} style={{ fontSize: 22, fontWeight: "700" }}>
        {output(node.content, state)}
      </Text>
    ),
  },
  // Leave images out.
  image: { match: () => null },
};

export const Reply = ({ text }: { text: string }) => (
  <StreamingMessageView rules={rules} text={text} />
);

A rule with a match that returns null never matches, which turns the element off. To change only the looks, such as the colors of a code block, use the theme's markdown key instead, as Styling and texts shows.

Show what the agent is doing

Before the first word of a reply arrives, the agent may spend a while thinking or calling tools. AITypingIndicatorView shows that it's working: a text of your choice, with a shimmer:

import { AITypingIndicatorView } from "stream-chat-react-native/ai-components";

<AITypingIndicatorView text="Checking external sources" />;

With Stream Chat, the backend says what the agent is doing with ai_indicator.* events, and useAIState returns the channel's state. Show the indicator for the states you want, between MessageList and the composer, inside Channel:

import { View } from "react-native";
import {
  AIStates,
  useAIState,
  useChannelContext,
} from "stream-chat-react-native";
import { AITypingIndicatorView } from "stream-chat-react-native/ai-components";

const indicatorText: Record<string, string> = {
  [AIStates.Thinking]: "Thinking",
  [AIStates.ExternalSources]: "Checking external sources",
};

export const AIIndicator = () => {
  const { channel } = useChannelContext();
  const { aiState } = useAIState(channel);
  const text = indicatorText[aiState];

  return text ? (
    <View style={{ paddingHorizontal: 24, paddingVertical: 12 }}>
      <AITypingIndicatorView text={text} />
    </View>
  ) : null;
};

Once the reply's text arrives, during AI_STATE_GENERATING, the text itself shows the agent is working, so the example hides the indicator. Say what the agent is doing lists the states and the events that set them.

Both the Chat SDK and the AI components export an AITypingIndicatorView and a StreamingMessageView, with different props. The ones from stream-chat-react-native read the AI state and the message themselves, while the ones from stream-chat-react-native/ai-components take the text to show. Import useAIState and AIStates from the first, and the views you render yourself from the second.

In an AIConversation, show what the agent is doing in its footer with AITypingIndicatorView, and the agent thinking with isThinking, as Conversation view describes. A controller shows the thinking by itself.

Styling and texts

The views take their colors from the AI components' theme, which StreamTheme provides. The theme's markdown key styles the Markdown, such as codeBlockContainer, table and inlineCode, and aiTypingIndicatorView styles the indicator's text. The shimmer color of the theme's colors is the highlight that sweeps across it:

<StreamTheme
  style={{
    aiTypingIndicatorView: { text: { fontSize: 15 } },
    colors: { shimmer: "#ffffff" },
  }}
>
  {children}
</StreamTheme>