## Overview

The AI assistant adds a chat dialog to your documentation site. Users can click the sparkle icon in the header to open the dialog and ask questions about the documentation content.

The assistant uses the full documentation text (generated by the [llms.txt integration](https://zudo-doc.takazudomodular.com/docs/guides/llms-txt)) as context, so it can answer questions about any page on the site.

## Enabling the Assistant

Set `aiAssistant` to `true` in `zfb.config.ts`:

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    aiAssistant: true,
    // ...
  }),
);
```

When enabled:

- A sparkle icon appears in the header bar
- The `POST /api/ai-chat` endpoint becomes available as a Cloudflare Workers SSR route

[Dynamic content unavailable in text export]

The production handler, `worker-entry.ts`, and `AiChatDailySpendCap` class described below belong
to this showcase repository; they are not exported by `@takazudo/zudo-doc` or added by a fresh
scaffold. The package-owned SSR route is a safe seam: it returns the fixed demo response while
demo mode is enabled and HTTP 501 if live mode is selected without a host handler. Downstream
projects must keep demo mode enabled or implement their own host `/api/ai-chat` handler, source
Worker entry, Durable Object class, and migration. Adding bindings alone does not activate live AI.



## Security Warning for Non-Demo Deployments

[Dynamic content unavailable in text export]

Setting `aiChatDemoMode: false` exposes a real Anthropic API key endpoint. An unsecured
deployment is at risk for cost abuse via unauthenticated API calls. Before going live:

1. **Restrict CORS origins** — set `aiChatAllowedOrigins` to your deployed docs URL(s).
   The default empty array blocks all cross-origin browser requests.
2. **Add an exact paid-call admission cap** — set `aiChatGlobalDailyLimit` to cap Anthropic
   fetch attempts per UTC day across all IPs. A SQLite Durable Object serializes concurrent
   decisions, so they cannot overshoot the configured value.
3. **Deploy behind Cloudflare** — `cf-connecting-ip` (used for per-IP rate limiting) is only
   trustworthy when the request passes through Cloudflare's network.

When `aiChatDemoMode` is `false`, both guards fail closed. A per-IP KV read failure returns HTTP
429, while missing or failing exact-cap infrastructure returns HTTP 500 before Anthropic is called.



```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    aiChatDemoMode: false,
    // Allow only your deployed docs site to make cross-origin requests.
    aiChatAllowedOrigins: ["https://your-docs-site.example.com"],
    // Exact UTC-day paid-call admission cap (false = no exact cap).
    aiChatGlobalDailyLimit: 500,
    // ...
  }),
);
```

## Environment Setup

In this showcase repository, the chat endpoint runs as a Cloudflare Workers SSR route
(`pages/api/ai-chat.tsx`) and reads its configuration from Cloudflare env bindings set in
`wrangler.toml`. Downstream implementations can use this graph as a reference, but these
host-owned files are not package APIs.

### Required CF bindings

**Secret** — set via `wrangler secret put ANTHROPIC_API_KEY`:

```env
ANTHROPIC_API_KEY=sk-ant-...
```

**Vars** — configure in `wrangler.toml`:

```toml title="wrangler.toml"
[vars]
DOCS_SITE_URL = "https://your-docs-site.workers.dev"
RATE_LIMIT_PER_MINUTE = "10"
RATE_LIMIT_PER_DAY = "100"
```

**KV namespace** — soft per-IP rate limiting and privacy-safe outcome audit records (no prompt,
response, IP, or IP hash fields):

```sh
wrangler kv namespace create RATE_LIMIT
```

Paste the returned `id` into the `[[kv_namespaces]]` block in `wrangler.toml`.

**Durable Object namespace** — exact UTC-day paid-call admission:

```toml title="wrangler.toml"
[[durable_objects.bindings]]
name = "AI_CHAT_DAILY_SPEND_CAP"
class_name = "AiChatDailySpendCap"

[[migrations]]
tag = "v1-ai-chat-daily-spend-cap"
new_sqlite_classes = ["AiChatDailySpendCap"]
```

Keep these names exact. The first `wrangler deploy` applies the Worker migration; this is not a
`wrangler d1 migrations` operation. Production uses custom `worker-entry.ts`, which preserves the
generated adapter handler from `dist/_worker.js`, its `dist/_zfb_inner.mjs` sidecar, and the
exported Durable Object class. Always build before Wrangler.

Each UTC date routes to a new object, so the cap resets at `00:00 UTC`. Admission occurs after
request checks, the softer eventually-consistent per-IP KV guard, and docs-context preparation,
immediately before exactly one Anthropic fetch. An admitted slot is not refunded if that fetch
fails and is not provider-confirmed billing. `aiChatGlobalDailyLimit: false` skips only this exact
cap; `aiChatDemoMode: true` skips KV, the Durable Object, and Anthropic entirely.

Preview aliases use an adapter-only service/config because versions implementing Durable Objects
do not receive preview URLs. They prove the SSR adapter/assets path, not live paid-call admission;
production `wrangler deploy` owns the migration. See Cloudflare's
[Durable Object migrations](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/),
the [Cloudflare Wisdom Durable Objects article](https://github.com/Takazudo/zudo-cloudflare-wisdom/blob/main/src/content/docs/workers/durable-objects.mdx),
and [Workers Logs structured-object guidance](https://developers.cloudflare.com/workers/observability/logs/workers-logs/).

Uses `claude-haiku-4-5-20251001` for fast, low-cost responses.

## Chat Dialog

The dialog is a package-owned client island (`@takazudo/zudo-doc/ai-chat-modal`) using the native `<dialog>` element. This showcase mounts it via `pages/lib/_body-end-islands.tsx` (the `BodyEndIslands` chrome-bindings slot).

### Layout

- **Narrow viewports** (below `lg`/1024px): Full viewport width and height
- **Wide viewports** (1024px and above): Centered, 90vw/90vh with a max width of 52.5rem, with a border

### Features

- Balloon-style message bubbles (user on right, assistant on left)
- Markdown rendering in assistant responses (bold, italic, code, lists, links)
- "Thinking..." indicator during API calls
- Error messages displayed inline
- Backdrop click or Escape to close
- Conversation resets on close

## API Reference

For the full endpoint specification (request/response types, error codes, environment variables), see the [AI Assistant API reference](https://zudo-doc.takazudomodular.com/docs/reference/ai-assistant-api).

## File Structure

```
pages/
└── api/
    ├── ai-chat.tsx               # CF Workers SSR endpoint (prerender = false)
    ├── _ai-chat-admission.ts     # Exact UTC-day paid-call admission cap
    ├── _ai-chat-audit.ts         # Privacy-safe outcome audit logging
    ├── _ai-chat-client.ts        # Claude API client (raw fetch)
    ├── _ai-chat-cors.ts          # CORS origin allowlist
    ├── _ai-chat-observability.ts # Structured operational logging
    ├── _ai-chat-payload.ts       # Request/response payload builders
    ├── _ai-chat-rate-limit.ts    # Per-IP KV rate limiting
    ├── _ai-chat-screening.ts     # Prompt-injection screening
    └── _ai-chat-types.ts         # Shared route types
src/
└── types/
    └── ai-chat.ts                # ChatMessage, AiChatRequest/Response types
worker-entry.ts                   # Production Worker entry (wraps dist/_worker.js)
```

The chat dialog island (`@takazudo/zudo-doc/ai-chat-modal`) and the chat-message
markdown renderer (`@takazudo/zudo-doc/render-markdown`) are package-owned —
not part of this repository's file tree.