# AI chat

> A complete AI chat with streamed markdown answers, folding reasoning, tool chips, citations, and a model switcher.

- Type: Block
- Page: https://uiarc.dev/components/blocks/ai-chat
- Markdown: https://uiarc.dev/components/blocks/ai-chat/markdown

- Access: Arc Pro
- Registry id: `ai-chat`
- Source file: `registry/blocks/ai-chat/ai-chat.tsx`
- Built from: Motion
- Keywords: react ai chat, chatgpt clone ui, streaming chat interface, llm chat ui, ai assistant ui, chat with citations, model switcher, chat with reasoning panel

Use this as a starting point and replace the sample data with your own.

## When to use

- A full page assistant with conversation history, model choice, and streamed answers.
- Research or support assistants that show tool calls and cite sources inline.
- Products that need regenerate, answer versions, and editable prompts out of the box.

## When not to use

- Use ai-composer when you only need the prompt input.
- Use chat-thread for person to person messaging without model output.
- Use support-widget for a small floating help chat.

## Installation

AI chat is part of Arc Pro. The live preview is public; the source and install command need Pro.

### CLI with a Pro token

1. Create a token in your account and set it in the environment (or `.env.local`). Never commit it.

```bash
export ARC_PRO_TOKEN=arc_pro_...
```

2. Add the Pro registry to `components.json`:

```json
{
  "registries": {
    "@uiarc": "https://uiarc.dev/r/{name}.json",
    "@uiarc-pro": {
      "url": "https://uiarc.dev/r/pro/{name}.json",
      "headers": {
        "Authorization": "Bearer ${ARC_PRO_TOKEN}"
      }
    }
  }
}
```

3. Install:

```bash
npx shadcn@latest add @uiarc-pro/ai-chat
```

### Manual

Signed-in Pro members can copy the source from the Manual tab on the docs page.

- Plans: https://uiarc.dev/pricing
- Create a Pro token: https://uiarc.dev/account#pro-access
- Setup guide: https://uiarc.dev/docs/ai#pro-access

## Usage

```tsx
"use client";

import { AiChat } from "@/registry/blocks/ai-chat/ai-chat";
import type { ChatResponder } from "@/registry/blocks/ai-chat/ai-chat";

const respond: ChatResponder = async function* (request, signal) {
  const response = await fetch("/api/chat", {
    method: "POST",
    body: JSON.stringify({ messages: request.messages, model: request.model }),
    signal,
  });
  const reader = response.body!.pipeThrough(new TextDecoderStream()).getReader();
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    yield { type: "text", text: value };
  }
};

export function Assistant() {
  return (
    <AiChat
      respond={respond}
      notice={null}
      models={[{ id: "default", name: "Default", description: "Balanced for everyday work" }]}
      defaultModel="default"
      defaultConversations={[]}
      user={{ name: "Ava Mitchell", detail: "Pro plan" }}
    />
  );
}
```

## API reference

### AiChat

