Skip to content

Latest commit

 

History

54 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Embeddable RAG FAQ-Based AI Chatbot Widget - Next.js, Redis, Vectorize FAQ Seed, Multiple AI Models, Full-Stack Project

License: MIT Next.js React TypeScript Redis Node.js launch with diploi badge

A production-ready, self-hosted RAG (Retrieval Augmented Generation) chatbot widget built with Next.js, Upstash Redis vector storage, and a multi-provider AI fallback chain. Embed it in a portfolio site, SaaS dashboard, or any web app — with a React widget or a vanilla JS script.

Screenshot 2026-01-25 at 15 13 47 Screenshot 2026-01-25 at 15 13 59 Screenshot 2026-01-25 at 15 14 09 Screenshot 2026-01-25 at 15 14 27 Screenshot 2026-01-25 at 15 14 49 Screenshot 2026-01-25 at 15 14 58 Screenshot 2026-01-25 at 15 15 16


Table of Contents


What You Will Learn

By studying and running this project, you will learn:

  • How to build a RAG chatbot with vector search over a FAQ knowledge base
  • How to stream AI responses with Server-Sent Events (SSE) on Next.js Edge routes
  • How to store sessions and embeddings in serverless Redis (Upstash)
  • How to design a multi-provider AI fallback chain (Gemini → OpenRouter → Groq → Hugging Face → OpenAI)
  • How to embed a chat widget via React or vanilla JavaScript
  • How to manage client state with TanStack Query (cache, optimistic updates)
  • How to wire Sentry with same-origin tunneling to bypass ad blockers

Overview

This repository is a full-stack Next.js application that serves two roles:

  1. Hosted chatbot backend — API routes for chat, history, feedback, and FAQ seeding
  2. Embeddable UI — floating chat widget (React in layout.tsx, or public/widget.js for external sites)

Users ask questions in natural language. The system:

  1. Embeds the question as a vector
  2. Finds the top matching FAQs in Redis (cosine similarity)
  3. Injects that context into the LLM prompt
  4. Streams the answer back token-by-token
  5. Persists the conversation in Redis (30-day session cookie)

The default FAQ dataset contains 20 Q&A pairs about Arnob Mahmud (portfolio use case). Replace lib/faqs.ts with your own content for any domain.


Features

Core

Feature Description
RAG search Semantic FAQ retrieval via embeddings + cosine similarity
Streaming replies SSE from /api/chat — tokens appear as they generate
Session history HttpOnly cookie chatbot_session + Redis persistence
AI fallbacks Automatic provider/model chain if one API fails or rate-limits
Embedding fallbacks Gemini → Hugging Face → OpenRouter → OpenAI for vectors
Dual embed modes React widget (this repo) or standalone widget.js
Theme system Dark/light mode, zero-flash inline script in layout.tsx
Mobile UX Keyboard-aware positioning, responsive widget

Production extras

Feature Description
Security headers X-Frame-Options, Referrer-Policy, etc. in next.config.ts
Sentry (optional) Error tracking with /api/monitoring tunnel (ad-blocker safe)
CORS Cross-origin embed support with credentials on API routes
Edge runtime Fast chat/history/feedback on Vercel Edge

Technology Stack

Frontend

Library Version Role
Next.js 16.1.4 App Router, SSR layout, API routes
React 19.2.3 UI components, hooks
TypeScript 5.x Type safety
Tailwind CSS 3.4 Styling
TanStack Query 5.x Server state, chat cache key ["chat-history"]
Radix UI Accessible dialogs, menus, toasts
Lucide React Icons

TanStack Query in one sentence: It fetches /api/history on load, caches messages, and applies optimistic updates when you send a message — so the UI feels instant while the stream completes.

Backend

Library / Service Role
Upstash Redis Session JSON + FAQ vector hashes
Google Gemini Primary chat + embeddings
OpenRouter Free-tier model fallbacks (:free suffix)
Groq Fast OSS model fallbacks
Hugging Face Router Embedding + chat fallbacks
OpenAI Optional paid last resort
Vercel AI SDK (ai package) Streaming abstraction
@sentry/nextjs Optional error monitoring

Runtimes

Route Runtime
/api/chat, /api/history, /api/feedback Edge
/api/seed Node.js (longer embedding batch job)

How It Works (Architecture)

