Bolnee-Chat
Chatbot integration in your business website. Self hosted, free forever!
Bolnee-Chat is a self-hosted RAG chatbot platform. Create a bot, add your website + PDFs as knowledge, pick any OpenAI-compatible provider (OpenRouter, OpenAI, Groq, Ollama, vLLM), and embed a 2-line snippet. Answers are grounded in your sources with citations, visitor chats are grouped and exportable, and everything runs on your infrastructure with SQLite.
Self-Hosted & Free ForeverNo vendor lock-in, no per-message billing. Run on your server, Vercel or Cloudflare Pages — SQLite, no external DB. |
RAG-Grounded AnswersCrawls your site + ingests PDFs/TXT/MD/DOCX, chunks to SQLite FTS and builds grounded prompts. Sources are cited, fallback is configurable. |
2-Line Embed, Any StackCopy |
Bot Overview — Stats & SnippetPer-bot: |
|
Appearance — Live PreviewEdit bot name, avatar (upload/preview), accent/background colour, theme |
|
Chats — Grouped by VisitorGrouped by |
|
Settings — Provider, Prompts, Danger Zone
|
|
Widget — Floating, Theme-Aware, PersistentBottom-right bubble → sliding window ( |
|
Also in the box:
locator · type · status · error · date with delete; append more via Add knowledge./api/public/avatar/:id (2MB limit, data/avatars/).apiKey/baseUrl/model via AES-256-GCM, never exposed in snippet.#020617 bg, #1e293b cards, slate-800 inputs, no white surfaces.
1. Create Chatbot — Brand in SecondsName + avatar (PNG/JPG/WEBP ≤2MB, preview) → stored as |
|
2. Add Knowledge — Website + FilesURL-only, files-only, or both. Same-origin crawler ( |
|
3. Configure Provider — Any OpenAI-Compatible APIPick provider → Base URL auto-fills → paste API key → Fetch models lists live models (prioritizes |
|
4. Embed — 2 LinesCopy from Overview → Embed code or Knowledge → Step 4. Auto-configures origin via |
|
| Requirement | Details |
|---|---|
| OS | macOS, Linux, Windows (WSL) |
| Runtime | Node.js 18+ |
| Package Manager | npm |
| Python | 3.10+ (crawler, optional but recommended) |
| Python Deps | aiohttp, beautifulsoup4, lxml, requests, brotli (+ playwright for JS-heavy sites) |
# Clone
git clone https://github.com/AniketWathore/bolnee-chat.git
cd bolnee-chat
# Env
cp .env.example .env
# edit .env — simplest self-hosted:
# DISABLE_AUTH=true
# JWT_SECRET=change-me-32-chars
# LLM_BASE_URL=https://openrouter.ai/api/v1 # optional global fallback
# LLM_API_KEY=sk-or-v1-... # optional global fallback
# LLM_MODEL=inclusionai/ling-3.0-flash-fin:free
# Install
npm install
# Python crawler deps (optional)
pip install aiohttp beautifulsoup4 lxml requests brotli
# Verify
npm run lint # tsc --noEmit
npm run build # vite + esbuild → dist/
npm run dev # http://localhost:3000 (auto-fallback to 3001 if busy)
SQLite is created at data/bolnee.db (git-ignored). Crawled sites → data/{chatbotId}_website.json, chunks in SQLite.
+ New chatbot → name + avatar (preview ≤2MB).https://your-site.com and/or upload PDF/TXT/MD/DOCX/FAQ → status queued → indexed.Fetch models → pick model → Save. Keys encrypted, not in embed.Overview:<script>
window.BotConfig = {
botName: "Customer Bot",
avatar: "https://your-domain/api/public/avatar/BOT_ID",
chatUrl: "https://your-domain/api/public/chat/BOT_ID",
accentColor: "#111111",
greeting: "Hi! How can I help?",
theme: "dark"
};
</script>
<script src="https://your-domain/chatbot-widget.js" async></script>
Paste before </body>. Widget stores VISITOR_ID + chat history in localStorage; greeting shows once.
| Tab | What it does |
|---|---|
| Overview | Live status, messages/users/sources, embed copy (src/components/ChatbotDashboard.tsx:244) |
| Appearance | name/avatar/accent/theme/greeting + live preview (src/components/ChatbotDashboard.tsx:282) |
| Chats | Grouped by visitor → date, CSV/JSON/PDF, Refresh (src/components/ChatbotDashboard.tsx:371) |
| Knowledge | Sources + Add knowledge wizard (src/components/ChatbotDashboard.tsx:449) |
| Settings | provider/model/baseUrl/apiKey, defaultMessage/fallbackMessage, Danger zone delete (src/components/ChatbotDashboard.tsx:493) |
| Method | Path | Description |
|---|---|---|
POST | /api/chatbots | Create bot |
GET | /api/chatbots | List bots |
PATCH | /api/chatbots/:id | Appearance + provider (name/avatar/accent/theme/greeting + provider/model/apiKey/baseUrl) |
GET | /api/chatbots/:id/appearance | Get appearance (server.ts:728) |
GET | /api/chatbots/:id/messages?limit=200 | Grouped messages |
GET | /api/chatbots/:id/stats | total/users |
GET | /api/chatbots/:id/messages/export?format=csv|json | Export |
GET | /api/knowledge/sources?chatbotId=ID | List sources |
POST | /api/knowledge/sources/:chatbotId | Add URL ({url}) or file (multipart) → queued |
DELETE | /api/knowledge/sources/:sourceId?chatbotId=ID | Delete source + chunks |
POST | /api/public/chat/:chatbotId | SSE chat {message, visitorId} → data: {token|error|sources} + data: [DONE] |
GET | /api/public/knowledge/:chatbotId | Public knowledge (cached) |
GET | /api/public/avatar/:chatbotId | Avatar file or redirect |
POST | /api/providers/models | List models for provider/baseUrl/apiKey |
GET | /api/stats | Global totalMessages/activeSessions |
Streaming: public/chatbot-widget.js:311 reads SSE via getReader(), falls back to text() + SW bypass for locked streams. Visitor grouping via X-Visitor-Id (VISITOR_ID in localStorage).
| Path | Role |
|---|---|
server.ts | Express + Vite dev, auth (DISABLE_AUTH), ingestion, RAG, SSE chat (server.ts:282) |
server/db.ts | SQLite (better-sqlite3) — chatbots/sources/chunks/messages, getChatbotAppearance |
server/ingestion.ts + crawler/run_crawler_for_bolnee.py | Crawl → /data/{id}_website.json → chunks |
crawler/crawler.py | Same-origin crawl, robots.txt, h1/h2/p/li extraction, sitemap + homepage |
public/chatbot-widget.js | Embeddable widget — BotConfig.chatUrl, accent, greeting, theme, VISITOR_ID, history |
src/components/ChatbotDashboard.tsx | Tabs: overview/appearance/chats/knowledge/settings, embedCode with theme |
src/components/KnowledgeSection.tsx | 4-step wizard: Knowledge → Provider → Processing (polls status) → Embed |
src/components/Overview.tsx | Stats + grid of 4 bots + View all |
src/components/BotCreationWizard.tsx | Name + avatar upload (2MB limit) |
src/index.css | Dark-mode tokens (--color-bg #020617, --color-card #1e293b) |
vercel.json / wrangler.toml | Hosting rewrites, bucket = "./dist", DISABLE_AUTH |
Dashboard is fully API-driven (/api/* relative) and auto-configures window.location.origin for embed URLs.
Vercel:
# env
DISABLE_AUTH=true
# JWT_SECRET not required for simple mode
# vercel.json already: rewrites /api/:path* → /api, /(.*) → /index.html, outputDirectory: dist
npm run build && vercel --prod
Cloudflare Pages / Workers:
# wrangler.toml: bucket = "./dist", DISABLE_AUTH=true
npm run build
wrangler pages deploy dist
# or: npx wrangler deploy
Chat endpoint streams SSE; for external sites use public https:// chatUrl (not localhost).
| Env | Description | Default |
|---|---|---|
DISABLE_AUTH / VITE_DISABLE_AUTH | No-login console (single-tenant) | false |
JWT_SECRET | Auth signing key (16+ chars, prod required) | development-secret-change-me |
LLM_BASE_URL / OPENROUTER_API_KEY / NVIDIA_API_KEY | Global provider fallback (per-bot settings take precedence) | — |
LLM_API_KEY | Global API key fallback | — |
LLM_MODEL | Global model fallback (e.g. openai/gpt-4o-mini) | gpt-4o-mini |
PORT | Server port (auto-fallback +1 if busy) | 3000 |
.env.example documents all.
| Symptom | Fix |
|---|---|
Port 3000 in use → 3001 and embed fails on external site | Embed uses window.location.origin; regenerate after restart or deploy to public URL (localhost embed is https:// mixed-content) |
getReader locked / ReadableStream locked | Bump public/sw.js to v3 skips POST /api/public/chat; hard-refresh to update SW |
Model 404 / 402 | Use Fetch models → pick :free (e.g. inclusionai/ling-3.0-flash-fin:free) or add credits |
404 knowledge/avatar | Ensure data/bolnee.db exists and bot id matches data/{id}_website.json |
| Avatar too large | PNG/JPG/WEBP ≤2MB; data URLs auto-converted to /api/public/avatar/:id |
| Greeting repeats on open/close | Fixed in public/chatbot-widget.js:115 — saveHistory/loadHistory in localStorage + engine=true guard; clear bolnee_msgs_* to reset |
npm run lint # tsc --noEmit clean
npm run build # vite + esbuild → dist/
npm run dev # http://localhost:3000
# Manual:
# 1. Create bot → avatar preview → Save appearance → preview updates
# 2. Add knowledge: URL + PDF → status queued → indexed
# 3. Provider → Fetch models → pick :free → Save
# 4. Overview → Copy embed → paste in plain HTML → widget loads, greeting once, close/open keeps history, dark/light/auto themes correct
# 5. Chats → grouped by visitor → CSV/JSON/PDF export
# 6. Settings → default/fallback messages → Chat without sources returns fallback
Distributed under the MIT License. See LICENSE for more information.
43 commits
TypeScript
63.7%
Python
23.1%
JavaScript
9.4%
Shell
3.0%
Bolnee-Chat
Chatbot integration in your business website. Self hosted, free forever!
Bolnee-Chat is a self-hosted RAG chatbot platform. Create a bot, add your website + PDFs as knowledge, pick any OpenAI-compatible provider (OpenRouter, OpenAI, Groq, Ollama, vLLM), and embed a 2-line snippet. Answers are grounded in your sources with citations, visitor chats are grouped and exportable, and everything runs on your infrastructure with SQLite.
Self-Hosted & Free ForeverNo vendor lock-in, no per-message billing. Run on your server, Vercel or Cloudflare Pages — SQLite, no external DB. |
RAG-Grounded AnswersCrawls your site + ingests PDFs/TXT/MD/DOCX, chunks to SQLite FTS and builds grounded prompts. Sources are cited, fallback is configurable. |
2-Line Embed, Any StackCopy |
Bot Overview — Stats & SnippetPer-bot: |
|
Appearance — Live PreviewEdit bot name, avatar (upload/preview), accent/background colour, theme |
|
Chats — Grouped by VisitorGrouped by |
|
Settings — Provider, Prompts, Danger Zone
|
|
Widget — Floating, Theme-Aware, PersistentBottom-right bubble → sliding window ( |
|
Also in the box:
locator · type · status · error · date with delete; append more via Add knowledge./api/public/avatar/:id (2MB limit, data/avatars/).apiKey/baseUrl/model via AES-256-GCM, never exposed in snippet.#020617 bg, #1e293b cards, slate-800 inputs, no white surfaces.
1. Create Chatbot — Brand in SecondsName + avatar (PNG/JPG/WEBP ≤2MB, preview) → stored as |
|
2. Add Knowledge — Website + FilesURL-only, files-only, or both. Same-origin crawler ( |
|
3. Configure Provider — Any OpenAI-Compatible APIPick provider → Base URL auto-fills → paste API key → Fetch models lists live models (prioritizes |
|
4. Embed — 2 LinesCopy from Overview → Embed code or Knowledge → Step 4. Auto-configures origin via |
|
| Requirement | Details |
|---|---|
| OS | macOS, Linux, Windows (WSL) |
| Runtime | Node.js 18+ |
| Package Manager | npm |
| Python | 3.10+ (crawler, optional but recommended) |
| Python Deps | aiohttp, beautifulsoup4, lxml, requests, brotli (+ playwright for JS-heavy sites) |
# Clone
git clone https://github.com/AniketWathore/bolnee-chat.git
cd bolnee-chat
# Env
cp .env.example .env
# edit .env — simplest self-hosted:
# DISABLE_AUTH=true
# JWT_SECRET=change-me-32-chars
# LLM_BASE_URL=https://openrouter.ai/api/v1 # optional global fallback
# LLM_API_KEY=sk-or-v1-... # optional global fallback
# LLM_MODEL=inclusionai/ling-3.0-flash-fin:free
# Install
npm install
# Python crawler deps (optional)
pip install aiohttp beautifulsoup4 lxml requests brotli
# Verify
npm run lint # tsc --noEmit
npm run build # vite + esbuild → dist/
npm run dev # http://localhost:3000 (auto-fallback to 3001 if busy)
SQLite is created at data/bolnee.db (git-ignored). Crawled sites → data/{chatbotId}_website.json, chunks in SQLite.
+ New chatbot → name + avatar (preview ≤2MB).https://your-site.com and/or upload PDF/TXT/MD/DOCX/FAQ → status queued → indexed.Fetch models → pick model → Save. Keys encrypted, not in embed.Overview:<script>
window.BotConfig = {
botName: "Customer Bot",
avatar: "https://your-domain/api/public/avatar/BOT_ID",
chatUrl: "https://your-domain/api/public/chat/BOT_ID",
accentColor: "#111111",
greeting: "Hi! How can I help?",
theme: "dark"
};
</script>
<script src="https://your-domain/chatbot-widget.js" async></script>
Paste before </body>. Widget stores VISITOR_ID + chat history in localStorage; greeting shows once.
| Tab | What it does |
|---|---|
| Overview | Live status, messages/users/sources, embed copy (src/components/ChatbotDashboard.tsx:244) |
| Appearance | name/avatar/accent/theme/greeting + live preview (src/components/ChatbotDashboard.tsx:282) |
| Chats | Grouped by visitor → date, CSV/JSON/PDF, Refresh (src/components/ChatbotDashboard.tsx:371) |
| Knowledge | Sources + Add knowledge wizard (src/components/ChatbotDashboard.tsx:449) |
| Settings | provider/model/baseUrl/apiKey, defaultMessage/fallbackMessage, Danger zone delete (src/components/ChatbotDashboard.tsx:493) |
| Method | Path | Description |
|---|---|---|
POST | /api/chatbots | Create bot |
GET | /api/chatbots | List bots |
PATCH | /api/chatbots/:id | Appearance + provider (name/avatar/accent/theme/greeting + provider/model/apiKey/baseUrl) |
GET | /api/chatbots/:id/appearance | Get appearance (server.ts:728) |
GET | /api/chatbots/:id/messages?limit=200 | Grouped messages |
GET | /api/chatbots/:id/stats | total/users |
GET | /api/chatbots/:id/messages/export?format=csv|json | Export |
GET | /api/knowledge/sources?chatbotId=ID | List sources |
POST | /api/knowledge/sources/:chatbotId | Add URL ({url}) or file (multipart) → queued |
DELETE | /api/knowledge/sources/:sourceId?chatbotId=ID | Delete source + chunks |
POST | /api/public/chat/:chatbotId | SSE chat {message, visitorId} → data: {token|error|sources} + data: [DONE] |
GET | /api/public/knowledge/:chatbotId | Public knowledge (cached) |
GET | /api/public/avatar/:chatbotId | Avatar file or redirect |
POST | /api/providers/models | List models for provider/baseUrl/apiKey |
GET | /api/stats | Global totalMessages/activeSessions |
Streaming: public/chatbot-widget.js:311 reads SSE via getReader(), falls back to text() + SW bypass for locked streams. Visitor grouping via X-Visitor-Id (VISITOR_ID in localStorage).
| Path | Role |
|---|---|
server.ts | Express + Vite dev, auth (DISABLE_AUTH), ingestion, RAG, SSE chat (server.ts:282) |
server/db.ts | SQLite (better-sqlite3) — chatbots/sources/chunks/messages, getChatbotAppearance |
server/ingestion.ts + crawler/run_crawler_for_bolnee.py | Crawl → /data/{id}_website.json → chunks |
crawler/crawler.py | Same-origin crawl, robots.txt, h1/h2/p/li extraction, sitemap + homepage |
public/chatbot-widget.js | Embeddable widget — BotConfig.chatUrl, accent, greeting, theme, VISITOR_ID, history |
src/components/ChatbotDashboard.tsx | Tabs: overview/appearance/chats/knowledge/settings, embedCode with theme |
src/components/KnowledgeSection.tsx | 4-step wizard: Knowledge → Provider → Processing (polls status) → Embed |
src/components/Overview.tsx | Stats + grid of 4 bots + View all |
src/components/BotCreationWizard.tsx | Name + avatar upload (2MB limit) |
src/index.css | Dark-mode tokens (--color-bg #020617, --color-card #1e293b) |
vercel.json / wrangler.toml | Hosting rewrites, bucket = "./dist", DISABLE_AUTH |
Dashboard is fully API-driven (/api/* relative) and auto-configures window.location.origin for embed URLs.
Vercel:
# env
DISABLE_AUTH=true
# JWT_SECRET not required for simple mode
# vercel.json already: rewrites /api/:path* → /api, /(.*) → /index.html, outputDirectory: dist
npm run build && vercel --prod
Cloudflare Pages / Workers:
# wrangler.toml: bucket = "./dist", DISABLE_AUTH=true
npm run build
wrangler pages deploy dist
# or: npx wrangler deploy
Chat endpoint streams SSE; for external sites use public https:// chatUrl (not localhost).
| Env | Description | Default |
|---|---|---|
DISABLE_AUTH / VITE_DISABLE_AUTH | No-login console (single-tenant) | false |
JWT_SECRET | Auth signing key (16+ chars, prod required) | development-secret-change-me |
LLM_BASE_URL / OPENROUTER_API_KEY / NVIDIA_API_KEY | Global provider fallback (per-bot settings take precedence) | — |
LLM_API_KEY | Global API key fallback | — |
LLM_MODEL | Global model fallback (e.g. openai/gpt-4o-mini) | gpt-4o-mini |
PORT | Server port (auto-fallback +1 if busy) | 3000 |
.env.example documents all.
| Symptom | Fix |
|---|---|
Port 3000 in use → 3001 and embed fails on external site | Embed uses window.location.origin; regenerate after restart or deploy to public URL (localhost embed is https:// mixed-content) |
getReader locked / ReadableStream locked | Bump public/sw.js to v3 skips POST /api/public/chat; hard-refresh to update SW |
Model 404 / 402 | Use Fetch models → pick :free (e.g. inclusionai/ling-3.0-flash-fin:free) or add credits |
404 knowledge/avatar | Ensure data/bolnee.db exists and bot id matches data/{id}_website.json |
| Avatar too large | PNG/JPG/WEBP ≤2MB; data URLs auto-converted to /api/public/avatar/:id |
| Greeting repeats on open/close | Fixed in public/chatbot-widget.js:115 — saveHistory/loadHistory in localStorage + engine=true guard; clear bolnee_msgs_* to reset |
npm run lint # tsc --noEmit clean
npm run build # vite + esbuild → dist/
npm run dev # http://localhost:3000
# Manual:
# 1. Create bot → avatar preview → Save appearance → preview updates
# 2. Add knowledge: URL + PDF → status queued → indexed
# 3. Provider → Fetch models → pick :free → Save
# 4. Overview → Copy embed → paste in plain HTML → widget loads, greeting once, close/open keeps history, dark/light/auto themes correct
# 5. Chats → grouped by visitor → CSV/JSON/PDF export
# 6. Settings → default/fallback messages → Chat without sources returns fallback
Distributed under the MIT License. See LICENSE for more information.
43 commits
TypeScript
63.7%
Python
23.1%
JavaScript
9.4%
Shell
3.0%