A full AI chat surface: a collapsible conversation list, streamed markdown answers, a reasoning panel, tool call chips, citations with source cards, regenerate with versions, editable prompts, a model switcher, image attachments, and a send button that becomes stop. Connect respond to your model. Also the default export.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultConversations` | `ChatConversation[]` | `sampleConversations` | Initial conversations. The block owns them afterwards. |
| `onConversationsChange` | `(conversations: ChatConversation[]) => void` | – | Called after every change, including each streamed chunk. Not called on mount. |
| `activeId` | `string \| null` | – | Controlled open conversation id; null shows a new chat. |
| `defaultActiveId` | `string \| null` | `null` | Open conversation on mount when uncontrolled. |
| `onActiveChange` | `(id: string \| null) => void` | – | Called when a conversation is opened or a new chat starts. |
| `models` | `ChatModel[]` | `sampleModels` | Options in the model switcher. |
| `model` | `string` | – | Controlled model id. |
| `defaultModel` | `string` | `"atlas"` | Starting model id when uncontrolled. |
| `onModelChange` | `(id: string) => void` | – | Called when a model is chosen. |
| `sidebarOpen` | `boolean` | – | Controlled sidebar state on wide layouts. |
| `defaultSidebarOpen` | `boolean` | `true` | Starting sidebar state on wide layouts. |
| `onSidebarOpenChange` | `(open: boolean) => void` | – | Called when the wide sidebar opens or closes. |
| `suggestions` | `ChatSuggestion[]` | `sampleSuggestions` | Prompts offered on an empty chat. |
| `respond` | `ChatResponder` | `sampleResponder` | (request, signal) => AsyncIterable<ChatStreamEvent>. Streams thinking, tool, sources, and text events. The default plays scripted samples. |
| `greeting` | `string` | `"What are we working on?"` | Heading on an empty chat. |
| `notice` | `ReactNode` | `"Sample responses"` | Small note in the header. Pass null to hide it. |
| `user` | `{ name: string; avatar?: string; detail?: string }` | `{ name: "Emma Collins", avatar: "/media/people/emma-collins.jpg", detail: "Team plan" }` | Account shown at the bottom of the sidebar. |
| `className` | `string` | – | Extra class on the root. |

### ChatResponder

Exported type. (request: ChatRequest, signal: AbortSignal) => AsyncIterable<ChatStreamEvent>. ChatRequest is { conversationId, messages, prompt, attachments, model }. Stop aborts the signal.

No props.

### ChatStreamEvent

Exported type. { type: "thinking"; text } appends reasoning, { type: "tool"; tool } adds or updates a ChatToolCall by id, { type: "sources"; sources } replaces the citation list, { type: "text"; text } appends answer markdown. The first tool or text event closes the thinking panel.

No props.

### sampleResponder

The default scripted responder, exported for demos and tests. It honors the abort signal.

No props.

### StreamText

Exported from ai-chat/markdown.tsx. Plain streamed text, such as reasoning: the same per-word fade as answers, without markdown blocks. Newlines become line breaks. The same file exports Markdown, parseBlocks, plainText, and CopyControl.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `text` (required) | `string` | – | Text so far. |
| `streaming` (required) | `boolean` | – | Animates newly arrived words while true. |
| `reduced` (required) | `boolean` | – | Pass the reduced motion preference; true renders words without the fade. |
| `className` | `string` | – | Class on the paragraph. |

## Keyboard interactions

| Keys | Action |
| --- | --- |
| Enter | Sends the message. Shift Enter adds a new line; IME composition is respected. |
| Escape | Stops generation from the composer, cancels a prompt edit, closes a source card, or closes the narrow drawer. |
| Arrow Down | On the model pill, opens the model list. |
| Arrow Up / Down, Home / End, Enter / Space | Move through and choose in the model list; Escape closes it and returns focus to the pill. |
| Tab to a citation | Keyboard focus opens the source card; Enter toggles it. |

## Accessibility

- The sidebar is an aside labelled "Chat history" with a nav of conversations; the current one has aria-current="page".
- Streaming answers set aria-busy. A polite live region announces generation, completion time, stops, and errors.
- The model switcher is a button with aria-haspopup="listbox" and a listbox using aria-activedescendant.
- Citation chips are labelled with source number, title, and domain; the source card is a role="dialog" linked with aria-controls.
- Thinking, tool detail, and source list toggles use aria-expanded and aria-controls. Icon buttons all have labels.

## Motion

- Streamed words fade and unblur in with a CSS keyframe; code lines fade in as they arrive.
- Answer height follows content on a spring through a ResizeObserver, so streaming grows instead of jumping.
- The model pill grows into its menu as one surface and shrinks back around the new name.
- Switching answer versions slides in the direction of travel. A new conversation title scrambles in.
- The wide sidebar springs its width open and shut; on narrow layouts it slides over the thread as a drawer.
- Reduced motion stops the word and line keyframes and spinners, removes offsets and blur, and uses short fades.

## Responsive behavior

- Below 720px root width the sidebar becomes a drawer over the thread with a scrim, closed by Escape or a tap outside.
- The main column is a size container. At 560px and below the thread, dock, and header padding tighten; at 380px the header notice hides.
- Message hover actions are always visible on devices without hover.

## Performance

- Every stream event patches state, so each chunk re-renders the active answer. Batch tiny tokens into larger chunks in respond.
- A ResizeObserver keeps the thread pinned to the bottom while you are within 56px of it; scrolling up releases it and shows a jump button.
- Messages are not virtualized. Very long conversations should load older messages on demand.

## Notes for AI

- Use for an assistant or copilot that streams answers, shows reasoning and tool use, and cites sources.
- Write respond as an async generator that yields ChatStreamEvent objects and passes the signal to fetch. Write citations in the text as [1], [2], matching the sources event.
- Conversations are uncontrolled after mount; persist them from onConversationsChange, ideally debounced since it fires per chunk.
- Markdown supports headings, lists, quotes, tables, and fenced code with basic regex highlighting from markdown.tsx; swap in your own highlighter if you need more languages.
- The reasoning panel renders with StreamText from markdown.tsx; reuse it for any plain streamed text next to the chat.
- Attachments are images only, up to four, via picker, paste, or drop. They arrive as blob: URLs, so upload them inside respond if the model needs them.

## Related

- [AI composer](https://uiarc.dev/components/blocks/ai-composer/markdown): A focused assistant thread where messages lift out of the composer and replies stream in.
- [Chat thread](https://uiarc.dev/components/chat-thread/markdown): A chat thread with grouped messages, reactions, read receipts, typing, and a composer with attachments.
- [Agent run](https://uiarc.dev/components/blocks/agent-run/markdown): A live AI agent run: steps stream in, tool calls expand, a diff waits for approval, and it all folds into a result.
- [Support conversation](https://uiarc.dev/components/blocks/support-conversation/markdown): A responsive message thread with useful quick replies and live feedback.
- [Text shimmer](https://uiarc.dev/components/text-shimmer/markdown): Show ongoing work with a calm light across the words.

## Guidance for AI tools

Blocks are complete, self-contained screens with sample data. Replace the sample data and connect the callbacks described above. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://uiarc.dev/llms.txt
