sagedesk 1.0.0 → 2.1.0

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 CHANGED
@@ -1,19 +1,40 @@
1
1
  <div align="center">
2
2
  <img src="https://raw.githubusercontent.com/mzeeshanwahid/sagedesk/main/assets/cover.jpg" width="1200" alt="sagedesk cover" />
3
3
  <h1 style="margin-top: 16px;">SageDesk</h1>
4
- <p>Local RAG-powered support chat widget. No API key. No backend. No monthly cost. Semantic search runs entirely in the visitor's browser via WebAssembly.</p>
4
+ <p>RAG-powered support chat widget for any website. Run entirely in the browser with no API key, or connect your own backend for LLM-synthesized answers.</p>
5
5
  </div>
6
6
 
7
7
  <br/>
8
8
 
9
- <p align="center"><a href="https://www.npmjs.com/package/sagedesk"><img src="https://img.shields.io/npm/v/sagedesk?color=0ea5e9&label=npm" alt="npm version" /></a> <a href="https://bundlephobia.com/package/sagedesk"><img src="https://img.shields.io/bundlephobia/minzip/sagedesk?color=22c55e&label=gzipped" alt="bundle size" /></a> <a href="./LICENSE"><img src="https://img.shields.io/npm/l/sagedesk?color=a855f7" alt="license" /></a> <a href="https://github.com/mzeeshanwahid/sagedesk/actions"><img src="https://img.shields.io/github/actions/workflow/status/mzeeshanwahid/sagedesk/ci.yml?label=tests" alt="tests" /></a> <a href="./package.json"><img src="https://img.shields.io/badge/dependencies-zero-f97316" alt="zero dependencies" /></a> <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5.x-3178c6" alt="TypeScript" /></a></p>
9
+ <p align="center"><a href="https://www.npmjs.com/package/sagedesk"><img src="https://img.shields.io/npm/v/sagedesk?color=0ea5e9&label=npm" alt="npm version" /></a> <a href="./LICENSE"><img src="https://img.shields.io/npm/l/sagedesk?color=a855f7" alt="license" /></a> <a href="https://github.com/mzeeshanwahid/sagedesk/actions"><img src="https://img.shields.io/github/actions/workflow/status/mzeeshanwahid/sagedesk/ci.yml?label=tests" alt="tests" /></a> <a href="./package.json"><img src="https://img.shields.io/npm/dependency-count/sagedesk?color=f97316" alt="dependencies" /></a> <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5.x-3178c6" alt="TypeScript" /></a></p>
10
10
 
11
11
  ---
12
12
 
13
- ## How it works
13
+ ## Operating Modes
14
14
 
15
- 1. **Build time** - You run `npx sagedesk build` on your machine. It reads your `knowledge.json`, embeds every entry using a local transformer model (default: `all-MiniLM-L6-v2`), and writes a minified vector index to a static JSON file.
16
- 2. **Runtime** - The widget fetches the index and loads the same model via WebAssembly. Visitor queries are embedded in-browser and matched against the index using optimized semantic search in under 100ms. **No API call is ever made.**
15
+ sagedesk ships two modes. Pick the one that fits your needs.
16
+
17
+ ### Local Mode (default)
18
+
19
+ All embedding and semantic search runs entirely in the visitor's browser via WebAssembly. No API key required. No backend. No per-query cost.
20
+
21
+ 1. **Build time** - Run `npx sagedesk build` on your machine. It reads your `knowledge.json`, embeds every entry using a local transformer model (default: `all-MiniLM-L6-v2`), and writes a minified vector index to a static JSON file.
22
+ 2. **Runtime** - The widget fetches the index and loads the same model via WebAssembly. Visitor queries are embedded in-browser and matched against the index using optimized semantic search in under 100ms. **No API call is ever made.**
23
+
24
+ ### LLM Mode
25
+
26
+ The widget posts visitor queries to your own backend. Your backend handles embedding, retrieval, and LLM synthesis. The API key lives in your environment variables and never touches the browser. sagedesk provides ready-made server handlers for Next.js and Express - you own your entire stack.
27
+
28
+ | | Local Mode | LLM Mode |
29
+ |---|---|---|
30
+ | API key required | No | Yes, yours |
31
+ | Backend required | No | Yes, yours |
32
+ | sagedesk infrastructure | None | None |
33
+ | Answer style | Exact retrieval | Natural, synthesized |
34
+ | Latency | < 100ms | 1–3 seconds |
35
+ | Cost | Zero | Per-query LLM API cost |
36
+ | Privacy | Fully local | Query sent to your LLM provider |
37
+ | Error resilience | N/A | Built-in: timeouts, fallbacks, automatic recovery |
17
38
 
