npm package: @fixmanhr/chat-widget
Description: Plug-and-play AI chat widget for Next.js. Bring your own system prompt, knowledge base, and lead capture callback.
Latest version: 0.1.1

# @fixmanhr/chat-widget

Plug-and-play AI chat widget for Next.js (App Router).
Bring your own system prompt, knowledge base, and lead capture callback.

- Zero styling dependencies — uses inline styles, works in any project
- Streams responses from Claude (Anthropic)
- Built-in knowledge base retrieval (keyword search, upgradeable to embeddings)
- Optional lead capture: AI collects name/email/company and calls your callback
- Rate limiting per IP out of the box
- TypeScript-first, fully typed

---

## Installation

```bash
npm install @fixmanhr/chat-widget @anthropic-ai/sdk
```

---

## Quick start (Next.js App Router)

### 1. Create the API route

```ts
// app/api/chat/route.ts
import { createChatHandler } from '@fixmanhr/chat-widget/server';

export const POST = createChatHandler({
  apiKey: process.env.ANTHROPIC_API_KEY!,

  systemPrompt: `
    You are the official assistant for AcmeCorp.
    Only answer questions about AcmeCorp products.
    If you don't know the answer, say so and offer to connect them with support.
    Answer in the same language as the user.
  `,

  knowledgeBase: [
    {
      id: 'about',
      title: 'About AcmeCorp',
      content: 'AcmeCorp makes enterprise software for...',
      tags: ['about', 'overview', 'what is'],
      category: 'overview',
    },
  ],

  // Called when AI collects a demo/contact lead
  onLead: async (lead) => {
    console.log('New lead:', lead);
    // send email, save to DB, call CRM, etc.
  },
});
```

### 2. Add the widget to your layout

```tsx
// app/[locale]/layout.tsx  (or app/layout.tsx)
import { ChatWidget } from '@fixmanhr/chat-widget';

export default function Layout({ children, params }) {
  return (
    <html>
      <body>
        {children}
        <ChatWidget
          locale={params.locale}
          brandName="AcmeCorp"
          primaryColor="#6366f1"
          welcomeMessages={{
            en: "Hi! I'm the AcmeCorp assistant. How can I help?",
            de: "Hallo! Ich bin der AcmeCorp Assistent.",
          }}
        />
      </body>
    </html>
  );
}
```

---

## ChatWidget props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `apiUrl` | `string` | `'/api/chat'` | Your chat API endpoint |
| `locale` | `string` | `'en'` | Active locale (selects welcome message) |
| `brandName` | `string` | `'Assistant'` | Name shown in header |
| `primaryColor` | `string` | `'#f26961'` | Button, avatar, user bubble color |
| `accentColor` | `string` | `'#2ca1da'` | Unread notification dot color |
| `welcomeMessages` | `Record<string, string>` | — | Welcome text per locale |
| `inputPlaceholder` | `string \| Record<string, string>` | — | Textarea placeholder |
| `subtitle` | `string` | `'Ask me anything'` | Header subtitle line |

---

## createChatHandler options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `apiKey` | `string` | **required** | Anthropic API key |
| `systemPrompt` | `string \| fn` | **required** | Your assistant rules. String or `(userMessage, context) => string` |
| `knowledgeBase` | `KnowledgeDocument[]` | `[]` | Docs for context retrieval |
| `model` | `string` | `claude-haiku-4-5-20251001` | Claude model ID |
| `maxTokens` | `number` | `1024` | Max tokens per reply |
| `rateLimitMax` | `number` | `20` | Max requests per IP per window |
| `rateLimitWindowMs` | `number` | `60000` | Rate limit window (ms) |
| `onLead` | `(lead: LeadData) => void` | — | Lead capture callback |
| `onQuestion` | `(event: AnalyticsEvent) => void` | — | Analytics hook |

---

## Knowledge base format

```ts
interface KnowledgeDocument {
  id: string;
  title: string;     // weighted 3x in search
  content: string;   // weighted 1x
  tags: string[];    // weighted 2x
  category: string;
}
```

Context retrieval works by keyword scoring. To upgrade to semantic search (embeddings + vector DB), pass a function as `systemPrompt`:

```ts
systemPrompt: async (userMessage, _) => {
  const context = await myVectorSearch(userMessage); // your embeddings call
  return `You are the AcmeCorp assistant.\n\n## Context:\n${context}`;
}
```

---

## Lead capture

If you pass `onLead`, the AI is automatically instructed to collect name, email, and company when a user asks for a demo. Once collected, your callback fires:

```ts
onLead: async ({ name, email, company, locale, timestamp }) => {
  await resend.emails.send({ to: 'you@company.com', subject: `Lead: ${company}`, ... });
  await db.leads.insert({ name, email, company });
}
```

---

## Environment variables

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