npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rag-chatbot-plugin

v1.1.2

Published

Drop-in AI chatbot for any website: embeddable widget + built-in RAG backend (PDF folder + OpenAI) + enquiry/booking email notifications. Works with Next.js, Vite, Express, or standalone.

Readme

rag-chatbot-plugin

Drop-in AI chatbot for any website: embeddable widget + built-in RAG backend (PDF folder + your choice of AI provider) + enquiry/booking email notifications, with real-time streaming (word-by-word) replies. One package — works with Next.js, Vite, Express, or standalone. No SaaS, no external hosting — runs inside your own project.

Supported AI providers: OpenAI, Google Gemini, Claude (Anthropic), OpenRouter, Grok (xAI), DeepSeek, Groq, Ollama (local), or any OpenAI-compatible API (custom). Provider, key, and model are all set in .env.

User question → Widget → /api/chatbot/chat (streamed) → [embed query → search PDF chunks →
context + question → AI provider] → answer, word-by-word
                                   ↘ (enquiry/booking intent) → Email via SMTP

Your API key stays server-side (.env) — it is never sent to the browser.


Step-by-step setup

These steps are written in order — follow them in sequence the first time. The two most common setup mistakes (both covered below) are forgetting to actually render the widget component after importing it, and using the wrong API route file name in Next.js — both fail silently (no crash, the chatbot just doesn't appear or 404s), so it's worth reading through once even if it looks obvious.

Step 1 — Install

npm install rag-chatbot-plugin

Step 2 — Create your config file

cp node_modules/rag-chatbot-plugin/chatbot.config.example.js ./chatbot.config.js

This file must live at your project root (same level as package.json), not inside src/ or app/.

Step 3 — Set up your .env

cp node_modules/rag-chatbot-plugin/.env.example ./.env

Then fill in at minimum:

AI_PROVIDER=gemini          # openai | gemini | claude | openrouter | grok | groq | ollama | custom
AI_API_KEY=your-key-here

If you're on Next.js, use .env.local instead of (or in addition to) .env — Next.js loads both, but keep the variable names exactly as shown above. A common mistake: renaming SMTP_PASS to something else like SMTP_PASSWORD in your .env without also updating chatbot.config.js to read the new name — the plugin only reads the exact names documented in .env.example, and a silently-undefined value doesn't throw, it just makes SMTP auth fail at send time.

PDF search needs an embeddings API, which only openai / gemini / ollama provide. If you chat with claude, openrouter, grok, or groq, add an embeddings provider too (Gemini has a free tier):

AI_PROVIDER=claude
ANTHROPIC_API_KEY=sk-ant-...
EMBED_PROVIDER=gemini
GEMINI_API_KEY=...

Provider defaults: openai → gpt-4o-mini, gemini → gemini-2.5-flash, claude → claude-sonnet-4-5, openrouter → openai/gpt-4o-mini, grok → grok-3-mini, deepseek → deepseek-chat, groq → llama-3.3-70b-versatile, ollama → llama3.2 (local, no key needed).

Step 4 — Add your PDFs

mkdir docs
# copy the PDFs you want the bot to answer from into ./docs

⚠️ Only put PDFs here that you're comfortable being searchable and quotable by anyone who chats with the widget. This folder gets fully indexed and answered from — it's meant for product info, FAQs, pricing sheets, etc., not invoices, receipts, contracts, or any personal/financial documents. There's no access control on RAG content; if it's in docs/, a visitor can potentially get it quoted back to them.

Step 5 — Wire up the backend route + the widget (pick your framework)

This is the step most setup mistakes happen at — both halves are required: the backend route handles chat requests, the widget is what actually renders on the page. Missing either one means "nothing happens" with no error.

Next.js (App Router)

Backend route — the file must be at this exact catch-all path, not app/api/chatbot/route.js:

// app/api/chatbot/[...path]/route.js   <-- note the [...path] segment, required
import { createChatbotHandler } from 'rag-chatbot-plugin/next';
const handler = createChatbotHandler();
export const GET = handler;
export const POST = handler;

Without [...path], Next.js only matches the exact base path (/api/chatbot) and silently 404s every real route the plugin uses (/api/chatbot/widget.js, /api/chatbot/chat, etc.) — no build error, no warning, it just doesn't work.

Widget — import and render it (importing alone does nothing):

// app/layout.js (or .tsx)
import { ChatWidget } from 'rag-chatbot-plugin/react';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <ChatWidget apiUrl="/api/chatbot" />   {/* <-- this line is the part people forget */}
      </body>
    </html>
  );
}

Double-check after wiring both up: open your browser's dev tools console and run window.RagChatbot — if that's undefined, the widget script never mounted (check the Network tab for a request to /api/chatbot/widget.js and see what it returned).

Vite (React/Vue/Svelte)

// vite.config.js
import ragChatbot from 'rag-chatbot-plugin/vite';
export default defineConfig({ plugins: [react(), ragChatbot()] });
<!-- index.html -->
<script src="/api/chatbot/widget.js" defer></script>

Production build has no server: npx chatbot-serve --static ./dist

Express / any Node app

import { chatbotMiddleware } from 'rag-chatbot-plugin/express';
app.use(await chatbotMiddleware());

