## AI Composer (S—031)

A chat surface for AI products: streaming replies, stop, retry, model picker, attachments and an auto-growing input.

- URL: https://sundaeui.dev/components/ai-composer
- Category: UI components · Art direction: Understated
- Access: Sundae Pro (authenticated install)
- Stack: React 18+, TypeScript, Tailwind CSS; dependencies: none
- Install: `npx shadcn@latest add https://sundaeui.dev/r/ai-composer.json`
- Tags: chat, tailwind, saas, streaming, react, composer, prompt, ai

Everything an AI chat needs, and nothing it doesn’t. Replies stream in token by token with a live cursor, and the send button becomes Stop while a response is generating. If a request fails, the error appears inline with a Retry that replays the last prompt with its history.
The composer grows with its content up to a comfortable limit. Enter sends, Shift + Enter adds a line, and IME composition is respected, so Japanese and Chinese input won’t send by accident. Attach up to five files as removable chips, switch models from the toolbar, and start from suggestions when the conversation is empty.
Bring any model. onSend gets the text, model, files and history, a token callback and an AbortSignal, so it works with fetch streams, the Vercel AI SDK, or your own server.

### Usage

```tsx
import { AiComposer } from '@/components/sundae/ai-composer'

<AiComposer
  models={[{ id: 'balanced', name: 'Balanced' }]}
  onSend={async ({ text, history }, onToken, signal) => {
    const res = await fetch('/api/chat', { method: 'POST', body: JSON.stringify({ text, history }), signal })
    const reader = res.body!.getReader()
    const decoder = new TextDecoder()
    for (;;) {
      const { value, done } = await reader.read()
      if (done) break
      onToken(decoder.decode(value))
    }
  }}
/>
```

### Props

- `models` (`{ id, name, hint? }[]`, required): Options for the model picker.
- `onSend` (`(input, onToken, signal) => Promise<void>`, required): Stream a reply. Throw to show Retry, and stop when the signal aborts.
- `suggestions` (`string[]`): Starter prompts for the empty state.
- `placeholder` (`string`): Composer placeholder.

### Accessibility

- The conversation is a polite role="log", so streamed text is announced as it settles.
- Send, Stop, Attach and Model all have labels. Errors use role="alert".
- Enter to send respects IME composition.
