Skip to content
Neon UI
Esc
navigateopen⌘Jpreview
On this page

AgentChat

Full agent chat pane with streamed turns, tool chips, and a composer.

AgentChat is the conversation pane for an agent platform: turns scroll in a message scroller that follows streams while you are pinned to the bottom (with a jump-to-latest button when you are not), user turns render as bubbles, agent turns as streamed markdown with ToolCallChip invocations and quiet reasoning lines, and the composer has a controls slot for ThinkingModelSelect. Typed against the AI SDK’s UIMessage — bring your own useChat transport.

The preview streams a scripted vibe-coding session through the real useChat lifecycle (no model or network): press send to play the next turn.

Start building

Describe a feature and the agent edits the live app, database included.

Install

npx shadcn@latest add https://ui.neon.com/r/agent-chat.json

Usage

import { useChat } from "@ai-sdk/react";
import { AgentChat } from "@/components/neon-ui/agent-chat";

const { messages, sendMessage, status } = useChat({ transport });

<AgentChat
  messages={messages}
  status={status}
  onSend={(text) => sendMessage({ text })}
/>;

Wiring to Neon

Stream a Neon-hosted agent (e.g. a Mastra agent on a Neon Function) directly from the browser with a short-lived JWT, so your app server never sits in the path of the long stream:

const { messages, sendMessage, status } = useChat({
  transport: new DefaultChatTransport({
    api: `${agentUrl}/chat`,
    prepareSendMessagesRequest: async ({ messages }) => ({
      headers: { Authorization: `Bearer ${await getToken()}` },
      body: { messages, model, reasoning_effort: effort },
    }),
  }),
});

Props

PropType
messagesUIMessage[]

Conversation from useChat.

TypeUIMessage[]
statusChatStatus

useChat status: "submitted" | "streaming" | "ready" | "error".

TypeChatStatus
onSend(text: string) => void

Called with the trimmed composer text.

Type(text: string) => void
controls?ReactNode

Slot at the left of the composer's control row, e.g. ThinkingModelSelect.

TypeReactNode
emptyState?ReactNode

Rendered while the conversation is empty.

TypeReactNode
disabled?boolean

Disable the composer, e.g. while an environment provisions.

Typeboolean
Defaultfalse
placeholder?string

Composer placeholder text.

Typestring
Default"Describe a change…"

Model switches

Attach AgentChatTurnMetadata (model, modelName, effort) to user turns via sendMessage metadata. When a turn’s model or effort differs from the previous turn, a quiet “switched to GPT-5.2 · high thinking” marker renders above it — part of the timeline, so it replays with the transcript. Try it in the preview: change the model or effort, then send.

sendMessage({ text }, { metadata: { model, modelName, effort } });

Sub-components

Both are exported for custom layouts.

ChatMessage

One turn from a UIMessage: user turns as bubbles, agent turns as markdown with reasoning and tool lines.

Add auth so each user only sees their own books.

Neon Auth is already wired; I need a user_id column and a filtered query.

Done — books now carry a user_id, and every query filters by the signed-in user from Neon Auth. Sign in as two different users to verify the isolation.

ChatInput

The composer on its own: auto-growing textarea, controls slot, Enter to send.

ChatWorkingIndicator

The busy row: the Neon mark and the status line painted with the same shadcn shimmer gradient, sweeping in sync. Tune both at once with shimmer-duration-* or shimmer-color-* on the wrapper; the grain-resolve NeonLoader remains available standalone. Rendered automatically while a message is submitted; the label is yours.

Agent is working…
Provisioning your database…

Accessibility

The busy marker uses role="status"; the composer is a labeled textarea (Enter sends, Shift+Enter breaks); the scroller preserves reading position during streams and reduced motion is respected throughout.