Verified against v0 · 2026-08-06
Build a chat interface with real streaming, stop, and retry states
A v0 brief for a chat UI where streaming state, auto-scroll, and retry are all scoped to the single message they belong to, not a global flag that drifts out of sync the moment two things happen at once.
The prompt
Ready to copy — highlighted parts are example details you can swap.
Generate a ChatInterface component for a Next.js app — the UI layer for a streaming AI chat, built assuming the actual model call is wired up separately through a Server-Sent Events endpoint at /api/chat that emits { delta: string } chunks and a final { done: true } event.
MESSAGE RENDERING
Render a scrollable list of messages, each tagged user, assistant, and system (system messages render as small centered notices, not chat bubbles), with clearly distinct visual treatment for each role — not just alignment, since color-blind users and quick visual scanning both need more than a left/right position to tell roles apart. Render assistant message content as Markdown, including fenced code blocks with syntax highlighting and a copy-to-clipboard button per code block — a chat UI that renders an assistant's code response as one unformatted paragraph is not shippable for a developer-facing coding assistant, so code rendering quality matters more than usual.
STREAMING BEHAVIOR
While an assistant response is streaming in, append incoming text to the same message bubble incrementally rather than replacing the whole bubble's content on every chunk, which causes a visible flash. Show a subtle typing/streaming indicator (a pulsing cursor at the end of the text, not a separate spinner element) only while that specific message is actively streaming, and remove it the instant streaming completes for that message — a stuck streaming indicator on a message that actually finished is a specific and common bug in chat UIs built without state explicitly scoped per-message. Provide a "Stop generating" button visible only while a response is streaming, wired to an AbortController whose signal is passed into the fetch call, aborted when 'Stop generating' is clicked; after stopping, the partial message that was already streamed in stays visible exactly as far as it got, marked with a small "stopped" label, rather than being cleared.
SCROLL BEHAVIOR
Auto-scroll to the newest message as it streams in, but only if the user was already at or near the bottom of the scroll area when the new content arrived — if the user has scrolled up to read an earlier message, do not yank them back down to the bottom mid-read; show a "New messages" button instead that scrolls down on click.
ERROR AND RETRY
If a message fails to send or a response errors out partway through streaming, show the error inline on that specific message, not as a global banner, with a "Retry" action that only resends that one failed message, not the entire conversation history. The input box must stay usable and retain whatever the user had typed if a send attempt fails, rather than clearing on a failed send the way it would on a successful one.
INPUT
A multi-line auto-growing textarea that submits on Enter and inserts a newline on Shift+Enter, disabled while a response is streaming, with a visible character or token count if 4,000 characters per message applies.
CONSTRAINTS
dark theme by default, monospace font for code blocks, generous line height for readabilityCustomize
Optional — swap in your own details for the highlighted parts above.
Why this works
Per-message streaming state, rather than a single global "is streaming" flag, is what actually prevents the stuck-indicator bug the prompt calls out by name. A chat UI is naturally an array of independent messages, and if the streaming or typing indicator's visibility is tracked as one boolean at the conversation level instead of a property scoped to the specific message currently in flight, a user stopping a response and immediately regenerating it can end up with two messages both showing a streaming cursor, or a message that finished minutes ago still displaying one. Explicitly requiring per-message scope forces the state model to actually match the UI's own structure instead of a simpler-looking flag that doesn't.
The conditional auto-scroll — only scrolling down if the user was already near the bottom — targets a well-known and specifically annoying chat-UI failure. Naive implementations scroll to bottom on every new chunk unconditionally, which is fine while the user is passively watching a response stream in, but actively hostile the moment they've scrolled up mid-stream to reread an earlier message, since it yanks their scroll position away from what they're reading multiple times per second while tokens are still arriving. Checking scroll position before auto-scrolling is the actual fix, not a nice-to-have refinement layered on afterward.
Scoping retry to the single failed message rather than the whole conversation addresses a real data-loss and side-effect risk in chat apps. If a "Retry" button resends the entire message history to regenerate a response, and the underlying API call is not perfectly idempotent — a side-effecting tool call inside an earlier assistant turn, a rate-limited endpoint that behaves differently on a second full pass — a full-conversation retry can duplicate or re-trigger things that already happened once. A retry scoped to exactly the one failed message re-runs only the part that's actually known to have failed, which is both cheaper and safer by construction than resending everything just to fix one broken reply.
Verified against
v0 v0 by Vercel (Next.js + shadcn/ui default stack) · 2026-08-06
Changelog
- 2026-08-06 — Initial publish, verified against v0 with per-message streaming state and a conditional auto-scroll guard.
Need this built into your business?
If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.
EXPLORE CUSTOM SOFTWARE