User message
    │
    ▼
┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│  Chat Widget    │────▶│  POST /api/chat  │────▶│  getSession()   │
│  (React / JS)   │◀────│  SSE stream      │◀────│  saveSession()  │
└─────────────────┘     └────────┬─────────┘     └─────────────────┘
                                 │
                    ┌────────────┼────────────┐
                    ▼            ▼            ▼
              searchFAQ()   getAIResponse()  Redis
              (RAG)         (fallback chain) (Upstash)
                    │            │
                    ▼            ▼
              generateEmbedding  Gemini / OpenRouter /
              + cosine search    Groq / HF / OpenAI

RAG step (lib/rag.ts):

// 1. Embed the user question
const queryEmbedding = await generateEmbedding(query);
// 2. Compare against all FAQ vectors in Redis
const results = await searchVectors(queryEmbedding, topK);
// 3. Format top matches as LLM context
return results
  .map((r) => `Q: ${r.metadata.question}\nA: ${r.metadata.answer}`)
  .join("\n\n");

Session cookie: New visitors get chatbot_session=sess_... (HttpOnly, SameSite=Lax, 30 days). The same ID loads history on return visits.


Project Structure

portfolio-chatbot-widget/
├── app/
│   ├── api/
│   │   ├── chat/route.ts       # POST — SSE streaming chat
│   │   ├── history/route.ts    # GET — load session messages
│   │   ├── feedback/route.ts   # POST — feedback form (logs; email TBD)
│   │   └── seed/route.ts       # POST — embed FAQs into Redis
│   ├── global-error.tsx        # Sentry global error boundary
│   ├── layout.tsx              # SSR shell + theme script + ChatbotWidget
│   ├── page.tsx                # Demo landing page
│   ├── providers.tsx           # TanStack Query + widget settings
│   └── robots.ts               # SEO / AI crawler rules
├── components/
│   ├── chatbot/
│   │   ├── chatbot-widget.tsx  # Main floating widget UI
│   │   ├── widget-menu.tsx     # Settings menu (theme, font, position)
│   │   └── message-skeleton.tsx
│   └── ui/                     # shadcn-style primitives (button, dialog, …)
├── contexts/
│   └── widget-settings-context.tsx  # Theme, font size, position (localStorage)
├── hooks/
│   ├── use-chat.ts             # TanStack Query + SSE sendMessage
│   └── use-widget-settings.ts
├── lib/
│   ├── ai/                     # Provider registry + streaming orchestrator
│   │   ├── providers.ts        # Model chains per provider
│   │   ├── index.ts            # getAIResponse() fallback loop
│   │   ├── gemini-stream.ts
│   │   ├── openai-stream.ts
│   │   ├── normalize-messages.ts
│   │   └── retriable.ts        # 429 / 5xx classification
│   ├── embeddings.ts           # Multi-provider embedding generation
│   ├── faqs.ts                 # FAQ knowledge base (edit this!)
│   ├── rag.ts                  # searchFAQ()
│   ├── redis.ts                # Sessions, vectors, cosine search
│   ├── sentry-env.ts           # DSN + tunnel path helpers
│   ├── sentry-filters.ts       # Ignore extension/browser noise
│   └── constants.ts
├── public/
│   ├── widget.js               # Vanilla embed script for external sites
│   └── styles.css              # Widget styles (shared)
├── docs/                       # Deployment, guardrails, integration guides
├── instrumentation.ts            # Sentry server/edge bootstrap
├── instrumentation-client.ts   # Sentry client + tunnel
├── sentry.server.config.ts
├── sentry.edge.config.ts
├── next.config.ts                # Security headers + Sentry wrapper
├── .env.example                  # All env vars (copy to .env.local)
└── SECURITY.md                   # Private vulnerability reporting

Prerequisites

  • Node.js 24.x (see .nvmrc and package.json engines)
  • npm (or pnpm/yarn)
  • Upstash Redis account (free tier works)
  • Google Gemini API key (primary AI)
  • Hugging Face token (embedding fallback — recommended)
  • Optional: OpenRouter, Groq, OpenAI keys for deeper fallback coverage
  • Optional: Sentry project for error monitoring

Installation & Setup

1. Clone and install

git clone https://github.com/arnobt78/portfolio-chatbot-widget.git
cd portfolio-chatbot-widget
npm install

