# Streaming messages

`StreamingMessage` 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`](https://getstream.io/chat/docs/sdk/react/ai-components/conversation-view/) shows every reply with it, and you can use it on its own in a component of yours:

```tsx
import { StreamingMessage } from "stream-chat-react/ai-components";

<StreamingMessage text={reply.text} />;
```

>
> **Info:** `StreamingMessage`, `AIMarkdown` and `AIStateIndicator` are part of the `stream-chat-react/ai-components` entry point. See the [Quickstart](https://getstream.io/chat/docs/sdk/react/ai-components/) to install them and load their stylesheet.
>

## Type the reply out

Pass the reply's text as it grows. `StreamingMessage` types out what's new since the last update, from where it was. Whatever has arrived is written out within about 400 ms, so the text on screen is never far behind: a long chunk types fast, and a trickle at the pace it comes.

The text there when it 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 gets shorter, such as a half-written reply the backend replaces with an error, shows at once too.

| Prop                   | Description                                                                   | Default                                      |
| ---------------------- | ----------------------------------------------------------------------------- | -------------------------------------------- |
| `text`                 | The Markdown to render.                                                       | -                                            |
| `letterIntervalMs`     | A fixed pace instead of catching up: the time between steps, in milliseconds. | `30` when only `renderingLetterCount` is set |
| `renderingLetterCount` | A fixed pace instead of catching up: the letters written each step.           | `2` when only `letterIntervalMs` is set      |

```tsx
<StreamingMessage
  text={reply.text}
  letterIntervalMs={30} // a step every 30 ms
  renderingLetterCount={3} // 3 letters a step
/>
```

With Stream Chat, the reply's text arrives with every `message.updated` event while the backend streams it. [What your backend sends](https://getstream.io/chat/docs/sdk/react/ai-components/conversation-state/#what-your-backend-sends) covers how to write it.

### Show the rest at once

`StreamingMessage` takes a `ref`, a `StreamingMessageRef`, whose `skipAnimation()` shows the whole text at once. Call it when the user stops the reply, so what has arrived shows without typing out:

```tsx
import { useEffect, useRef } from "react";
import { useChannel } from "stream-chat-react";
import {
  StreamingMessage,
  type StreamingMessageRef,
} from "stream-chat-react/ai-components";

export const Reply = ({ text }: { text: string }) => {
  const channel = useChannel();
  const ref = useRef<StreamingMessageRef>(null);

  useEffect(() => {
    const subscription = channel.on("ai_indicator.stop", () =>
      ref.current?.skipAnimation(),
    );
    return () => subscription.unsubscribe();
  }, [channel]);

  return <StreamingMessage ref={ref} text={text} />;
};
```

## In the message list

In the `MessageList` of `stream-chat-react`, a message that `Chat`'s `isMessageAIGenerated` marks renders with `StreamedMessageText`, which types it out in the same way, and shows the rest at once when `ai_indicator.stop` reaches the channel. [SDK integration](https://getstream.io/chat/docs/sdk/react/ai-components/sdk-integration/#streamedmessagetext) shows how to turn it on, and how to [use `StreamingMessage` in its place](https://getstream.io/chat/docs/sdk/react/ai-components/sdk-integration/#using-streamingmessage-instead).

`StreamedMessageText` keeps showing what's on screen when the text gets shorter, until it mounts again. Swap in `StreamingMessage` if your backend replaces a reply with a shorter text, such as an error.

### Type text out your own way

To type out text in a component of your own, use the `useMessageTextStreaming` hook from `stream-chat-react`, which `StreamedMessageText` is built on. It takes the `text`, and a fixed pace as `streamingLetterIntervalMs` (`30` by default) and `renderingLetterCount` (`2` by default). It returns the `streamedMessageText` typed out so far, and `skipAnimation()`:

```tsx
import { useMessageTextStreaming } from "stream-chat-react";
import { AIMarkdown } from "stream-chat-react/ai-components";

export const TypedReply = ({ text }: { text: string }) => {
  const { streamedMessageText } = useMessageTextStreaming({
    renderingLetterCount: 4,
    streamingLetterIntervalMs: 15,
    text,
  });

  return <AIMarkdown>{streamedMessageText}</AIMarkdown>;
};
```

The [`useMessageTextStreaming`](https://getstream.io/chat/docs/sdk/react/reference/hooks/ai-hooks/#usemessagetextstreaming) reference explains how the typing follows the text, and how to replace `StreamedMessageText` with a component built on it.

## Code and charts

`AIMarkdown` is the Markdown renderer `StreamingMessage` uses, with GitHub-flavored Markdown such as tables. Render it directly when the text is complete and doesn't need typing out:

```tsx
import { AIMarkdown } from "stream-chat-react/ai-components";

