Getting started
Sir Chats-a-Lot is an open-source React widget that drops an LLM-driven chat panel onto your site. It answers visitor questions from your published content via Gnosys Web, and renders forms / buttons / confirms inline when a text reply isn't enough.
This doc walks you through the five-minute install, the configuration surface, the API contract between the widget and your server, and the basics of theming and self-hosting.
Install
Add the package:
npm install sir-chats-a-lotMount it in your app (Next.js App Router shown; the same pattern works in Remix, Astro, or plain React):
"use client";
import {
SirChatsALotProvider,
SirChatsALotLauncher,
SirChatsALotWidget,
} from "sir-chats-a-lot";
import type { AgentRequest, SirChatsALotApi } from "sir-chats-a-lot";
import "sir-chats-a-lot/styles";
const api: SirChatsALotApi = async (request: AgentRequest) => {
const res = await fetch("/api/scal", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request),
});
if (!res.ok) throw new Error(await res.text());
return res.json();
};
export function AppScal() {
return (
<SirChatsALotProvider productName="My SaaS" api={api}>
<SirChatsALotLauncher />
<SirChatsALotWidget />
</SirChatsALotProvider>
);
}You'll also need a server route at /api/scal that talks to your LLM provider. The repo ships examples/nextjs-anthropic and examples/nextjs-openai as reference implementations.
Configuration
The provider accepts these props (all optional except productName and api):
| Prop | Type | Default | Purpose |
|---|---|---|---|
productName | string | — | Shown in the launcher and header |
api | (req) => Promise<res> | — | Your server-route caller |
rateLimitMs | number | 2000 | Cooldown between sends/submits |
maxMessageLength | number | 2000 | Character cap per message |
maxMessages | number | 50 | Conversation history cap |
greeting | string | — | First assistant message when chat opens |
subtitle | string | "AI assistant" | Header subtitle |
theme | object | — | Color/radius/font overrides |
onError | fn | — | Observability hook |
onMessage | fn | — | Per-message hook |
Knowledge layer
SCAL pairs with gnosys/web — a zero-dependency static knowledge index. The CLI crawls your site (sitemap, directory, or URL list), produces markdown, and builds a TF-IDF index. Your server route loads the index and feeds matches to the LLM each turn.
Setup
Install Gnosys, initialize a web config, and build the index:
npm install gnosys
npx gnosys web init
npx gnosys web buildThen add "postbuild": "gnosys web build" to your package.json scripts so the index regenerates on every deploy.
Surfaces API
Every turn, your server returns an AgentResponse. It can include text, a surface, or both. The LLM picks which surface (if any) to send based on what the visitor needs next.
Built-in surface types
| Type | Purpose |
|---|---|
quickActions | Row of tappable suggestion cards |
form | Labeled fields with validation and a submit button |
confirm | Yes/no dialog |
composed | Mixed text/field/button/divider blocks |
Response shape
{
"message": "Two paths to install — pick one:",
"surface": {
"type": "quickActions",
"props": {
"actions": [
{ "id": "npm", "label": "NPM package" },
{ "id": "script", "label": "Script tag" },
{ "id": "docker", "label": "Docker" }
]
}
},
"sources": [
{ "title": "Installation guide", "path": "/docs/install" }
]
}When the visitor interacts with a surface, the widget sends an AgentAction back with the payload. Your server feeds that to the LLM and the next turn decides what to do next — reply, send a follow-up surface, or finish.
Theming
Every visual aspect is driven by CSS custom properties (--scal-color-accent, --scal-radius, etc.). Override via the theme prop or plain CSS targeting the widget container.
import { SirChatsALotProvider } from "sir-chats-a-lot";
<SirChatsALotProvider
productName="My SaaS"
api={api}
theme={{
colors: {
accent: "#d49b3a",
background: "#0a100e",
foreground: "#e3ddca",
},
radius: { card: 14, button: 999 },
fontFamily: {
body: "Inter, system-ui, sans-serif",
display: "Big Shoulders Display, system-ui, sans-serif",
},
}}
>
...
</SirChatsALotProvider>Self-hosting
SCAL is browser-only. Your server route owns the API key, rate limiting (server-side, in addition to the widget's client-side cooldown), prompt-injection defense, and content moderation. The package never makes outbound LLM calls itself.
The minimum your route needs to do:
- Accept a POST with the AgentRequest JSON body
- Retrieve relevant content from your knowledge index (Gnosys Web or other)
- Call your LLM with the system prompt + retrieved context + conversation history
- Validate the LLM's JSON response, returning an AgentResponse
- Return
{ blocked: true }for moderation failures, or{ error: "..." }for system failures