App frame for an AI agent chat: a top bar with breadcrumb, search and Ask AI over a chat panel, a message list that sticks to the newest message while it streams, a jump-to-latest button once you scroll up, an empty state with suggestions, and a growing composer with Send / Stop.
Set your access key from API keys as NEXTUI_TOKEN in .env.local and add the @nextui namespace below to components.json once. Then every item installs by name. Each install counts as a copy on a free account.
$pnpm dlx shadcn@latest add @nextui/conversation-shell
1{2"registries": {3"@nextui": {4"url": "https://nextui.dev/r/{name}.json",5"headers": { "Authorization": "Bearer ${NEXTUI_TOKEN}" }6}7}8}
1"use client";23import { useChat } from "@ai-sdk/react";4import { ConversationShell } from "@/components/nextui/conversation-shell";5import { StreamingMessage } from "@/components/nextui/streaming-message";6import { ToolCall } from "@/components/nextui/tool-call";78export function Chat() {9const { messages, sendMessage, status, stop } = useChat();1011return (12<ConversationShell13busy={status === "submitted" || status === "streaming"}14onStop={stop}15onSend={(text) => sendMessage({ text })}16messages={messages.map((m) => ({17id: m.id,18role: m.role === "user" ? "user" : "assistant",19content: m.parts.map((part, i) =>20part.type === "text" ? (21m.role === "user" ? part.text : <StreamingMessage key={i} part={part} />22) : part.type.startsWith("tool-") ? (23<ToolCall key={i} part={part as never} />24) : null,25),26}))}27/>28);29}
| Prop | Type | Default | Description |
|---|---|---|---|
| messages | { id: string; role: "user" | "assistant"; content: ReactNode }[] | – | The thread, oldest first; content can be text or any primitive |
| onSend | (text: string) => void | – | Called with the composer text on Enter or Send; also called by suggestion chips |
| busy | boolean | false | The agent is answering: Send becomes Stop and sending is blocked |
| onStop | () => void | – | Called by the Stop button |
| suggestions | string[] | – | Prompts on the empty state |
| emptyTitle | ReactNode | "What should the agent do?" | Heading of the empty state |
| placeholder | string | "Ask the agent…" | Composer placeholder |
| title | string | "Launch planning" | Title of the open chat, shown in the top bar breadcrumb |
| brand | string | "Agent" | Product name next to the logo |
| onAskAI | () => void | – | Called by the Ask AI button in the top bar |
| autoPlay | boolean | false | Play a scripted two-turn chat on a loop while on screen, for demos |
| className | string | – | Extra classes on the frame; set the height here (default h-[600px]) |
Sign in to see the code
Preview it freely. A free account copies code and prompts a few times a day.