18
39
  ---
19
40
 
@@ -25,7 +46,9 @@ npm install sagedesk
25
46
 
26
47
  ---
27
48
 
28
- ## Step 1 - Write your knowledge file
49
+ ## Local Mode Setup
50
+
51
+ ### Step 1 - Write your knowledge file
29
52
 
30
53
  Create `knowledge.json` at the root of your project.
31
54
 
@@ -50,10 +73,10 @@ Create `knowledge.json` at the root of your project.
50
73
  }
51
74
  ```
52
75
 
53
- ### Knowledge Schema
76
+ #### Knowledge Schema
54
77
 
55
78
  | Field | Type | Required | Description |
56
- |---|:---:|:---:|:---:|
79
+ |---|:---:|:---:|---|
57
80
  | `knowledge[].id` | `string` | yes | Unique identifier for the entry. |
58
81
  | `knowledge[].queries` | `string[]` | no | **Recommended.** Multiple phrasings for better matching. |
59
82
  | `knowledge[].question` | `string` | no | Legacy single-question field. |
@@ -61,29 +84,29 @@ Create `knowledge.json` at the root of your project.
61
84
 
62
85
  ---
63
86
 
64
- ## Step 2 - Build the index
87
+ ### Step 2 - Build the index
65
88
 
66
89
  ```bash
67
90
  npx sagedesk build --input knowledge.json --output public/support-index.json
68
91
  ```
69
92
 
70
- This generates the vector index. Run this whenever your knowledge file changes.
93
+ This generates the vector index. Re-run it whenever your knowledge file changes.
71
94
 
72
- ### CLI Options
95
+ #### CLI Options
73
96
 
74
97
  | Option | Description | Default |
75
- |---|:---:|:---:|
76
- | `-i, --input <path>` | Path to knowledge JSON (Required) | - |
98
+ |---|---|:---:|
99
+ | `-i, --input <path>` | Path to knowledge JSON | **Required** |
77
100
  | `-o, --output <path>` | Output path for index JSON | `./public/support-index.json` |
78
101
  | `--model <name>` | Embedding model to use | `all-MiniLM-L6-v2` |
79
- | `--minScore <number>` | Confidence threshold (0.0 to 1.0) | `0.42` |
102
+ | `--minScore <number>` | Confidence threshold (0.01.0) | `0.42` |
80
103
  | `--verbose` | Print chunk details during build | `false` |
81
104
 
82
105
  ---
83
106
 
84
- ## Step 3 - Add the widget
107
+ ### Step 3 - Add the widget
85
108
 
86
- ### Vanilla HTML / JS
109
+ #### Vanilla HTML / JS
87
110
 
88
111
  ```html
89
112
  <script type="module">
@@ -101,7 +124,7 @@ This generates the vector index. Run this whenever your knowledge file changes.
101
124
  </script>
102
125
  ```
103
126
 
104
- ### React
127
+ #### React
105
128
 
106
129
  ```tsx
107
130
  import { SageDeskWidget } from 'sagedesk/react';
