# 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](https://www.ashrivastava.xyz) 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-v2` q8) 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:

```bash
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/widget
```

Then create a `siteask.config.json` (see `examples/siteask.config.json`):

```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:

```bash
npx tsx node_modules/@siteask/widget/scripts/build-index.ts siteask.config.json
```

This 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`](./examples/nextjs-api-route.ts) | Next.js App Router (Node) |
| [`examples/vercel-edge.ts`](./examples/vercel-edge.ts) | Vercel Edge / Next.js Edge runtime |
| [`examples/cloudflare-worker.ts`](./examples/cloudflare-worker.ts) | Cloudflare Workers (KV-aware) |
| [`examples/express-server.ts`](./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 body
```

The 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

```html
<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:

```html
<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

1. **Build time.** `siteask build-index` walks 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.
2. **First visitor opens chat.** Widget loads `siteask-index.json` and, lazily from `esm.sh`, the Transformers.js library + the same q8 model. Cached for subsequent sessions.
3. **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.
4. **Widget POSTs to your backend.** Body: `{ query, contexts: [...] }`. Your backend prompts the LLM with the contexts, returns a streamed answer.
5. **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 publish` once 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
