@assemble-inc/chat-widget 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +274 -18
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,34 +1,290 @@
|
|
|
1
|
-
|
|
1
|
+
# @assemble-inc/chat-widget
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An embeddable, general-purpose AI chat widget. Drop it into any React app — or any plain HTML page — to give users a floating chat interface backed by Anthropic Claude and MCP (Model Context Protocol). Fully configurable at embed time: system prompt, model, knowledge base, UI copy, and more.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
npm
|
|
9
|
-
# or
|
|
10
|
-
yarn dev
|
|
10
|
+
npm install @assemble-inc/chat-widget
|
|
11
11
|
# or
|
|
12
|
-
|
|
12
|
+
yarn add @assemble-inc/chat-widget
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Peer dependencies** — make sure these are already in your project:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install react react-dom
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### 1. Mount the widget
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { Widget } from "@assemble-inc/chat-widget";
|
|
29
|
+
import "@assemble-inc/chat-widget/style.css";
|
|
30
|
+
|
|
31
|
+
export default function App() {
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
{/* your app */}
|
|
35
|
+
<Widget
|
|
36
|
+
config={{
|
|
37
|
+
systemPrompt: "You are a helpful assistant for Acme Corp.",
|
|
38
|
+
title: "Ask Us Anything",
|
|
39
|
+
placeholder: "Type your question...",
|
|
40
|
+
}}
|
|
41
|
+
/>
|
|
42
|
+
</>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The widget renders as a fixed floating button in the bottom-right corner (configurable). Clicking it opens the chat panel.
|
|
48
|
+
|
|
49
|
+
### 2. Add the server-side handler
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// server.ts
|
|
53
|
+
import express from "express";
|
|
54
|
+
import { createChatHandler } from "@assemble-inc/chat-widget/server";
|
|
55
|
+
|
|
56
|
+
const app = express();
|
|
57
|
+
app.use(express.json());
|
|
58
|
+
|
|
59
|
+
app.post(
|
|
60
|
+
"/api/chat",
|
|
61
|
+
createChatHandler({
|
|
62
|
+
apiKey: process.env.ANTHROPIC_API_KEY!,
|
|
63
|
+
mcpServerUrl: process.env.MCP_SERVER_URL!,
|
|
64
|
+
mcpBearerToken: process.env.MCP_BEARER_TOKEN,
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
app.listen(3001);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 3. Environment variables
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
75
|
+
MCP_SERVER_URL=https://your-mcp-server.example.com/mcp
|
|
76
|
+
MCP_BEARER_TOKEN=your-bearer-token # optional
|
|
77
|
+
SYSTEM_PROMPT="You are..." # optional server-side override
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Script-tag Embed (no React required)
|
|
83
|
+
|
|
84
|
+
For non-React apps, use the self-contained IIFE bundle:
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<link rel="stylesheet" href="https://cdn.example.com/asm-widget/index.css" />
|
|
88
|
+
<script src="https://cdn.example.com/asm-widget/embed.js"></script>
|
|
89
|
+
<script>
|
|
90
|
+
AsmWidget.init({
|
|
91
|
+
endpoint: "https://my-server.com/api/chat",
|
|
92
|
+
systemPrompt: "You are a helpful assistant.",
|
|
93
|
+
title: "Help",
|
|
94
|
+
});
|
|
95
|
+
</script>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Build the embed bundle:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm run build:embed # outputs dist/embed.js
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Configuration Reference
|
|
107
|
+
|
|
108
|
+
### `WidgetConfig`
|
|
109
|
+
|
|
110
|
+
All configuration is passed as a single `config` prop. Every field is optional.
|
|
111
|
+
|
|
112
|
+
#### Network
|
|
113
|
+
|
|
114
|
+
| Field | Type | Default | Description |
|
|
115
|
+
| ---------- | -------- | ------------- | -------------------------- |
|
|
116
|
+
| `endpoint` | `string` | `'/api/chat'` | Backend chat endpoint URL. |
|
|
117
|
+
|
|
118
|
+
#### AI Behavior
|
|
119
|
+
|
|
120
|
+
These fields are forwarded to the server on every request. **Server-side values always win** when both are set.
|
|
121
|
+
|
|
122
|
+
| Field | Type | Description |
|
|
123
|
+
| -------------- | -------- | ------------------------------------------------------------------------------------------------- |
|
|
124
|
+
| `systemPrompt` | `string` | Full system prompt for this embed. Used when the server has no `systemPrompt` configured. |
|
|
125
|
+
| `context` | `string` | Additional context appended after the system prompt (e.g. current page, user role). |
|
|
126
|
+
| `mcpServerUrl` | `string` | Which MCP server (knowledge base) to use. Must appear in the server's `mcpCredentials` allowlist. |
|
|
127
|
+
| `model` | `string` | Claude model ID (e.g. `'claude-opus-4-5'`). Defaults to `claude-sonnet-4-5`. |
|
|
128
|
+
| `temperature` | `number` | Response creativity, 0–1. |
|
|
129
|
+
|
|
130
|
+
#### Conversation
|
|
131
|
+
|
|
132
|
+
| Field | Type | Description |
|
|
133
|
+
| ----------------- | -------------------------- | ------------------------------------------------------------------ |
|
|
134
|
+
| `initialMessages` | `Array<{ role, content }>` | Seed messages shown when the widget first opens. |
|
|
135
|
+
| `persist` | `boolean` | Save the conversation to `sessionStorage` across page navigations. |
|
|
136
|
+
|
|
137
|
+
#### User Identity
|
|
138
|
+
|
|
139
|
+
| Field | Type | Description |
|
|
140
|
+
| ------ | ----------------------- | --------------------------------------------------------------------------- |
|
|
141
|
+
| `user` | `{ id?, name?, role? }` | Forwarded in every request body for server-side personalisation or logging. |
|
|
142
|
+
|
|
143
|
+
#### UI
|
|
144
|
+
|
|
145
|
+
| Field | Type | Default | Description |
|
|
146
|
+
| ---------------- | --------------------------------- | ---------------- | --------------------------------- |
|
|
147
|
+
| `title` | `string` | `'Ask ASMBL'` | Header title. |
|
|
148
|
+
| `logo` | `string` | Built-in logo | URL to a custom logo image. |
|
|
149
|
+
| `welcomeHeading` | `string` | — | Heading shown in the empty state. |
|
|
150
|
+
| `placeholder` | `string` | — | Textarea placeholder text. |
|
|
151
|
+
| `position` | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | Widget anchor corner. |
|
|
152
|
+
|
|
153
|
+
### `WidgetProps`
|
|
154
|
+
|
|
155
|
+
| Prop | Type | Description |
|
|
156
|
+
| -------------- | ---------------------------------- | ------------------------------------------------------------------------------------ |
|
|
157
|
+
| `config` | `WidgetConfig` | All configuration (see above). |
|
|
158
|
+
| `children` | `ReactNode` | Custom content shown in the empty state. Replaces the built-in empty state entirely. |
|
|
159
|
+
| `open` | `boolean` | Control open/close state from outside (controlled mode). |
|
|
160
|
+
| `defaultOpen` | `boolean` | Initial open state when uncontrolled. Defaults to `false`. |
|
|
161
|
+
| `onOpenChange` | `(open: boolean) => void` | Called whenever the widget opens or closes. |
|
|
162
|
+
| `onMessage` | `(msg: { role, content }) => void` | Called after each new assistant message. |
|
|
163
|
+
| `onError` | `(error: Error) => void` | Called when a stream error occurs. |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Server Reference
|
|
168
|
+
|
|
169
|
+
### `createChatHandler(config)`
|
|
170
|
+
|
|
171
|
+
Returns an Express-compatible `(req, res) => Promise<void>` request handler that streams Claude responses via your MCP server.
|
|
172
|
+
|
|
173
|
+
| Option | Type | Required | Description |
|
|
174
|
+
| ---------------- | ------------------------ | -------- | -------------------------------------------------------------------------------- |
|
|
175
|
+
| `apiKey` | `string` | Yes | Anthropic API key. |
|
|
176
|
+
| `mcpServerUrl` | `string` | Yes | Default Streamable HTTP URL of your MCP server. |
|
|
177
|
+
| `mcpBearerToken` | `string` | No | Bearer token for the default MCP server. |
|
|
178
|
+
| `model` | `string` | No | Claude model ID. Overrides widget value. Defaults to `claude-sonnet-4-5`. |
|
|
179
|
+
| `systemPrompt` | `string` | No | Operator system prompt. Overrides widget value. |
|
|
180
|
+
| `temperature` | `number` | No | Model temperature. Overrides widget value. |
|
|
181
|
+
| `mcpCredentials` | `Record<string, string>` | No | Map of additional MCP server URLs → bearer tokens for multi-context deployments. |
|
|
182
|
+
|
|
183
|
+
#### Precedence rules
|
|
184
|
+
|
|
185
|
+
For `systemPrompt`, `model`, `temperature`, and `mcpServerUrl`, the server-side value **always takes precedence** over what the widget sends. The resolution order is:
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
ChatHandlerConfig value → widget config value → built-in default
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### Multi-context deployments
|
|
192
|
+
|
|
193
|
+
A single server can serve multiple embeds pointing at different MCP servers:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
createChatHandler({
|
|
197
|
+
apiKey: process.env.ANTHROPIC_API_KEY!,
|
|
198
|
+
mcpServerUrl: process.env.MCP_SERVER_URL!, // default fallback
|
|
199
|
+
mcpBearerToken: process.env.MCP_BEARER_TOKEN,
|
|
200
|
+
mcpCredentials: {
|
|
201
|
+
"https://mcp.acme.com/hr": process.env.MCP_HR_TOKEN,
|
|
202
|
+
"https://mcp.acme.com/it": process.env.MCP_IT_TOKEN,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
13
205
|
```
|
|
14
206
|
|
|
15
|
-
|
|
207
|
+
Each embed specifies which server it wants via `config.mcpServerUrl`. Bearer tokens are looked up server-side — they are never exposed to the browser.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Examples
|
|
16
212
|
|
|
17
|
-
|
|
213
|
+
### Minimal embed
|
|
18
214
|
|
|
19
|
-
|
|
215
|
+
```tsx
|
|
216
|
+
<Widget config={{ systemPrompt: "You are a helpful assistant." }} />
|
|
217
|
+
```
|
|
20
218
|
|
|
21
|
-
|
|
219
|
+
### Fully configured embed
|
|
22
220
|
|
|
23
|
-
|
|
221
|
+
```tsx
|
|
222
|
+
<Widget
|
|
223
|
+
config={{
|
|
224
|
+
endpoint: "https://api.acme.com/chat",
|
|
225
|
+
systemPrompt: "You are an HR assistant for Acme Corp.",
|
|
226
|
+
mcpServerUrl: "https://mcp.acme.com/hr",
|
|
227
|
+
model: "claude-opus-4-5",
|
|
228
|
+
temperature: 0.3,
|
|
229
|
+
title: "Ask HR",
|
|
230
|
+
welcomeHeading: "How can we help?",
|
|
231
|
+
placeholder: "Ask an HR question...",
|
|
232
|
+
position: "bottom-left",
|
|
233
|
+
persist: true,
|
|
234
|
+
user: { id: currentUser.id, name: currentUser.name },
|
|
235
|
+
}}
|
|
236
|
+
onMessage={({ content }) => analytics.track("chat_message", { content })}
|
|
237
|
+
onOpenChange={(open) => setHelpOpen(open)}
|
|
238
|
+
/>
|
|
239
|
+
```
|
|
24
240
|
|
|
25
|
-
|
|
26
|
-
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
241
|
+
### Custom empty state (children)
|
|
27
242
|
|
|
28
|
-
|
|
243
|
+
```tsx
|
|
244
|
+
<Widget config={{ title: "Support", placeholder: "Describe your issue..." }}>
|
|
245
|
+
<div>
|
|
246
|
+
<h3>How can we help?</h3>
|
|
247
|
+
<button onClick={() => sendMessage("Reset my password")}>
|
|
248
|
+
Reset my password
|
|
249
|
+
</button>
|
|
250
|
+
<button onClick={() => sendMessage("Billing question")}>
|
|
251
|
+
Billing question
|
|
252
|
+
</button>
|
|
253
|
+
</div>
|
|
254
|
+
</Widget>
|
|
255
|
+
```
|
|
29
256
|
|
|
30
|
-
|
|
257
|
+
---
|
|
31
258
|
|
|
32
|
-
|
|
259
|
+
## Local Development
|
|
33
260
|
|
|
34
|
-
|
|
261
|
+
```bash
|
|
262
|
+
# Install dependencies
|
|
263
|
+
npm install
|
|
264
|
+
|
|
265
|
+
# Copy and fill in environment variables
|
|
266
|
+
cp .env.example .env
|
|
267
|
+
|
|
268
|
+
# Start the Vite dev server (port 3003) + Express API server (port 3001)
|
|
269
|
+
npm run dev
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The Vite dev server proxies `/api/*` to `http://localhost:3001`.
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
npm run lint # type-check without emitting
|
|
276
|
+
npm run build # build the React library (dist/)
|
|
277
|
+
npm run build:embed # build the standalone IIFE script (dist/embed.js)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Publishing
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
npm version patch # bug fixes (0.1.x)
|
|
286
|
+
npm version minor # new features (0.x.0)
|
|
287
|
+
npm version major # breaking changes (x.0.0)
|
|
288
|
+
|
|
289
|
+
npm publish
|
|
290
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assemble-inc/chat-widget",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Embeddable AI chat widget powered by Anthropic and MCP",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chat",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"import": "./dist/server.js",
|
|
26
26
|
"require": "./dist/server.cjs"
|
|
27
27
|
},
|
|
28
|
-
"./style.css": "./dist/index.css"
|
|
28
|
+
"./style.css": "./dist/index.css",
|
|
29
|
+
"./embed": "./dist/embed.js"
|
|
29
30
|
},
|
|
30
31
|
"files": [
|
|
31
32
|
"dist"
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
"scripts": {
|
|
37
38
|
"dev": "concurrently \"vite\" \"tsx watch server.ts\"",
|
|
38
39
|
"build": "vite build && tsup",
|
|
40
|
+
"build:embed": "vite build --config vite.embed.config.ts",
|
|
39
41
|
"preview": "vite preview",
|
|
40
42
|
"lint": "tsc --noEmit",
|
|
41
43
|
"auth": "tsx scripts/auth.ts"
|
|
@@ -50,7 +52,8 @@
|
|
|
50
52
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
51
53
|
"ai": "^6.0.176",
|
|
52
54
|
"class-variance-authority": "^0.7.1",
|
|
53
|
-
"express": "^4.21.2"
|
|
55
|
+
"express": "^4.21.2",
|
|
56
|
+
"react-markdown": "^9.0.1"
|
|
54
57
|
},
|
|
55
58
|
"devDependencies": {
|
|
56
59
|
"@tailwindcss/postcss": "^4.2.4",
|