@@ -110,17 +133,17 @@ export default function App() {
110
133
  return (
111
134
  <SageDeskWidget
112
135
  indexUrl="/support-index.json"
113
- agent={{
114
- name: 'Support',
136
+ agent={{
137
+ name: 'Support',
115
138
  accentColor: '#534AB7',
116
- theme: 'light'
139
+ theme: 'light'
117
140
  }}
118
141
  />
119
142
  );
120
143
  }
121
144
  ```
122
145
 
123
- ### Next.js (App Router)
146
+ #### Next.js (App Router)
124
147
 
125
148
  Place in your root layout for site-wide availability.
126
149
 
@@ -135,9 +158,9 @@ export default function RootLayout({ children }) {
135
158
  {children}
136
159
  <SageDeskNext
137
160
  indexUrl="/support-index.json"
138
- agent={{
139
- name: 'Support',
140
- theme: 'dark'
161
+ agent={{
162
+ name: 'Support',
163
+ theme: 'dark'
141
164
  }}
142
165
  />
143
166
  </body>
@@ -148,42 +171,265 @@ export default function RootLayout({ children }) {
148
171
 
149
172
  ---
150
173
 
151
- ## Configuration (`AgentConfig`)
174
+ ## LLM Mode Setup
175
+
176
+ LLM mode requires the same `knowledge.json` and built index from the steps above. You also need an API key from any supported provider (OpenAI, Anthropic, Gemini, DeepSeek, Groq, or any OpenAI-compatible service).
177
+
178
+ ### Step 1 - Add your API key
179
+
180
+ Add your key to your backend's environment variables. It must never be exposed to the browser.
181
+
182
+ ```
183
+ SAGEDESK_LLM_API_KEY=sk-...
184
+ ```
185
+
186
+ ### Step 2 - Register the server handler
187
+
188
+ sagedesk exports a server handler from `sagedesk/server`. Drop it into your existing backend - no new server required.
189
+
190
+ #### Next.js App Router
191
+
192
+ ```ts
193
+ // app/api/sagedesk/route.ts
194
+ import { createSageDeskHandler } from 'sagedesk/server';
195
+ import { resolve } from 'path';
196
+
197
+ export const POST = createSageDeskHandler({
198
+ indexPath: resolve(process.cwd(), 'public', 'sagedesk-index.json'),
199
+ provider: 'deepseek',
200
+ apiKey: process.env.SAGEDESK_LLM_API_KEY!,
201
+ model: 'deepseek-chat',
202
+ });
203
+ ```
204
+
205
+ #### Express
206
+
207
+ ```ts
208
+ import express from 'express';
209
+ import { createSageDeskMiddleware } from 'sagedesk/server';
210
+ import { resolve } from 'path';
211
+
212
+ const app = express();
213
+ app.use(express.json());
214
+
215
+ app.use('/api/sagedesk', createSageDeskMiddleware({
216
+ indexPath: resolve(process.cwd(), 'public', 'sagedesk-index.json'),
217
+ provider: 'openai',
218
+ apiKey: process.env.SAGEDESK_LLM_API_KEY!,
219
+ model: 'gpt-4o-mini',
220
+ }));
221
+ ```
222
+
223
+ > **Serverless & Vercel compatible.** The server handler uses a pure WebAssembly embedding backend with no native binary dependencies. It works out of the box on Vercel, AWS Lambda, and any other serverless platform — no additional configuration required.
224
+
225
+ ### Step 3 - Configure the widget
226
+
227
+ Point the widget at your endpoint with `mode="llm"`. No `indexUrl` needed on the client.
228
+
229
+ #### React
230
+
231
+ ```tsx
232
+ import { SageDeskWidget } from 'sagedesk/react';
233
+
234
+ export default function App() {
235
+ return (
236
+ <SageDeskWidget
237
+ mode="llm"
238
+ endpoint="/api/sagedesk"
239
+ agent={{
240
+ name: 'Support',
241
+ theme: 'dark'
242
+ }}
243
+ />
244
+ );
245
+ }
246
+ ```
247
+
248
+ #### Next.js (App Router)
249
+
250
+ ```tsx
251
+ // app/layout.tsx
252
+ import { SageDeskNext } from 'sagedesk/next';
253
+
254
+ export default function RootLayout({ children }) {
255
+ return (
256
+ <html lang="en">
257
+ <body>
258
+ {children}
259
+ <SageDeskNext
260
+ mode="llm"
261
+ endpoint="/api/sagedesk"
262
+ agent={{
263
+ name: 'Support',
264
+ theme: 'dark'
265
+ }}
266
+ />
267
+ </body>
268
+ </html>
269
+ );
270
+ }
271
+ ```
272
+
273
+ ### Supported LLM Providers
274
+
275
+ The `provider` field accepts either a **provider name string** or a **full API base URL**. Use a provider name for built-in support, or pass a custom URL if you're using a self-hosted model or a provider not listed below.
276
+
277
+ #### Built-in Providers
278
+
279
+ OpenAI, Gemini, DeepSeek, and Groq all use the OpenAI-compatible chat completions format. Anthropic uses its own wire format and is handled natively.
280
+
281
+ | Provider | `provider` value | Example model |
282
+ |---|---|---|
283
+ | OpenAI | `'openai'` | `gpt-4o-mini` |
284
+ | Anthropic (Claude) | `'anthropic'` | `claude-haiku-4-5-20251001` |
285
+ | Google Gemini | `'gemini'` | `gemini-2.0-flash` |
286
+ | DeepSeek | `'deepseek'` | `deepseek-chat` |
287
+ | Groq | `'groq'` | `llama3-8b-8192` |
288
+
289
+ #### Custom Providers
290
+
291
+ If your provider is not listed above, pass the full API base URL as the `provider` value:
292
+
293
+ ```ts
294
+ createSageDeskHandler({
295
+ indexPath: './public/support-index.json',
296
+ provider: 'https://api.example.com/v1', // Custom base URL
297
+ apiKey: process.env.CUSTOM_LLM_API_KEY!,
298
+ model: 'your-model-name',
299
+ });
300
+ ```
301
+
302
+ ### Server Handler Options (`SageDeskHandlerConfig`)
303
+
304
+ | Option | Type | Required | Description |
305
+ |---|:---:|:---:|---|
306
+ | `indexPath` | `string` | yes | Filesystem path to the built index JSON. |
307
+ | `provider` | `string` | yes | Provider name (e.g., `'openai'`, `'anthropic'`) or full API base URL (e.g., `'https://api.example.com/v1'`). |
308
+ | `apiKey` | `string` | yes | LLM API key (server-side only). |
309
+ | `model` | `string` | yes | Model name passed to the provider. |
310
+ | `embeddingModel` | `string` | no | Must match build-time model. Defaults to `all-MiniLM-L6-v2`. |
311
+ | `topK` | `number` | no | Number of chunks retrieved for context. Defaults to `5`. |
312
+ | `minScore` | `number` | no | Minimum similarity score for a chunk. Defaults to `0.42`. |
313
+ | `systemPrompt` | `string` | no | Override the default system prompt sent to the LLM. |
314
+ | `llmTimeoutMs` | `number` | no | Timeout for LLM API calls in milliseconds. Defaults to `5000` (5 seconds). |
315
+
316
+ ---
317
+
318
+ ## Error Handling & Fallbacks (LLM Mode)
319
+
320
+ sagedesk includes built-in resilience for LLM mode. If the LLM provider fails-whether due to authentication errors, quota exhaustion, timeouts, or malformed responses-the widget gracefully falls back without interrupting the user experience.
321
+
322
+ ### How It Works
323
+
324
+ 1. **Request Timeout** - Each LLM request is automatically aborted if it exceeds `llmTimeoutMs` (default: 5 seconds). This prevents the widget from hanging.
325
+
326
+ 2. **Automatic Fallback** - When an LLM request fails, the server returns the best matching knowledge chunks without synthesis. Visitors still get relevant, grounded information.
327
+
328
+ 3. **Developer Transparency** - The browser console logs meaningful warnings for debugging:
329
+ - `"[sagedesk] Support service authentication failed. Showing relevant knowledge instead."` - Invalid or expired API key
330
+ - `"[sagedesk] Support service quota exhausted. Showing relevant knowledge instead."` - Rate limit hit
331
+ - `"[sagedesk] Support service took too long to respond. Showing relevant knowledge instead."` - Timeout
332
+ - `"[sagedesk] Support service error. Showing relevant knowledge instead."` - Generic API error
333
+ - `"[sagedesk] Support service returned invalid response. Showing relevant knowledge instead."` - Malformed response
334
+
335
+ 4. **User Experience** - Visitors always see a fallback message (configured via `agent.fallback` or `agent.fallbackPool`) alongside relevant knowledge chunks. No errors are exposed to users.
336
+
337
+ ### Configuring Timeout
338
+
339
+ Adjust the LLM request timeout based on your provider's typical response time:
340
+
341
+ ```ts
342
+ // Next.js
343
+ export const POST = createSageDeskHandler({
344
+ indexPath: './public/support-index.json',
345
+ provider: 'deepseek',
346
+ apiKey: process.env.SAGEDESK_LLM_API_KEY!,
347
+ model: 'deepseek-chat',
348
+ llmTimeoutMs: 8000, // 8 seconds
349
+ });
350
+ ```
351
+
352
+ ```ts
353
+ // Express
354
+ app.use('/api/sagedesk', createSageDeskMiddleware({
355
+ indexPath: './public/support-index.json',
356
+ provider: 'openai',
357
+ apiKey: process.env.SAGEDESK_LLM_API_KEY!,
358
+ model: 'gpt-4o-mini',
359
+ llmTimeoutMs: 10000, // 10 seconds
360
+ }));
361
+ ```
362
+
363
+ ---
364
+
365
+ ## Widget Configuration (`AgentConfig`)
366
+
367
+ Applies to both modes.
152
368
 
153
369
  | Field | Type | Default | Description |
154
- |---|:---:|:---:|:---:|
370
+ |---|:---:|:---:|---|
155
371
  | `name` | `string` | **Required** | Display name in the chat header. |
156
372
  | `theme` | `classic`, `light`, `dark` | `classic` | Visual style of the widget. |
157
- | `model` | `string` | `all-MiniLM-L6-v2` | **Must match build-time model.** |
373
+ | `model` | `string` | `all-MiniLM-L6-v2` | Embedding model. Must match build-time model. Local mode only. |
158
374
  | `accentColor` | `string` | `#534AB7` | Hex color for primary UI elements. |
159
375
  | `greeting` | `string` | - | Initial message shown to visitors. |
160
- | `fallback` | `string` | - | Message shown when no match is found. |
376
+ | `fallback` | `string` | - | Message shown when no answer is found. |
377
+ | `fallbackPool` | `string[]` | - | Array of fallback messages. One is randomly selected when no answer is found. |
161
378
  | `position` | `bottom-right`, `bottom-left` | `bottom-right` | Widget placement. |
162
- | `avatarUrl` | `string` | - | Optional URL for the agent's avatar. |
163
- | `contactUrl` | `string` | - | Link shown in fallback responses. |
164
- | `poweredBy` | `boolean` | `true` | Show "Powered by sagedesk" footer. |
379
+ | `avatarUrl` | `string` | - | URL for the agent's avatar image. |
380
+ | `contactUrl` | `string` | - | Link appended to fallback responses. |
165
381
  | `suggestedChips` | `string[]` | - | Override auto-generated suggested questions. |
166
382
 
167
383
  ---
168
384
 
385
+ ## Search Configuration (`SearchConfig`)
386
+
387
+ Optional. Applies to both modes. Controls how semantic search matches answers.
388
+
389
+ | Field | Type | Default | Description |
390
+ |---|:---:|:---:|---|
391
+ | `minScore` | `number` | `0.42` | Minimum similarity score (0.0–1.0) required for a result to be considered a match. Lower values return more results but may be less relevant. |
392
+ | `topK` | `number` | `5` | Maximum number of chunks to retrieve and consider for the answer. |
393
+
394
+ ### Example: Custom Search Settings
395
+
396
+ ```tsx
397
+ // Local mode
398
+ <SageDeskWidget
399
+ indexUrl="/support-index.json"
400
+ agent={{ name: 'Support' }}
401
+ search={{ minScore: 0.5, topK: 3 }}
402
+ />
403
+
404
+ // LLM mode
405
+ <SageDeskWidget
406
+ mode="llm"
407
+ endpoint="/api/sagedesk"
408
+ agent={{ name: 'Support' }}
409
+ search={{ minScore: 0.6, topK: 5 }}
410
+ />
411
+ ```
412
+
413
+ ---
414
+
169
415
  ## Model Selection
170
416
 
171
- sagedesk defaults to `all-MiniLM-L6-v2` (~22MB), which offers an excellent balance of speed and quality for English.
417
+ sagedesk defaults to `all-MiniLM-L6-v2` (~22MB), which offers an excellent balance of speed and quality for English. The model used at build time and at runtime must match.
172
418
 
173
419
  | Model | Dimensions | Size | Best For |
174
- |---|:---:|:---:|:---:|
420
+ |---|:---:|:---:|---|
175
421
  | `all-MiniLM-L6-v2` | 384 | ~22 MB | Most English sites. |
176
422
  | `bge-small-en-v1-5` | 384 | ~25 MB | High-precision English. |
177
423
  | `paraphrase-multilingual-MiniLM-L12-v2` | 384 | ~45 MB | 50+ languages. |
178
424
  | `all-mpnet-base-v2` | 768 | ~85 MB | Maximum semantic quality. |
179
425
 
180
- > **Note:** The model used in `npx sagedesk build --model <name>` must match the `agent.model` property in your runtime configuration.
426
+ > **Note:** The `--model` flag in `npx sagedesk build` must match the `agent.model` prop (local mode) or the `embeddingModel` option in your server handler (LLM mode).
181
427
 
182
428
  ---
183
429
 
184
430
  ## Browser Support
185
431
 
186
- Requires **WebAssembly** support.
432
+ Requires **WebAssembly** support (local mode only - LLM mode has no browser requirements beyond `fetch`).
187
433
 
188
434
  - Chrome 90+
189
435
  - Firefox 89+