# AI Chat

> The AI kit's shared vocabulary: message, tool call and model types, token and duration formatting, live timers and a mock stream.

- Type: React helper (Hooks & Utilities)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add ai-chat`
- Docs: https://wingo-ui.com/components/ai-chat

## Interaction

Press Generate to stream a fake answer through mockStream: the text arrives word by word (or letter by letter with chunk char), the token estimate grows, and the elapsed time ticks in formatDuration and formatTimer. Stop aborts the stream through its signal. Below, the capability icons and the tool-call status tones every AI component uses.

## Installation

```bash
npx wingo-ui@latest add ai-chat
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/ai-chat` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `lib/ai-chat.ts`

npm dependencies: `npm i lucide-react`

Wingo UI dependencies (installed automatically):

- [Number format](https://wingo-ui.com/components/number-format) ([Markdown](https://wingo-ui.com/components/number-format.md))
- [Relative time](https://wingo-ui.com/components/relative-time) ([Markdown](https://wingo-ui.com/components/relative-time.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))

## Usage

```tsx
"use client"

import { useState } from "react"
import { estimateTokens, formatTokens, mockStream } from "@/lib/ai-chat"
import { Button } from "@/components/ui/button"

export function FakeAnswer() {
  const [text, setText] = useState("")

  const generate = async () => {
    setText("")
    for await (const delta of mockStream("You have 3 overdue invoices, $22,775.95 in total.")) setText((t) => t + delta)
  }

  return (
    <div className="flex flex-col gap-3">
      <Button onClick={generate}>Generate</Button>
      <p>{text}</p>
      <span className="text-xs text-muted-foreground">{formatTokens(estimateTokens(text), "en-US")} tokens</span>
    </div>
  )
}
```

## API

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `delay?` | `number` | `28` | mockStream(text, { delay, chunk, firstDelay = 350, signal }): an async generator that yields text in chunks every `delay` ms, like a model streaming. for await (const delta of mockStream(answer, { signal })) setText((t) => t + delta). Aborting the signal ends it. |
| `chunk?` | `"word" \| "char"` | `"word"` | mockStream: whole words with their spaces (how models stream) or one character at a time. |
| `locale?` | `string` | `"en-US"` | The locale passed to formatTokens and formatDuration in the demo (every formatter takes one). |
| `compact?` | `boolean` | `true` | formatTokens(count, locale?, { compact = true }): 812, "12.5K", "1.3M" in en-US; compact false writes every digit ("12,480"). |
| `ChatMessageData?` | `{ id; role: ChatRole; content: string; status?: ChatStatus; createdAt?; author?: ChatPerson; model?; attachments?: ChatAttachment[]; reasoning?: ChatReasoning; toolCalls?: ToolCall[]; sources?: ChatSource[]; error?; feedback?: ChatFeedback; edited?; branch?: { index; count } }` |  | One message of a conversation; content is markdown. ChatRole = "user" \| "assistant" \| "system"; ChatStatus = "pending" \| "streaming" \| "complete" \| "stopped" \| "error"; ChatFeedback = "up" \| "down" \| null. ChatAttachment = { id; name; type?; size?; url?; previewUrl?; status?: "uploading" \| "ready" \| "error"; progress? (0..1); error? }. ChatSource = { id; title; url?; snippet?; favicon? }. ChatReasoning = { content; status?; startedAt?; endedAt?; durationMs? }. ChatPerson = { id; name; avatar? }. |
| `ToolCall?` | `{ id; name; title?; description?; status: ToolCallStatus; input?; output?; error?; startedAt?; endedAt?; durationMs?; logs?: string[]; steps?: ToolCall[] }` |  | One tool call or agent step. ToolCallStatus = "pending" \| "running" \| "complete" \| "error" \| "canceled" \| "awaiting-approval". TOOL_CALL_STATUS_TONES maps them to tones (neutral, info, success, danger, neutral, warning) and DEFAULT_TOOL_CALL_STATUS_LABELS to English words. getToolCallTitle(call) and humanizeToolName("search_invoices") give "Search invoices"; summarizeToolCalls(calls) counts total / running / complete / error / pending / awaiting, sub-steps included; stringifyToolValue(value) prints inputs and outputs (strings as they are, the rest as indented JSON). |
| `AiModel?` | `{ id; name; provider?; description?; icon?; group?; capabilities?: AiCapability[]; contextWindow?; speed?: 1 \| 2 \| 3; cost?: 1 \| 2 \| 3; pricing?: { input; output; currency? }; badge?: { label; tone? }; recommended?; isNew?; disabled?; disabledReason?; keywords? }` |  | A model for pickers. AiCapability = "vision" \| "tools" \| "reasoning" \| "web" \| "files" \| "code" \| "image" \| "voice", with AI_CAPABILITIES (the order), AI_CAPABILITY_ICONS (lucide) and DEFAULT_AI_CAPABILITY_LABELS. Pricing is per 1M tokens. |
| `ChatSuggestion?` | `{ id; label; prompt?; description?; icon?; group? }` |  | A suggested prompt; prompt defaults to label. |
| `estimateTokens?` | `(text: string) => number` |  | A fast estimate for hints and meters: about 4 characters per token for Latin text, one per CJK character. Real counts come from the provider. |
| `formatDuration?` | `(ms: number, { locale?, style?: "long" \| "short" \| "narrow" }) => string` |  | "2.4 sec", "42 sec", "2 min, 5 sec", "1 hr, 12 min" in en-US: tenths below 10 s, then at most two whole units, through Intl unit formatting. |
| `formatTimer?` | `(ms: number) => string` |  | A stopwatch: "0:42", "12:05", "1:02:03" (recordings, an agent's run time). |
| `getElapsed?` | `({ startedAt?, endedAt?, durationMs? }, now?) => number \| null` |  | ms of a reasoning block, tool call or run: durationMs if given, else end (or now while running) minus start; null when the start is unknown. |
| `useTicker?` | `(running: boolean, interval = 1000) => number \| null` |  | A clock for live timers that ticks only while running and shares one interval per step across components. null on the server and during hydration (show nothing or the final duration), then Date.now() rounded to the step. |
| `createChatId?` | `(prefix = "msg") => string` |  | A unique id for messages, attachments and calls created in the browser. Call it in handlers, never during render. |

## Examples

- **Agent steps**: A tool call list with status tones, durations and a summary header, built only from the kit's helpers and types.

## See also

- [Relative time](https://wingo-ui.com/components/relative-time) ([Markdown](https://wingo-ui.com/components/relative-time.md))
- [Number format](https://wingo-ui.com/components/number-format) ([Markdown](https://wingo-ui.com/components/number-format.md))
- [useStickToBottom](https://wingo-ui.com/components/use-stick-to-bottom) ([Markdown](https://wingo-ui.com/components/use-stick-to-bottom.md))
- [Fluid Orb](https://wingo-ui.com/components/fluid-orb) ([Markdown](https://wingo-ui.com/components/fluid-orb.md))
- [Matrix Orb](https://wingo-ui.com/components/matrix-orb) ([Markdown](https://wingo-ui.com/components/matrix-orb.md))

---

Canonical: https://wingo-ui.com/components/ai-chat
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
