# AI Chat
Source: https://ui.spencerwueste.com/docs/blocks/ai-chat/
Markdown: https://ui.spencerwueste.com/llm/blocks/ai-chat.md

> A complete chat surface that composes the nine core chat components, with a mock streaming transport you can swap for your API.

> Live preview: https://ui.spencerwueste.com/preview/ai-chat

## What's inside

The block wires nine components into one screen:

- **Thread** keeps the viewport pinned to new tokens.
- **Message** renders user bubbles and assistant markdown with an action bar.
- **Reasoning** shows thinking while it streams, then collapses.
- **Tool** displays each tool call with status, input, and output.
- **Prompt Input** with attach, model, send, and stop actions.
- **Suggestions** on the empty state.
- **Model Selector** in the header and inside the input.
- **Text Shimmer** as the "Thinking…" placeholder.
- **Feedback Bar** after the first completed reply.

## Installation

```bash
npx shadcn@latest add https://ui.spencerwueste.com/r/ai-chat.json
```

Manual install dependencies: `@heroicons-animated/react motion`

Registry JSON: https://ui.spencerwueste.com/r/ai-chat.json

The registry item pulls in every component it depends on.

## Usage

```tsx
import AIChat from "@/components/blocks/ai-chat/chat";

export default function Page() {
  return (
    <div className="h-dvh p-4">
      <AIChat />
    </div>
  );
}
```

Give the parent a bounded height. The block fills it and scrolls internally.

## Going live

The demo uses `mockTransport`, an async generator that yields scripted events. To connect a real model, write a transport with the same shape:

```ts
import type { ChatTransport } from "@/components/blocks/ai-chat/types";

export const apiTransport: ChatTransport = async function* ({ messages, model, signal }) {
  const res = await fetch("/api/chat", {
    method: "POST",
    body: JSON.stringify({ messages, model }),
    signal,
  });
  const reader = res.body!.getReader();
  const decoder = new TextDecoder();
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    for (const line of decoder.decode(value).split("\n").filter(Boolean)) {
      yield JSON.parse(line); // { type: "text-delta", text: "…" } etc.
    }
  }
  yield { type: "done" };
};
```

Then pass it in:

```tsx
<AIChat transport={apiTransport} />
```

### Event types

| Event | Payload | Effect |
| --- | --- | --- |
| `reasoning-delta` | `{ text }` | Appends to the current reasoning part. |
| `reasoning-done` | | Marks reasoning complete (collapses the block). |
| `tool-start` | `{ id, name, input? }` | Adds a running tool card. |
| `tool-result` | `{ id, output }` | Completes the tool with output. |
| `tool-error` | `{ id, errorText }` | Marks the tool as failed. |
| `text-delta` | `{ text }` | Appends assistant text. |
| `error` | `{ message }` | Ends the turn with an error message. |
| `done` | | Ends the turn. |

## API Reference

| Prop | Type | Description |
| --- | --- | --- |
| `transport` | `ChatTransport` | Event source. Defaults to `mockTransport`. |
| `models` | `ChatModel[]` | Options for the model selector. |
| `defaultModel` | `string` | Initially selected model id. |
| `suggestions` | `string[]` | Empty-state prompt chips. |
| `title` | `string` | Header title. Default "Assistant". |
| `className` | `string` | Extra classes on the root. |