2. Configure environment

cp .env.example .env.local

Edit .env.local — see Environment Variables below.

3. Seed FAQs (required before first chat)

npm run dev
# In another terminal (set SEED_SECRET in .env.local first):
curl -X POST http://localhost:3000/api/seed \
  -H "Authorization: Bearer $SEED_SECRET"

Expected response: {"success":true,"count":20}

4. Open the demo

Visit http://localhost:3000 and use the widget in the bottom-right corner.


Environment Variables

Copy from .env.example. Never commit .env.local.

Required (minimum to run chat)

Variable Description Where to get
UPSTASH_REDIS_URL Upstash REST URL console.upstash.com → Database → REST API
UPSTASH_REDIS_TOKEN Upstash REST token Same as above
GOOGLE_GEMINI_API_KEY Primary LLM + embeddings aistudio.google.com/apikey
HUGGING_FACE_API_KEY Embedding fallback huggingface.co/settings/tokens

Optional — AI fallbacks

Variable Description
OPENROUTER_API_KEY Free-tier models via OpenRouter (:free chain)
GROQ_API_KEY Groq OSS models
OPENAI_API_KEY Paid last-resort chat + embeddings

Alternate names supported: OpenRouter_API_KEY, Groq_Llama_API_KEY, Hugging_Face_Inference_API_KEY.

App / widget configuration

Variable Default Description
NEXT_PUBLIC_CHATBOT_URL http://localhost:3000 Public deploy URL (embed + API base)
NEXT_PUBLIC_SITE_URL OpenRouter HTTP-Referer fallback
CHATBOT_TITLE Chat Assistant Widget header title
CHATBOT_GREETING 👋 How can I help… First message bubble
CHATBOT_PLACEHOLDER Message... Input placeholder
SESSION_TTL 2592000 (30 days) Redis session TTL in seconds
SEED_SECRET Required — protects POST /api/seed
FEEDBACK_EMAIL Intended recipient (email sending not wired yet)

Optional — Sentry

Variable Description
NEXT_PUBLIC_SENTRY_DSN Browser DSN (required for client errors)
SENTRY_DSN Server alias (falls back to public DSN)
SENTRY_ORG Org slug — build-time source maps
SENTRY_PROJECT Project slug (not org name)
SENTRY_AUTH_TOKEN CI/Vercel auth token for source map upload

When DSN is empty, Sentry is disabled — no runtime overhead.

Example .env.local (minimal)

UPSTASH_REDIS_URL=https://xxxx.upstash.io
UPSTASH_REDIS_TOKEN=AXxxxx
GOOGLE_GEMINI_API_KEY=AIza...
HUGGING_FACE_API_KEY=hf_...
NEXT_PUBLIC_CHATBOT_URL=http://localhost:3000

Running the Project

Command Purpose
npm run dev Development server at http://localhost:3000
npm run build Production build (TypeScript + Next.js)
npm start Run production server locally
npm run lint ESLint

Node version: Use Node 24 (nvm use reads .nvmrc).


Seeding the FAQ Knowledge Base

The seed endpoint reads lib/faqs.ts, generates embeddings in batches, and stores vectors in Redis.

curl -X POST https://your-domain.com/api/seed \
  -H "Authorization: Bearer $SEED_SECRET"

SEED_SECRET must be set in .env.local / Vercel — requests without a valid secret return 401; if unset server-side, returns 503.

When to re-seed: After editing lib/faqs.ts or changing embedding models.

Customize FAQs: Edit the faqs array in lib/faqs.ts — each entry is [question, answer].

Production re-seed (Vercel Bot Protection)

With Bot Protection = Challenge enabled (recommended), raw curl from a cold terminal may return 429. Reseed is not blocked — use one of these:

Option A — Browser DevTools (easiest, no firewall changes)

  1. Open https://portfolio-chatbot-widget.vercel.app in Safari/Chrome.
  2. DevTools → Console:
fetch('/api/seed', {
  method: 'POST',
  headers: { Authorization: 'Bearer YOUR_SEED_SECRET' }
}).then(r => r.json()).then(console.log)
// Expected: { success: true, count: 20 }

Option B — Terminal curl (same machine)

  1. Visit the site in a browser first (passes the Vercel challenge cookie).
  2. Then run:
