@siteask/widget
v0.1.0
Published
Drop-in AI chat widget for any website. Browser-side RAG, BYOK LLM, single script tag.
Downloads
25
Maintainers
Readme
SiteAsk
A drop-in AI chat widget for any website. One <script> tag to install, browser-side RAG with no embedding API costs, BYOK LLM through your own endpoint, source-citable answers. Open source.
Built as a generalisation of the Ask Amit assistant — same browser-side embedding architecture, but configurable for any content source.
Status: v0.1, MVP. The widget works; the toolchain is intentionally lean. Issues and PRs welcome once we publish.
What you get
- 🪶 Tiny bundle. ~10 KB gzipped widget. Transformers.js loads lazily from CDN only when the visitor opens the chat.
- 🌐 Browser-side embedding. Query embedding (
Xenova/all-MiniLM-L6-v2q8) runs in the visitor's browser. No vector DB to host, no embedding-API bill. - 💰 You pay only for LLM calls. And only when someone actually chats.
- 🔑 BYOK. Bring any LLM — OpenRouter, Anthropic, OpenAI, your own gateway. The widget calls your backend, never an LLM provider directly. Your key, your rate limits, your prompts.
- 📚 Source-cited answers. Each reply links back to the pages it drew from.
- 🎨 Themed. Light / dark / auto, primary colour, position, custom strings — all via data attributes.
Install
1. Build your content index
In your site's repo:
npm install --save-dev @siteask/widget
# or, while we're pre-publish:
git clone https://github.com/amitshrivastavaa/amit-shrivastava
ln -s "$PWD/amit-shrivastava/site-ask" node_modules/@siteask/widgetThen create a siteask.config.json (see examples/siteask.config.json):
{
"out": "./public/siteask-index.json",
"sources": [
{ "kind": "markdown", "dir": "./content/blog", "urlPrefix": "/blog" },
{ "kind": "urls", "urls": ["https://your-site.com/about"] }
]
}Build the index:
npx tsx node_modules/@siteask/widget/scripts/build-index.ts siteask.config.jsonThis writes public/siteask-index.json. Host it at any URL on your site — it's a static file.
2. Deploy a tiny backend
Pick the runtime that fits your stack — drop the matching example into your project, set OPENROUTER_API_KEY (or swap the upstream call for your LLM of choice), and you're done:
| File | Runtime |
|---|---|
| examples/nextjs-api-route.ts | Next.js App Router (Node) |
| examples/vercel-edge.ts | Vercel Edge / Next.js Edge runtime |
| examples/cloudflare-worker.ts | Cloudflare Workers (KV-aware) |
| examples/express-server.ts | Standalone Express server |
Every example ships hardened by default — per-IP rate limit (12 req / 60s), 32 KB body cap, per-field length caps on every string the widget can send, 500-token max output cap, optional CORS allowlist via SITEASK_ALLOWED_ORIGIN. So you can't accidentally publish a public endpoint that lets anyone drain your LLM credits.
The contract is:
POST <your-backend>
body: { query, contexts: [{ title, url, text }, ...] }
response: text/plain streaming bodyThe Cloudflare example auto-uses KV for cross-isolate rate limiting if you bind one; the Vercel Edge example documents the Vercel KV swap. For Node-based deployments at scale (multiple instances), swap the in-memory limiter for Redis (rate-limit-redis).
3. Drop the widget on any page
<script
src="https://cdn.jsdelivr.net/npm/@siteask/widget@latest/dist/widget.js"
data-index-url="/siteask-index.json"
data-api-url="/api/siteask"
data-title="Ask the docs"
data-subtitle="AI-powered search"
data-primary-color="#7c3aed"
data-suggestions="What is X?|How do I install Y?|Pricing"
async
></script>That's it. A floating chat button appears in the corner.
Programmatic init
If you prefer JS over data attributes:
<script src="https://cdn.jsdelivr.net/npm/@siteask/widget@latest/dist/widget.js"></script>
<script>
SiteAsk.init({
indexUrl: "/siteask-index.json",
apiUrl: "/api/siteask",
theme: "dark",
title: "Ask the docs",
suggestions: ["What is X?", "How do I install Y?"],
})
</script>Configuration
| Option | Data attribute | Default | Description |
|---|---|---|---|
| indexUrl | data-index-url | required | URL of your prebuilt siteask-index.json |
| apiUrl | data-api-url | required | URL of your backend endpoint |
| theme | data-theme | "auto" | "light" | "dark" | "auto" |
| position | data-position | "bottom-right" | "bottom-right" | "bottom-left" |
| primaryColor | data-primary-color | #2563eb | Any CSS colour |
| title | data-title | "Ask" | Header title |
| subtitle | data-subtitle | "AI assistant" | Header subtitle |
| greeting | data-greeting | short default | Shown when chat is empty |
| suggestions | data-suggestions | none | Pipe-separated list of starter prompts |
| placeholder | data-placeholder | "Ask anything…" | Input placeholder |
| model | data-model | Xenova/all-MiniLM-L6-v2 | Embedding model id |
| branding | data-branding | "true" | "false" to hide the SiteAsk footer link |
How it actually works
- Build time.
siteask build-indexwalks your content sources, chunks each document into ~900-char paragraphs, embeds every chunk with the local Transformers.js pipeline (all-MiniLM-L6-v2, q8 quantised). Outputs one static JSON file. - First visitor opens chat. Widget loads
siteask-index.jsonand, lazily fromesm.sh, the Transformers.js library + the same q8 model. Cached for subsequent sessions. - Visitor types a question. Widget embeds the query in-browser, scores all chunks by dot product (vectors are L2-normalised, so dot = cosine), keeps top 6.
- Widget POSTs to your backend. Body:
{ query, contexts: [...] }. Your backend prompts the LLM with the contexts, returns a streamed answer. - Sources rendered below the bubble. Each citation links to the source URL the index already knows.
The whole pipeline is symmetric to a real production RAG stack — embedding + retrieval + LLM with grounded context — but the embedding and retrieval halves run for free on the user's device.
Why it exists
Most chat-widget products are SaaS-only and charge per query (or per "monthly active user"). For a personal blog or docs site that gets a few thousand chats a month, that's $50–$500 of pure margin going to a vendor. SiteAsk does the parts that don't need a backend — embedding and retrieval — on the device, so the only thing you pay for is the LLM call itself (and only when someone chats).
Roadmap
npm publishonce the contract is stable- Cloudflare Workers + Vercel Edge backend examples
- Optional WebGPU acceleration (~5× faster embedding on supported devices)
- Streaming retrieval (rerank as the user types)
- Conversation memory across turns
- Adapter for Cloudflare Vectorize / Pinecone if you'd rather store the index server-side
License
MIT
