@fixmanhr/chat-widget
v0.1.1
Published
Plug-and-play AI chat widget for Next.js. Bring your own system prompt, knowledge base, and lead capture callback.
Maintainers
Readme
@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
npm install @fixmanhr/chat-widget @anthropic-ai/sdkQuick start (Next.js App Router)
1. Create the API route
// 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
// 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
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:
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:
onLead: async ({ name, email, company, locale, timestamp }) => {
await resend.emails.send({ to: '[email protected]', subject: `Lead: ${company}`, ... });
await db.leads.insert({ name, email, company });
}Environment variables
ANTHROPIC_API_KEY=sk-ant-...