<AIMarkdown>{reply.text}</AIMarkdown>;
```

Code blocks with a language render with syntax highlighting and line numbers. A code block whose language is `chartjs`, or `json` that holds a Chart.js config, renders as a chart instead. The config needs a `type` and `data` with `datasets`, and the chart types are `bar`, `line`, `pie`, `doughnut`, `bubble`, `scatter`, `radar` and `polarArea`:

````md
```chartjs
{
  "type": "bar",
  "data": {
    "labels": ["Jan", "Feb", "Mar"],
    "datasets": [{ "label": "Signups", "data": [120, 180, 240] }]
  }
}
```
````

The chart renderer loads the first time a chart shows, with a "Loading chart..." placeholder until it's ready. If it fails to load or the config is invalid, the block shows as code instead of breaking the message. Any other `json` block stays highlighted code.

| Prop                 | Description                                                                                                   | Default                     |
| -------------------- | ------------------------------------------------------------------------------------------------------------- | --------------------------- |
| `children`           | The Markdown to render.                                                                                       | -                           |
| `toolComponents`     | Components that render code blocks by their language, as `Record<string, ComponentType<ToolComponentProps>>`. | `chartjs` and `json` charts |
| `markdownComponents` | Overrides for the Markdown elements, as the `components` of `react-markdown`, such as `code` or `pre`.        | -                           |

### Render your own blocks

To render a block your own way, such as a tool's output, register a component for its language in `toolComponents`. It receives the block's content as `data`, and a `fallback`, the block as code, to return when the data can't be rendered:

```tsx
import {
  AIMarkdown,
  type ToolComponentProps,
} from "stream-chat-react/ai-components";

// Renders a block tagged `weather`, such as {"city":"Oslo","temperature":4}.
const WeatherTool = ({ data, fallback }: ToolComponentProps) => {
  try {
    const { city, temperature } = JSON.parse(data) as {
      city: string;
      temperature: number;
    };
    return (
      <div className="weather-tool">
        {city}: {temperature}°C
      </div>
    );
  } catch {
    return fallback;
  }
};

// Declared outside the component, so AIMarkdown gets the same object on every render.
const toolComponents = { weather: WeatherTool };

export const MessageContent = ({ text }: { text: string }) => (
  <AIMarkdown toolComponents={toolComponents}>{text}</AIMarkdown>
);
```

A component registered for `chartjs` or `json` takes the place of the built-in chart.

## Show what the agent is doing

Before the first word of a reply arrives, the agent may spend a while thinking or calling tools. `AIStateIndicator` shows that it's working: a text with three pulsing dots. Pass the text, or let it pick a playful phrase, such as "Brewing up an answer":

```tsx
import { AIStateIndicator } from "stream-chat-react/ai-components";

<AIStateIndicator text="Checking external sources" />;
```

It picks its phrase when it mounts, so change its `key` to pick a new one. It renders with `role="status"`, so screen readers read the text.

With Stream Chat, the backend says what the agent is doing with `ai_indicator.*` events, and [`useAIState`](https://getstream.io/chat/docs/sdk/react/ai-components/conversation-state/#follow-what-the-agent-is-doing) returns the channel's state. Show the indicator for the states you want, inside `Channel`:

```tsx
import { AIStates, type Channel as StreamChannel } from "stream-chat";
import {
  Channel,
  MessageComposer,
  MessageList,
  useAIState,
  useChannel,
} from "stream-chat-react";
import { AIStateIndicator as AIStateIndicatorView } from "stream-chat-react/ai-components";

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

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

  return text ? <AIStateIndicatorView text={text} /> : null;
};

export const ChannelView = ({ channel }: { channel: StreamChannel }) => (
  <Channel channel={channel}>
    <MessageList />
    <AIIndicator />
    <MessageComposer />
  </Channel>
);
```

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](https://getstream.io/chat/docs/sdk/react/ai-components/conversation-state/#say-what-the-agent-is-doing) lists the states and the events that set them.

`stream-chat-react` exports an `AIStateIndicator` of its own, from its main entry point, which is why the example renames this one on import. That one follows the channel's state by itself, but only shows "Thinking..." and "Generating...". This one only draws the indicator. See [SDK integration](https://getstream.io/chat/docs/sdk/react/ai-components/sdk-integration/#aistateindicator).

In an `AIConversation`, show what the agent is doing in its `footer` with `AITypingIndicator`, and the agent thinking with `isThinking`, as [Conversation view](https://getstream.io/chat/docs/sdk/react/ai-components/conversation-view/#show-the-agent-thinking) describes.

## Styling and texts

The components follow the SDK's theme in light and dark mode, and their colors come from the `--str-chat__ai-*` variables described in [Theming](https://getstream.io/chat/docs/sdk/react/ai-components/#theming): `syntax-*` for the code highlighting, `table-*` for tables, `inline-code-bg` for inline code, and `text-secondary` for the indicator. Their class names start with `str-chat__ai-`, such as `str-chat__ai-streaming-message` and `str-chat__ai-state-indicator`.

Their texts go through the SDK's translations, such as `aiComponents.chart.loading.text` ("Loading chart...") and the indicator's phrases under `aiComponents.stateIndicator.*`. English is built in. To change or translate them, register your own dictionary, as described in [Localization](https://getstream.io/chat/docs/sdk/react/features/app-experience/localization/).

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/ai-components/streaming-messages/](https://getstream.io/chat/docs/sdk/react/ai-components/streaming-messages/).