For AI agents: the complete documentation index is at llms.txt. Markdown versions are available by appending .md or sending Accept: text/markdown.
Reflex Logo
Docs Logo
Recipes

/

Others

/

Chat

Chat App

A chat interface renders a running list of messages and lets the user send new ones. Each message is a {"role": ..., "content": ...} dictionary held in state, and the component loops over that list to draw bubbles. This recipe covers a basic chat UI and the streaming patterns that keep the loading experience clean.

Basic Chat UI

Store messages in state, append the user's message on submit, and produce a reply. The example below echoes the input so it runs on its own; in a real app you would call your model provider instead.

Expand

Streaming Responses

When a model streams its reply token by token, show a loading indicator immediately and append the assistant message only once the stream actually starts producing content.

The key rule: do not pre-append an empty assistant message before the response begins. Doing so renders a blank bubble alongside the loading dots, producing a broken double-indicator experience.

Both snippets below use the package and read the API key from the OPENAI_API_KEY environment variable. They also run as background events, so all state mutations happen inside async with self: blocks with deltas being sent to the client after exiting the block.

Implementation

The loading dots show first, and the assistant bubble appears only on the first streamed token. Two more details make this safe:

  • The streaming LLM call is wrapped in a try/finally so is_streaming always resets even if the API call raises
  • An is_generating guard serializes requests while the assistant message is updated by a captured index rather than self.messages[-1], which would otherwise point at whichever message is currently last and let overlapping streams write to the wrong bubble:
Expand

Key Details

  1. Build the API request from self.messages directly — there is no trailing empty message to slice off with [:-1].
  2. Append the assistant message only on the first token from the stream.
  3. Hide the loading indicator after the first token, not at the end of the stream.
  4. Wrap the stream in try/finally so is_streaming resets even when the API call fails.
  5. Guard overlapping submits with is_generating and update the assistant message by its captured index (not self.messages[-1]) — background events run concurrently, so a second stream could otherwise append tokens to the wrong message.

Built with Reflex