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.
| No vendor lock-in, no per-message billing. Run on your server, Vercel or Cloudflare Pages — SQLite, no external DB. | Crawls your site + ingests PDFs/TXT/MD/DOCX, chunks to SQLite FTS and builds grounded prompts. Sources are cited, fallback is configurable. | Copy |
Also in the box:
Knowledge management— listslocator · type · status · error · date
with delete; append more viaAdd knowledge.** Avatar file storage**— data URLs converted to/api/public/avatar/:id
(2MB limit,data/avatars/
).Encrypted provider keys— per-botapiKey/baseUrl/model
via AES-256-GCM, never exposed in snippet.Dark-mode console—#020617
bg,#1e293b
cards,slate-800
inputs, no white surfaces.
| 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) |
git clone https://github.com/AniketWathore/bolnee-chat.git
cd bolnee-chat
cp .env.example .env
npm install
pip install aiohttp beautifulsoup4 lxml requests brotli
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.
Create chatbot—+ New chatbot
→ name + avatar (preview ≤2MB).Add knowledge— enterhttps://your-site.com
and/or upload PDF/TXT/MD/DOCX/FAQ → statusqueued → indexed
.Configure provider— choose provider →Fetch models
→ pick model → Save. Keys encrypted, not in embed.Embed— copy snippet fromOverview
:
<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:
DISABLE_AUTH=true
npm run build && vercel --prod
Cloudflare Pages / Workers:
npm run build
wrangler pages deploy dist
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
Distributed under the MIT License. See LICENSE for more information.