Skip to content

docs: chat app recipe with streaming patterns#6788

Open
amsraman wants to merge 6 commits into
mainfrom
docs/chat-app-recipe
Open

docs: chat app recipe with streaming patterns#6788
amsraman wants to merge 6 commits into
mainfrom
docs/chat-app-recipe

Conversation

@amsraman

Copy link
Copy Markdown
Contributor

Adds a chat-app recipe under docs/recipes/others/: a basic message-bubble chat UI plus the streaming-append patterns (append the assistant message on the first token, not before) that keep the loading experience clean.

Draft — from the AI-builder memory corpus. Draft pending a news fragment / skip-changelog label and a sidebar/recipe-index entry if needed.

🤖 Generated with Claude Code

@codspeed-hq

codspeed-hq Bot commented Jul 17, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 26 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing docs/chat-app-recipe (a131a48) with main (7e80931)2

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (fed116f) during the generation of this report, so 7e80931 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a new "Chat App" recipe under docs/recipes/others/chat.md and registers it in the docs sidebar. It documents a basic message-bubble chat UI and the correct streaming patterns for appending assistant messages only on the first token rather than pre-appending an empty bubble.

  • Basic UI: A runnable demo exec snippet that echoes messages using rx.foreach and rx.el.form, fully self-contained.
  • Streaming section: Side-by-side Incorrect/Correct examples illustrating anti-patterns (empty pre-appended bubble, no try/finally, no overlapping-request guard) versus the correct approach (first-token bubble creation, index-based update, is_generating guard, try/finally reset).
  • Sidebar registration: recipes.others.chat appended to the "Other" section in recipes.py so the page is discoverable in the left navigation.

Confidence Score: 5/5

Safe to merge — this is a documentation-only change adding a new recipe page and its sidebar entry.

All previously flagged issues have been addressed. The streaming code examples are logically correct: mutations happen inside async with self, the yield is placed after the block exits, is_streaming resets unconditionally in finally, and overlapping submits are serialized via is_generating with index-based bubble updates. No runtime code is touched.

No files require special attention.

Important Files Changed

Filename Overview
docs/recipes/others/chat.md New recipe covering basic chat UI and streaming patterns; previous review issues (yield inside lock, missing import, missing try/finally) have all been resolved
docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/recipes.py Adds recipes.others.chat to the Other section of the sidebar; straightforward one-line addition

Reviews (4): Last reviewed commit: "Keep streamed tokens tied to their own a..." | Re-trigger Greptile

Comment thread docs/recipes/others/chat.md Outdated
Comment thread docs/recipes/others/chat.md
Comment thread docs/recipes/others/chat.md
Comment thread docs/recipes/others/chat.md
@amsraman
amsraman marked this pull request as ready for review July 17, 2026 00:40
@amsraman
amsraman requested review from a team and Alek99 as code owners July 17, 2026 00:40

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 722007582a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/recipes/others/chat.md Outdated
self.is_streaming = False # hide dots on first token
first_token = False
else:
self.messages[-1]["content"] += delta

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep streamed tokens tied to their own assistant message

Because this handler is registered with background=True, a second submit can run while the first stream is still appending tokens. In that case each async with self refreshes state, and self.messages[-1] points to whichever message is currently last (for example the next user's prompt or another assistant reply), so the first response can corrupt a later message. Capture the assistant message index when it is appended, or serialize/disable concurrent sends before recommending this as the correct streaming pattern.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Confirmed the concern against Reflex's event processor: background events call _create_event_task immediately and bypass per-token ordering (unlike sequential events, which go through _enqueue_for_token), so two send_message submits on the same token run as concurrent asyncio tasks. Each async with self then re-resolves self.messages[-1] against current state, so one stream could append to another turn's message.

The Correct example now (1) captures the assistant message's index when the bubble is created on the first token and appends subsequent tokens to self.messages[assistant_index] instead of self.messages[-1], and (2) adds a separate is_generating guard (distinct from is_streaming, which flips off on the first token) that's set at the start and reset in finally, so overlapping submits are ignored while a reply is still streaming. The stream-append-on-first-token teaching point is unchanged. Added an explanatory sentence above the snippet plus a bullet in the 'differences that matter' list.

@amsraman

Copy link
Copy Markdown
Contributor Author

Addressed in the latest commit ("Keep streamed tokens tied to their own assistant message"), taking the second remedy you suggested (per-turn index rather than self.messages[-1]).

The streaming handler now captures the assistant bubble's index when it's first appended (assistant_index = len(self.messages) - 1) and writes every subsequent delta to self.messages[assistant_index]. Because messages are only ever appended, a captured index never shifts, so two overlapping background streams each write to their own bubble — a later handler can no longer land tokens on an earlier message. The stream is wrapped in try/finally so is_streaming resets even if the API call raises.

On the loading flag: is_streaming is a plain indicator that's cleared on the first token (and again in finally), so under true overlap it's cosmetic (the dots) rather than a correctness issue — the message data itself is kept consistent by the captured index. A real app that wants to forbid overlap entirely would disable the input while is_streaming, which I've called out in the notes.

I traced the state mutations rather than driving a live token stream (the example uses a placeholder LLM client).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant