Embeddable RAG FAQ-Based AI Chatbot Widget - JavaScript, Cloudflare Workers, Vectorize, BGE Model, Llama 3.1 Full-Stack Project
A production-ready, embeddable AI chatbot widget powered by Cloudflare Workers, featuring RAG (Retrieval Augmented Generation), real-time streaming responses, and a zero-dependency client-side script.
- Live Demo: https://ai-chatbot-widget.arnobt78.workers.dev/
- Production Live: https://www.arnobmahmud.com/
- Security: See SECURITY.md for private vulnerability reporting
- Walkthrough: docs/PROJECT_WALKTHROUGH.md — short learning path
- Author: Arnob Mahmud | LinkedIn: https://www.linkedin.com/in/arnob-mahmud-05839655/ | Contact: contact@arnobmahmud.com
- Overview
- Who This Is For (Learning Goals)
- Features
- Technology Stack
- Keywords (Short Glossary)
- Project Structure
- How It Works (Walkthrough)
- Installation & Setup
- Environment Variables & Bindings
- Scripts & Local Development
- Deployment
- Seeding the FAQ Knowledge Base
- Usage — Embed the Widget
- API Endpoints
- Backend Deep Dive (
src/index.js) - Frontend Widget Deep Dive (
public/widget.js) - Demo Page (
public/index.html) - CSS & Styling
- AI Models (Chat + RAG)
- Reusing Pieces in Other Projects
- Code Examples
- Security Notes
- Related Documentation
- Troubleshooting
- Conclusion
- License
- Happy Coding!
Prefer a shorter tour first? Start with docs/PROJECT_WALKTHROUGH.md.
This repository is a full-stack edge chatbot:
| Layer | What it is |
|---|---|
| Frontend | A single vanilla JavaScript file (public/widget.js) that injects a floating chat UI into any website |
| Backend | One Cloudflare Worker (src/index.js) that serves static assets and exposes JSON/SSE APIs |
| AI | Cloudflare Workers AI for chat + text embeddings |
| Memory | Cloudflare KV for conversation sessions (cookie-based) |
| Knowledge | Cloudflare Vectorize for FAQ semantic search (RAG) |
You do not need Next.js, React, Express, or a traditional database to run the core project. Everything important lives at the Cloudflare edge.
- How an embeddable widget works with one
<script>tag - How RAG combines embeddings + a vector index + an LLM
- How SSE streaming makes answers appear token-by-token
- How Workers bindings (
AI,VECTORIZE,CHAT_SESSIONS,ASSETS,CHAT_LIMITER) replace classic.envAPI wiring for many Cloudflare features - How a model fallback chain keeps chat alive if the primary model fails
| Level | What to focus on |
|---|---|
| Beginner | Embed the widget, run npm run dev, call /api/health, read the demo page |
| Intermediate | Trace chat() → faq() → runChatStream(), edit FAQs in seed(), change CHAT_MODELS |
| Advanced | Change Vectorize metadata shape, reuse RAG helpers, tune CHAT_LIMITER in wrangler.jsonc |
- RAG (Retrieval Augmented Generation) — embeds the user question, searches Vectorize (
topK: 3), injects FAQ Q&A into the system prompt - Streaming chat — Workers AI with
stream: true, returned astext/event-stream - Model fallback — primary
@cf/meta/llama-3.1-8b-instruct-fast, then@cf/zai-org/glm-4.7-flashifAI.runfails at start - Session persistence —
chatbot_sessionHttpOnly cookie + KV (30-day TTL) - History API — restore prior messages when the widget reloads
- Seed API — one-shot (or repeatable) upsert of FAQ embeddings into Vectorize
- Health check — simple monitoring endpoint
- CORS —
Access-Control-Allow-Origin: *so the widget can be embedded cross-origin - Static asset serving —
public/via theASSETSbinding with long cache headers
- Zero runtime dependencies — pure ES6+ in the browser
- Inline + CSS file styling — works even when host sites do not use Tailwind
- Dark / light mode — system preference + manual toggle
- Mobile-aware layout — keyboard-safe positioning on small screens
- Typing indicator while the stream is in progress
- Configurable via
window.CHATBOT_*globals before loading the script
| Technology | Role | Beginner note |
|---|---|---|
| Cloudflare Workers | Serverless JS at the edge | Like a tiny Node server, but global and cold-start friendly |
| Workers AI | Run LLMs + embedding models | You call env.AI.run(modelId, { ... }) — no separate OpenAI key required for these models |
| Vectorize | Vector database | Stores embedding arrays; finds “similar meaning” FAQs |
| KV | Key–value store | Saves chat sessions as JSON strings |
| Wrangler | CLI for develop + deploy | wrangler.dev locally, wrangler deploy to production |
| Technology | Role |
|---|---|
| Vanilla JavaScript | Widget logic (public/widget.js) |
| HTML | Demo page (public/index.html) |
| Tailwind CSS | Utility classes for demo + widget stylesheet build |
| PostCSS / Autoprefixer | CSS toolchain (devDependencies) |
| Package | Why it exists |
|---|---|
wrangler |
Cloudflare Workers tooling |
tailwindcss |
Compile src/input.css → public/styles.css |
postcss / autoprefixer |
CSS processing used with Tailwind |
There are no production dependencies — the Worker and widget run without an npm runtime package tree on the client.
| Keyword | Meaning in this project |
|---|---|
| RAG | Retrieve FAQ snippets first, then generate an answer with that context |
| Embedding | A numeric vector that represents text meaning (here: 768 dimensions from BGE) |
| Vectorize | Cloudflare’s vector index that stores those embeddings |
| SSE | Server-Sent Events — a one-way stream of events from server → browser |
| Worker | A single JS module (src/index.js) handling every HTTP request |
| Binding | Named resource attached to the Worker (env.AI, env.VECTORIZE, …) |
| KV | Durable key–value storage for sessions |
| Widget | Floating chat UI injected by widget.js |
| Seed | Upload FAQ embeddings into Vectorize via POST /api/seed |
| Fallback model | Second Workers AI model tried if the first fails to start streaming |
cloudflare-chatbot-widget/
├── src/
│ ├── index.js # Cloudflare Worker: APIs + asset router + RAG + chat
│ ├── input.css # Tailwind entry (demo utilities)
│ └── widget-styles.css # Widget-specific CSS appended after Tailwind build
├── public/
│ ├── index.html # Demo / landing page that loads the widget
│ ├── widget.js # Embeddable chatbot (vanilla JS)
│ ├── vendor/ # Self-hosted obs SDK (`cb-obs.min.js`; tunnel-friendly name)
│ └── styles.css # Built CSS (generated — do not hand-edit as source of truth)
├── docs/
│ ├── PROJECT_WALKTHROUGH.md # Short learning path
│ ├── AGILE_V_PROTOCOL.md
│ ├── LLM_MODEL_SELECTION.md
│ ├── Redis_Sentry_PostHog_INTEGRATION_GUIDE.md # portable guide (not wired here)
│ └── VERCEL_PRODUCTION_GUARDRAILS.md # portable guide (not wired here)
├── .agile-v/ # Agile V project memory (agents / process)
├── wrangler.jsonc # Bindings: AI, Vectorize, KV, ASSETS, CHAT_LIMITER
├── tailwind.config.js
├── package.json
├── LICENSE
├── SECURITY.md
├── AGENTS.md
├── CLAUDE.md
└── README.md # You are here
| File | Responsibility |
|---|---|
src/index.js |
All backend logic: /api/*, RAG, chat stream, seed, static pass-through |
public/widget.js |
UI + fetch to /api/chat and /api/history |
public/index.html |
Teaching demo + sample questions |
wrangler.jsonc |
Declarative Cloudflare resources |
docs/LLM_MODEL_SELECTION.md |
Model catalog notes + this repo’s live model IDs |
User types a message in widget.js
│
▼
POST /api/chat ──► Cloudflare Worker (src/index.js)
│
├─► Read/create session in KV (cookie chatbot_session)
├─► faq(): embed question with BGE → Vectorize top 3 FAQs
├─► Build messages: system(+FAQ) + last 10 turns
├─► runChatStream(): try Llama 3.1 Fast, else GLM-4.7-Flash
├─► Stream SSE tokens back to the browser
└─► On stream end: save assistant message to KV
- Widget opens — FAB button appears; greeting shows from
CHATBOT_GREETING. - User sends text —
widget.jsPOSTs{ message }to/api/chatwithcredentials: 'include'. - Session — Worker reads cookie or creates
sess_<uuid>in KV. - RAG — Question → embedding → similar FAQs → text context.
- Generation — LLM streams tokens; widget appends them live.
- Persist — Full assistant reply stored in KV for
/api/history.
If RAG fails, chat still works (empty FAQ context) and the error is logged with console.error.
- Node.js 18+ (recommended)
- npm
- A free Cloudflare account
- Wrangler login:
npx wrangler login
git clone https://github.com/arnobt78/Embeddable-AI-Chatbot-Widget--JavaScript-Cloudflare-Workers-FullStack.git
cd Embeddable-AI-Chatbot-Widget--JavaScript-Cloudflare-Workers-FullStack
npm installKV namespace (sessions):
npx wrangler kv namespace create CHAT_SESSIONSCopy the returned id into wrangler.jsonc → kv_namespaces[0].id.
Vectorize index (FAQ embeddings — must match BGE dimensions = 768):
npx wrangler vectorize create faq-vectors \
--dimensions=768 \
--metric=cosineEnsure wrangler.jsonc has:
Workers AI — already enabled via:
"ai": { "binding": "AI" }No separate “OpenAI-style” API key is required for the built-in Workers AI models used here.
npm run devOpen the URL Wrangler prints (usually http://127.0.0.1:8787).
npm run deploycurl -X POST https://YOUR-SUBDOMAIN.workers.dev/api/seed \
-H "Authorization: Bearer YOUR_SEED_SECRET"No classic .env is required for chat + RAG. Cloudflare uses bindings in wrangler.jsonc plus an optional Wrangler secret for seed auth.
| Mechanism | Required? | Purpose |
|---|---|---|
wrangler.jsonc bindings |
Yes | AI, VECTORIZE, CHAT_SESSIONS, ASSETS, CHAT_LIMITER |
SEED_SECRET (Wrangler secret / .dev.vars) |
Yes to call /api/seed |
Fail-closed seed lock (REQ-0011) |
SENTRY_DSN (Wrangler secret / .dev.vars) |
Optional | Worker Sentry + /api/monitoring tunnel allowlist |
.env / .env.local |
No | Not used by this Worker |
CLOUDFLARE_API_TOKEN |
Optional | CI / non-interactive wrangler deploy |
Local (gitignored) — copy from the example file:
cp .dev.vars.example .dev.vars
# edit SEED_SECRET to a long random stringProduction:
npx wrangler secret put SEED_SECRETIf SEED_SECRET is missing, POST /api/seed returns 503. Wrong token returns 401.
Accepted headers:
Authorization: Bearer <SEED_SECRET>X-Seed-Secret: <SEED_SECRET>
Create a Sentry project as Cloudflare Workers (not Next.js/React). Then:
npx wrangler secret put SENTRY_DSN
# local: add SENTRY_DSN=… to `.dev.vars` (see `.dev.vars.example`)Enables @sentry/cloudflare on the Worker (model/RAG hard failures) and allowlists POST /api/monitoring (browser SDK tunnel past ad blockers). Browser bundle is served as /vendor/cb-obs.min.js (neutral filename — *sentry* paths get ERR_BLOCKED_BY_CLIENT). Refresh with npm run vendor:sentry.
POST /api/chat is limited to 20 requests per IP per 60 seconds via the Workers Rate Limiting binding (CHAT_LIMITER in wrangler.jsonc). This avoids racy KV counters. Over limit → 429 with Retry-After: 60. Limits are enforced per Cloudflare colo (abuse prevention, not global billing accounting).
public/robots.txt allows normal crawlers on / and Disallow: / for common AI scrapers (GPTBot, ChatGPT-User, Google-Extended, CCBot, anthropic-ai, ClaudeBot, Bytespider, meta-externalagent). Served with a 1-hour cache (not year-long immutable).
export CLOUDFLARE_API_TOKEN="your-api-token-from-dash.cloudflare.com"
npm run deployCreate a token with Workers edit permissions: Cloudflare API Tokens.
When embedding on another site (or Next.js), you only need a public Worker URL — not private AI keys in the browser:
<script>
window.CHATBOT_BASE_URL = "https://your-worker.workers.dev";
</script>
<script src="https://your-worker.workers.dev/widget.js"></script>In a Next.js host you might use:
# Host app only — optional public URL to the Worker (never put Workers AI secrets in NEXT_PUBLIC_*)
NEXT_PUBLIC_CHATBOT_URL=https://your-worker.workers.dev| Binding name | Type | Used for |
|---|---|---|
AI |
Workers AI | Chat + embeddings |
VECTORIZE |
Vectorize index faq-vectors |
RAG search / upsert |
CHAT_SESSIONS |
KV namespace | Session JSON |
CHAT_LIMITER |
Rate Limiting (20 / 60s) | Abuse cap on /api/chat |
ASSETS |
Static assets ./public |
widget.js, CSS, HTML, robots.txt |
SEED_SECRET |
Secret (not a binding in jsonc) | Authorize /api/seed |
SENTRY_DSN |
Secret (optional) | Sentry Worker SDK + monitoring tunnel allowlist |
| npm script | What it does |
|---|---|
npm run build:css |
Compiles Tailwind from src/input.css, then appends src/widget-styles.css → public/styles.css |
npm run dev |
build:css + wrangler dev |
npm run deploy |
build:css + wrangler deploy |
Always rebuild CSS before deploy if you edited src/input.css or src/widget-styles.css (the deploy script already does this).
- Confirm
wrangler.jsoncIDs match your account’s KV + Vectorize resources. - Set production secret:
npx wrangler secret put SEED_SECRET. - Run
npm run deploy. - Note the
*.workers.devURL. - Call
POST /api/seedonce with the Bearer token. - Open
/and test the widget. - Embed on your site with
CHATBOT_BASE_URLpointing at the Worker.
The FAQ corpus lives inside seed() in src/index.js (about 20 Q&A pairs about the portfolio). Seeding:
- Embeds each
question + answerwith@cf/baai/bge-base-en-v1.5 - Upserts vectors + metadata into Vectorize
curl -X POST https://YOUR-SUBDOMAIN.workers.dev/api/seed \
-H "Authorization: Bearer YOUR_SEED_SECRET"
# → { "success": true, "count": 20 }Re-run after you edit FAQ text. Changing embedding model dimensions requires a new Vectorize index (keep BGE 768 unless you intentionally migrate).
<script>
window.CHATBOT_TITLE = "Support Assistant";
window.CHATBOT_GREETING = "Hi! How can I help you today?";
</script>
<script src="/widget.js"></script><script>
window.CHATBOT_BASE_URL = "https://ai-chatbot-widget.arnobt78.workers.dev";
window.CHATBOT_TITLE = "Support Assistant";
window.CHATBOT_GREETING = "Hi! How can I help you today?";
window.CHATBOT_PLACEHOLDER = "Type your message...";
</script>
<script src="https://ai-chatbot-widget.arnobt78.workers.dev/widget.js"></script>| Variable | Default | Purpose |
|---|---|---|
CHATBOT_BASE_URL |
window.location.origin |
Worker origin for API + assets |
CHATBOT_TITLE |
'Chat Assistant' |
Header title |
CHATBOT_GREETING |
'👋 How can I help you today?' |
First bot message |
CHATBOT_PLACEHOLDER |
'Message...' |
Input placeholder |
Set globals before loading widget.js.
Base URL = your Worker origin.
Liveness check. When SENTRY_DSN is set, also returns the public client DSN for the widget.
curl https://YOUR-SUBDOMAIN.workers.dev/api/health
# { "status": "ok", "sentryDsn": "https://…@….ingest.sentry.io/…" } # or sentryDsn: nullSentry envelope tunnel (browser SDK). Same-origin / Worker-origin POST so ad blockers do not block *.sentry.io. Allowlists host + project from SENTRY_DSN only (not an open proxy).
Streams an assistant reply (SSE).
Request
POST /api/chat
Content-Type: application/json
{ "message": "Tell me about Arnob Mahmud" }Response
Content-Type: text/event-stream- Optional
Set-Cookie: chatbot_session=...on first visit - Body: Workers AI SSE chunks (
data: {...})
Errors
400— missing message405— wrong method503— all chat models inCHAT_MODELSfailed to start
Returns messages for the current session cookie.
curl -c cookies.txt -b cookies.txt https://YOUR-SUBDOMAIN.workers.dev/api/history
# { "messages": [ { "role": "user"|"assistant", "content": "...", "timestamp": 123 } ] }Upserts FAQ embeddings into Vectorize. Requires SEED_SECRET.
curl -X POST https://YOUR-SUBDOMAIN.workers.dev/api/seed \
-H "Authorization: Bearer YOUR_SEED_SECRET"
# { "success": true, "count": N }
# Without secret → 503 or 401| Path | Description |
|---|---|
/ or /index.html |
Demo page |
/widget.js |
Embeddable widget |
/styles.css |
Compiled styles |
/robots.txt |
Crawl rules (blocks AI scrapers) |
Served via env.ASSETS. JS/CSS use long immutable cache; HTML and robots.txt use max-age=3600.
Think of the Worker as a tiny router + helpers.
const CHAT_MODEL = "@cf/meta/llama-3.1-8b-instruct-fast";
const CHAT_MODEL_FALLBACK = "@cf/zai-org/glm-4.7-flash";
const CHAT_MODELS = [CHAT_MODEL, CHAT_MODEL_FALLBACK];
const EMBED_MODEL = "@cf/baai/bge-base-en-v1.5";
const TTL = 30 * 24 * 60 * 60; // session cookie + KV TTL (seconds)
const CHAT_RATE_WINDOW_S = 60; // matches wrangler CHAT_LIMITER period
// Limit value (20) lives in wrangler.jsonc → ratelimits.CHAT_LIMITERTries each model in CHAT_MODELS until env.AI.run(..., { stream: true }) succeeds. If the primary fails (capacity, deprecation, transient error), the fallback starts immediately. Mid-stream failures cannot switch models after bytes are already sent.
- Embed
qwithEMBED_MODEL VECTORIZE.query(..., { topK: 3, returnMetadata: "all" })- Format matches as
Q: ...\nA: ... - On error → log + return
""(chat continues without FAQ context)
Rate-limit check → validates input → session → RAG → stream → save to KV on flush.
assertSeedAuth → hardcoded FAQ array → parallel embeddings → VECTORIZE.upsert.
Routes /api/* or falls through to ASSETS.
An IIFE that:
- Creates a floating button (
#cb-btn) with inline styles (works without Tailwind on the host) - Loads
/styles.cssasynchronously fromCHATBOT_BASE_URL - Builds the chat panel DOM (header, messages, input, menu)
- Binds open/close, theme toggle, clear chat, send message
- Streams
/api/chatand updates the message list progressively - Loads
/api/historyon init withcredentials: 'include'
Host sites may not include your Tailwind build. Critical positioning uses inline style so the FAB still appears correctly.
Copy public/widget.js + ensure styles.css (or equivalent) is reachable from CHATBOT_BASE_URL. Point CHATBOT_BASE_URL at any compatible Worker implementing the same API shapes.
Teaching page that:
- Explains the widget
- Lists sample FAQ questions
- Sets
CHATBOT_TITLE/CHATBOT_GREETING - Loads
/widget.js
Use it as a template for your own landing page or keep it as a local playground.
| Source | Role |
|---|---|
src/input.css |
Tailwind directives for the demo page |
src/widget-styles.css |
Widget-specific rules appended at build time |
public/styles.css |
Generated output used in production |
npm run build:csstailwind.config.js scans ./public/**/*.{html,js} and enables darkMode: 'class'.
| Role | Model ID | Notes |
|---|---|---|
| Chat (primary) | @cf/meta/llama-3.1-8b-instruct-fast |
Free-plan friendly; replaces deprecated @cf/meta/llama-3-8b-instruct |
| Chat (fallback) | @cf/zai-org/glm-4.7-flash |
Used if primary AI.run throws |
| Embeddings | @cf/baai/bge-base-en-v1.5 |
768-d — must match Vectorize index |
This project does not call Gemini, OpenRouter, Groq, or Hugging Face APIs. Those providers appear only as portable reference material in docs/LLM_MODEL_SELECTION.md.
Change models in one place at the top of src/index.js (CHAT_MODELS / EMBED_MODEL).
Point CHATBOT_BASE_URL at your deployed Worker and include widget.js (see Usage).
Copy faq() + EMBED_MODEL into another Worker that already has AI + VECTORIZE bindings. Keep dimensions consistent.
Copy runChatStream() + the SSE TransformStream pattern from chat(). Your client must read text/event-stream the same way widget.js does.
Pattern: HttpOnly cookie → KV JSON → history endpoint. Works for any small conversational app on Workers.
Edit the faqs array in seed(), redeploy, re-seed. Or later extract FAQs to a JSON file and import it (nice homework exercise).
// App Router layout — load once globally
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<script
dangerouslySetInnerHTML={{
__html: `window.CHATBOT_BASE_URL="${process.env.NEXT_PUBLIC_CHATBOT_URL}";`,
}}
/>
<script
src={`${process.env.NEXT_PUBLIC_CHATBOT_URL}/widget.js`}
defer
/>
</body>
</html>
);
}Remember: the AI keys stay on Cloudflare; the host app only needs the public Worker URL.
curl https://ai-chatbot-widget.arnobt78.workers.dev/api/healthUse the live widget, or stream with curl:
curl -N -X POST https://YOUR-SUBDOMAIN.workers.dev/api/chat \
-H "Content-Type: application/json" \
-d '{"message":"Where is Arnob located?"}'// src/index.js — educational example
const CHAT_MODELS = [
"@cf/meta/llama-3.1-8b-instruct-fast",
"@cf/zai-org/glm-4.7-flash",
// add another free-plan Workers AI text model if needed
];const faqs = [
["What is your refund policy?", "Refunds are available within 14 days..."],
["How do I contact support?", "Email support@example.com..."],
];Then redeploy + authenticated POST /api/seed.
/api/seedis secret-gated (SEED_SECRET) — fail-closed if unset./api/chatis rate-limited (20 req / IP / min viaCHAT_LIMITER) to protect Workers AI Neurons./api/monitoringtunnels Sentry envelopes only for the configuredSENTRY_DSNhost/project (not auth; allowlist only).public/robots.txtblocks common AI scrapers from the demo HTML.- CORS is wide open (
*) by design for embeddability — consider an allowlist if you only support specific sites. - Session cookies use
HttpOnly; SameSite=Lax— third-party embeds may need a different session strategy (SameSite=None; Secureor header-based session ids). - Report vulnerabilities privately via SECURITY.md (
contact@arnobmahmud.com).
| Doc | Purpose |
|---|---|
| SECURITY.md | Private vulnerability reporting |
| docs/PROJECT_WALKTHROUGH.md | Short educational walkthrough |
| docs/LLM_MODEL_SELECTION.md | Free-tier model reference + this repo’s Workers AI IDs |
| docs/AGILE_V_PROTOCOL.md | Agent / quality workflow |
| docs/VERCEL_PRODUCTION_GUARDRAILS.md | External reference only (Next.js/Vercel) — not applied here; use Workers patterns above |
| CLAUDE.md / AGENTS.md | Agent orientation |
.agile-v/ |
Living project state for Agile V cycles |
Portable guides under docs/ about Redis/Sentry/PostHog are not wired into this Worker — adapt carefully if you borrow ideas.
| Symptom | Likely cause | Fix |
|---|---|---|
Chat returns Cloudflare 1101 |
Deprecated / unavailable chat model, or undeployed local fix | Deploy current src/index.js; confirm CHAT_MODELS |
Chat returns 429 |
Rate limit (20/min/IP) | Wait for Retry-After seconds |
Seed returns 503 |
SEED_SECRET not set |
Add .dev.vars or wrangler secret put SEED_SECRET |
Seed returns 401 |
Wrong/missing Bearer token | Use Authorization: Bearer … matching the secret |
| Answers ignore FAQs | Vectorize empty or wrong dimensions | Authenticated POST /api/seed; index must be 768-d for BGE |
| Widget UI broken on host site | CSS not loaded / wrong CHATBOT_BASE_URL |
Set CHATBOT_BASE_URL to Worker origin; ensure /styles.css 200 |
| History empty after refresh | Cookie blocked / third-party context | Check cookie flags; test first-party Worker demo first |
wrangler deploy fails in CI |
Missing token | Set CLOUDFLARE_API_TOKEN |
| Neurons / capacity errors | Free-tier daily limit or model capacity | Wait for UTC reset; fallback model may still answer |
This project is a compact, teachable example of a modern edge AI stack: a portable chat widget, a single Worker backend, RAG over Vectorize, streaming LLM replies, and session memory in KV — without a traditional app server.
Use it to:
- Learn RAG end-to-end on Cloudflare
- Ship a portfolio or support chatbot quickly
- Copy patterns (SSE, bindings, embed scripts) into your own Workers
Explore the live demo, read src/index.js top-to-bottom once, then change one FAQ and re-seed — that single loop teaches most of the architecture.
This project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.
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.