Or fully automatic sidecar (any Node app, any framework, zero routing setup):

import 'rag-chatbot-plugin/auto';   // self-starts on port 4757

Plain HTML / PHP / WordPress / old sites

npx chatbot-serve            # in a folder with chatbot.config.js + docs/
<script src="http://yourserver:4757/widget.js" defer></script>

Keep alive in production: pm2 start "npx chatbot-serve" --name chatbot

Step 6 — Verify it's actually working

curl http://localhost:<port>/api/chatbot/health

Should return {"ok":true,"pdfs":[...],"chunks":N,"emailEnabled":true|false}. If chunks is 0, either docs/ is empty or indexing hasn't finished yet (check server logs — first-run embedding takes a few seconds per PDF).

Then open the page in a browser, click the chat launcher (bottom-right by default), and send a message. If the reply comes back word-by-word rather than all at once, streaming is working (see below).


Streaming responses

Replies stream in word-by-word (like ChatGPT) instead of appearing all at once — no config flag needed, it's on by default. The widget requests streaming via Accept: text/event-stream, and automatically falls back to a single buffered JSON response if a proxy or older deployment strips SSE support, so it degrades gracefully rather than breaking.

If you're calling POST {basePath}/chat directly (not through the widget) and want streaming yourself, send Accept: text/event-stream and parse Server-Sent Events:

event: delta
data: {"text":"partial reply chunk"}

event: done
data: {"reply":"full final reply","emailSent":true}

Omit that header (or don't set Accept at all) for the plain JSON response: { "reply": "...", "emailSent": true }.

Configuration

Everything lives in chatbot.config.js (see chatbot.config.example.js for the full annotated version). Required: apiKey (or AI_API_KEY in .env), pdfFolder. Highlights:

| Section | What you control | |---|---| | AI | provider, apiKey, model, embeddings (separate provider), temperature, maxTokens, systemPrompt, language ('auto' mirrors visitor) | | rag | chunkSize, chunkOverlap, topK, minScore, strictMode, watchFolder | | email | SMTP, to, from, triggers (enquiry/booking/quote/callback), requireContact, ccVisitor, sessionSummary | | server | port, basePath, cors, rateLimit, adminToken (enables /reindex) | | widget | botName, welcomeMessage, placeholder, position, avatarUrl, openOnLoad, full theme |

Widget theming

widget: {
  theme: {
    preset: 'dark',            // 'light' | 'dark'
    primaryColor: '#e11d48',   // brand color (launcher, header, user bubbles)
    borderRadius: '16px',
    // fine-grained: background, surface, text, botBubble, fontFamily, onPrimary
  }
}

Per-page overrides without touching config — data-attributes on the script tag:

<script src="/api/chatbot/widget.js" defer
        data-bot-name="Sam"
        data-theme="dark"
        data-primary-color="#059669"
        data-position="bottom-left"
        data-welcome="Namaste! Kaise madad karun?"></script>

JS API: RagChatbot.open(), RagChatbot.close(), RagChatbot.toggle().

Email / lead capture

The model is given a send_enquiry_email tool. When a visitor shows enquiry/booking intent, the bot first collects their contact (requireContact: true), then the plugin emails the lead (with conversation transcript) to email.to via SMTP. If SMTP fails, the lead is saved to chatbot-leads.jsonl — never lost.

API endpoints

| Method | Path | Purpose | |---|---|---| | GET | {basePath}/widget.js | Widget script (config injected) | | POST | {basePath}/chat | { sessionId, pageUrl, messages } → SSE stream if Accept: text/event-stream, else { reply, emailSent? } | | GET | {basePath}/health | { ok, pdfs, chunks, emailEnabled } | | POST | {basePath}/session-end | Visitor closed chat/left page — sends owner a session summary email (if email.sessionSummary.enabled) | | POST | {basePath}/reindex | Rebuild index (x-admin-token header required) |

Troubleshooting

  • Widget doesn't appear at all: check you actually rendered <ChatWidget />, not just imported it (Next.js/React). Check the Network tab for a request to .../widget.js — a 404 there usually means the backend route isn't wired up correctly (Next.js: wrong file path, missing [...path]).
  • GET /health 404s but other pages load fine: same route-wiring issue — recheck Step 5 for your framework.
  • Email doesn't send but no error in the chat: check server logs for the actual SMTP error — a bad host/port/password fails at send time, not at startup, and gets caught + written to chatbot-leads.jsonl as a fallback rather than crashing.
  • "API key missing" on startup: AI_API_KEY (or the provider-specific env var) isn't set, or your .env/.env.local isn't in the project root.
  • Chat replies with a 429 / quota error: that's your AI provider's rate limit, not a plugin bug — check your provider's dashboard.

Notes

  • Index is cached in pdfFolder/.chatbot-index.json — indexing + embedding happens once, not per restart. Commit it to skip re-indexing on deploy (useful on Vercel).
  • watchFolder: true re-indexes automatically when PDFs change (dev).
  • Any OpenAI-compatible API works via AI_PROVIDER=custom + AI_BASE_URL (Azure, Together, LM Studio...).
  • Singleton by design: backend starts once per process; widget mounts once even if the script is included twice.
  • Node >= 18 required. Dependencies: unpdf, nodemailer only.