curl -X POST https://portfolio-chatbot-widget.vercel.app/api/seed \
  -H "Authorization: Bearer $SEED_SECRET"

Option C — Automated cron/CI only (optional)

Add a Vercel Firewall exception for POST /api/seed if you need hands-off reseed without a browser visit. Not required for normal use.


Usage & Embedding

Option A — React widget (this repo)

Already mounted in app/layout.tsx via <ChatbotWidget />. Config comes from env vars injected into window:

window.CHATBOT_BASE_URL = "https://your-app.vercel.app";
window.CHATBOT_TITLE = "Chat Assistant";
window.CHATBOT_GREETING = "👋 How can I help you today?";
window.CHATBOT_PLACEHOLDER = "Message...";

Option B — Vanilla JS on any website

On your external site (e.g. WordPress, static HTML):

<script>
  window.CHATBOT_BASE_URL = "https://portfolio-chatbot-widget.vercel.app";
  window.CHATBOT_TITLE = "Portfolio Assistant";
</script>
<script
  src="https://portfolio-chatbot-widget.vercel.app/widget.js"
  async
></script>

The script creates a floating button, loads /styles.css from your deployment, and calls the same /api/chat and /api/history endpoints with cookies.

Option C — Copy components into another Next.js app

  1. Copy components/chatbot/, hooks/use-chat.ts, contexts/widget-settings-context.tsx
  2. Copy public/styles.css
  3. Point CHATBOT_BASE_URL at your deployed API origin
  4. Ensure CORS + credentials work for your domain

API Reference

POST /api/chat

Stream a chat response.

Request:

{ "message": "Tell me about Arnob Mahmud" }

Headers: Cookie: chatbot_session=... (optional — created if missing)

Response: text/event-stream

data: {"response":"Hello"}
data: {"response":" there"}
data: [DONE]

Runtime: Edge


GET /api/history

Return messages for the current session cookie.

Response:

{
  "messages": [
    { "role": "user", "content": "Hi", "timestamp": 1700000000000 },
    { "role": "assistant", "content": "Hello!", "timestamp": 1700000001000 }
  ]
}

Runtime: Edge


DELETE /api/history

Clear the current session on the server and expire the session cookie. Used by Clear Chat / New Chat in the widget.

Response: { "success": true }

Runtime: Edge


POST /api/seed

Embed all FAQs from lib/faqs.ts into Redis. Requires auth:

Authorization: Bearer <SEED_SECRET>
# or
x-seed-secret: <SEED_SECRET>

Response: { "success": true, "count": 20 }

Runtime: Node.js


POST /api/feedback

Submit widget feedback or issue report.

Request:

{
  "type": "feedback",
  "rating": 5,
  "comment": "Great widget!",
  "email": "user@example.com"
}

Note: Logs to server console; email integration is TODO.

Runtime: Edge


All chat/history/feedback routes support CORS with Access-Control-Allow-Credentials: true for cross-origin embeds.


Frontend Architecture

useChat hook (hooks/use-chat.ts)

  • Query key: ["chat-history"] — loads history on mount
  • Optimistic update: User message appears immediately on send
  • Streaming: Parses SSE chunks and updates assistant message in cache
  • Rollback: On error, restores previous cache snapshot
const { messages, sendMessage, isLoading, clearChat } = useChat();

Widget settings (contexts/widget-settings-context.tsx)

Persists to localStorage:

  • Theme (light / dark)
  • Font size
  • Widget position (left / right)

Zero-flash theme

An inline <script> in layout.tsx runs before paint, reads localStorage, and sets CSS variables — preventing a white flash on dark mode.


Backend & AI Pipeline

Provider registry (lib/ai/providers.ts)

Chat providers are tried in order. Within each provider, models are tried until one succeeds. On HTTP 429, remaining models for that provider are skipped (fast-skip).

Order Provider Example models
1 Gemini gemini-2.5-flash, gemini-2.5-flash-lite
2 OpenRouter openai/gpt-oss-20b:free, …
3 Groq openai/gpt-oss-120b, …
4 Hugging Face openai/gpt-oss-20b, …
5 OpenAI gpt-4o-mini (paid)

See docs/LLM_MODEL_SELECTION.md for verification notes and update policy.

Embeddings (lib/embeddings.ts)

Used by RAG search and /api/seed. Fallback chain: Gemini → Hugging Face Router → OpenRouter free embedding → OpenAI.

Redis data model (lib/redis.ts)

Key pattern Content
chat:session:{id} JSON session with messages (TTL)
chat:vectors:faq-{n} Hash: vector, metadata (question + answer)

Vector search uses in-process cosine similarity over all FAQ keys (fine for ~20–100 FAQs; for larger scale, use RediSearch or a dedicated vector DB).


Reusing Components in Other Projects

Piece Reuse strategy
ChatbotWidget Drop into any Next.js layout.tsx with Providers
useChat Change CHATBOT_BASE_URL or pass custom API base
lib/faqs.ts Replace Q&A content — your domain knowledge
lib/ai/providers.ts Adjust model IDs when providers deprecate models
public/widget.js Zero-build embed for non-React sites
Integration guide docs/Redis_Sentry_PostHog_INTEGRATION_GUIDE.md — portable Redis/Sentry/PostHog patterns

Minimal external embed checklist:

  1. Deploy this app (or fork) with env vars set
  2. POST /api/seed once (with Authorization: Bearer $SEED_SECRET)
  3. Add widget.js + CHATBOT_BASE_URL to your site
  4. Ensure your domain is allowed by CORS (currently reflects request Origin)

Deployment

Vercel (recommended)

  1. Push to GitHub
  2. Import repo in vercel.com
  3. Add all env vars from .env.example
  4. Deploy — Node 24 picked up via engines field
  5. Post-deploy: re-seed using Production re-seed (browser DevTools or curl after visiting site)
  6. Set NEXT_PUBLIC_CHATBOT_URL to your Vercel URL

See docs/DEPLOYMENT.md for full steps including Vercel Firewall (Bot Challenge + AI Bots Deny).

Self-hosted

npm run build
npm start

Requires Node 24 and the same env vars.


Observability (Sentry)

Optional error tracking with ad-blocker-safe tunnel:

  • Client events POST to same-origin /api/monitoring
  • Server forwards to Sentry ingest
  • Noise filters drop browser extension errors

Setup details: docs/Redis_Sentry_PostHog_INTEGRATION_GUIDE.md


Documentation Index

Document Purpose
docs/PROJECT_WALKTHROUGH.md Agent/dev quick reference
docs/DEPLOYMENT.md Vercel/VPS deploy steps
docs/LLM_MODEL_SELECTION.md Free-tier model research
docs/VERCEL_PRODUCTION_GUARDRAILS.md Headers, robots, firewall
docs/Redis_Sentry_PostHog_INTEGRATION_GUIDE.md Portable integration patterns
docs/AGILE_V_PROTOCOL.md Project planning protocol
SECURITY.md Private vulnerability reporting

Known Limitations

Item Status
API rate limiting Not implemented at HTTP layer (AI layer has 429 fast-skip)
Feedback email Logs only — no Resend/SendGrid yet
PostHog analytics Documented as optional template, not wired in code
Large FAQ corpora In-memory cosine scan — migrate to vector DB at scale

Keywords

RAG · Retrieval Augmented Generation · Next.js App Router · Edge Runtime · Server-Sent Events · SSE streaming · Upstash Redis · vector search · cosine similarity · embeddings · Google Gemini · OpenRouter · Groq · Hugging Face · chatbot widget · embeddable widget · portfolio chatbot · TanStack Query · React 19 · TypeScript · Tailwind CSS · self-hosted AI · FAQ bot · Sentry tunnel · open source


Conclusion

This project demonstrates a complete, deployable RAG chatbot you can host yourself, customize with your own FAQs, and embed anywhere. The architecture prioritizes reliability (multi-provider fallbacks), performance (Edge streaming), and developer experience (typed hooks, clear API routes, portable docs).

Fork it, swap lib/faqs.ts, adjust branding env vars, and you have a production-grade assistant for your portfolio or product docs.


License

This project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.


Happy Coding! 🎉

This is an open-source project - feel free to use, enhance, and extend this project further!

If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com.

Security: Please report vulnerabilities privately via SECURITY.mdcontact@arnobmahmud.com.

About

A production-ready, self-hosted RAG (Retrieval Augmented Generation) chatbot widget built with Next.js, Redis vector storage, and multiple AI model fallbacks. Perfect for embedding into portfolio websites or any web application.

Topics

Resources

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages