sagedesk 1.0.0 → 2.0.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/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>
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,106 @@ 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'
164
+ }}
165
+ />
166
+ </body>
167
+ </html>
168
+ );
169
+ }
170
+ ```
171
+
172
+ ---
173
+
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
+
196
+ export const POST = createSageDeskHandler({
197
+ indexPath: './public/support-index.json',
198
+ provider: 'deepseek',
199
+ apiKey: process.env.SAGEDESK_LLM_API_KEY!,
200
+ model: 'deepseek-chat',
201
+ });
202
+ ```
203
+
204
+ #### Express
205
+
206
+ ```ts
207
+ import express from 'express';
208
+ import { createSageDeskMiddleware } from 'sagedesk/server';
209
+
210
+ const app = express();
211
+ app.use(express.json());
212
+
213
+ app.use('/api/sagedesk', createSageDeskMiddleware({
214
+ indexPath: './public/support-index.json',
215
+ provider: 'openai',
216
+ apiKey: process.env.SAGEDESK_LLM_API_KEY!,
217
+ model: 'gpt-4o-mini',
218
+ }));
219
+ ```
220
+
221
+ ### Step 3 - Configure the widget
222
+
223
+ Point the widget at your endpoint with `mode="llm"`. No `indexUrl` needed on the client.
224
+
225
+ #### React
226
+
227
+ ```tsx
228
+ import { SageDeskWidget } from 'sagedesk/react';
229
+
230
+ export default function App() {
231
+ return (
232
+ <SageDeskWidget
233
+ mode="llm"
234
+ endpoint="/api/sagedesk"
235
+ agent={{
236
+ name: 'Support',
237
+ theme: 'dark'
238
+ }}
239
+ />
240
+ );
241
+ }
242
+ ```
243
+
244
+ #### Next.js (App Router)
245
+
246
+ ```tsx
247
+ // app/layout.tsx
248
+ import { SageDeskNext } from 'sagedesk/next';
249
+
250
+ export default function RootLayout({ children }) {
251
+ return (
252
+ <html lang="en">
253
+ <body>
254
+ {children}
255
+ <SageDeskNext
256
+ mode="llm"
257
+ endpoint="/api/sagedesk"
258
+ agent={{
259
+ name: 'Support',
260
+ theme: 'dark'
141
261
  }}
142
262
  />
143
263
  </body>
@@ -146,44 +266,166 @@ export default function RootLayout({ children }) {
146
266
  }
147
267
  ```
148
268
 
269
+ ### Supported LLM Providers
270
+
271
+ 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.
272
+
273
+ #### Built-in Providers
274
+
275
+ OpenAI, Gemini, DeepSeek, and Groq all use the OpenAI-compatible chat completions format. Anthropic uses its own wire format and is handled natively.
276
+
277
+ | Provider | `provider` value | Example model |
278
+ |---|---|---|
279
+ | OpenAI | `'openai'` | `gpt-4o-mini` |
280
+ | Anthropic (Claude) | `'anthropic'` | `claude-haiku-4-5-20251001` |
281
+ | Google Gemini | `'gemini'` | `gemini-2.0-flash` |
282
+ | DeepSeek | `'deepseek'` | `deepseek-chat` |
283
+ | Groq | `'groq'` | `llama3-8b-8192` |
284
+
285
+ #### Custom Providers
286
+
287
+ If your provider is not listed above, pass the full API base URL as the `provider` value:
288
+
289
+ ```ts
290
+ createSageDeskHandler({
291
+ indexPath: './public/support-index.json',
292
+ provider: 'https://api.example.com/v1', // Custom base URL
293
+ apiKey: process.env.CUSTOM_LLM_API_KEY!,
294
+ model: 'your-model-name',
295
+ });
296
+ ```
297
+
298
+ ### Server Handler Options (`SageDeskHandlerConfig`)
299
+
300
+ | Option | Type | Required | Description |
301
+ |---|:---:|:---:|---|
302
+ | `indexPath` | `string` | yes | Filesystem path to the built index JSON. |
303
+ | `provider` | `string` | yes | Provider name (e.g., `'openai'`, `'anthropic'`) or full API base URL (e.g., `'https://api.example.com/v1'`). |
304
+ | `apiKey` | `string` | yes | LLM API key (server-side only). |
305
+ | `model` | `string` | yes | Model name passed to the provider. |
306
+ | `embeddingModel` | `string` | no | Must match build-time model. Defaults to `all-MiniLM-L6-v2`. |
307
+ | `topK` | `number` | no | Number of chunks retrieved for context. Defaults to `5`. |
308
+ | `minScore` | `number` | no | Minimum similarity score for a chunk. Defaults to `0.42`. |
309
+ | `systemPrompt` | `string` | no | Override the default system prompt sent to the LLM. |
310
+ | `llmTimeoutMs` | `number` | no | Timeout for LLM API calls in milliseconds. Defaults to `5000` (5 seconds). |
311
+
149
312
  ---
150
313
 
151
- ## Configuration (`AgentConfig`)
314
+ ## Error Handling & Fallbacks (LLM Mode)
315
+
316
+ 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.
317
+
318
+ ### How It Works
319
+
320
+ 1. **Request Timeout** - Each LLM request is automatically aborted if it exceeds `llmTimeoutMs` (default: 5 seconds). This prevents the widget from hanging.
321
+
322
+ 2. **Automatic Fallback** - When an LLM request fails, the server returns the best matching knowledge chunks without synthesis. Visitors still get relevant, grounded information.
323
+
324
+ 3. **Developer Transparency** - The browser console logs meaningful warnings for debugging:
325
+ - `"[sagedesk] Support service authentication failed. Showing relevant knowledge instead."` - Invalid or expired API key
326
+ - `"[sagedesk] Support service quota exhausted. Showing relevant knowledge instead."` - Rate limit hit
327
+ - `"[sagedesk] Support service took too long to respond. Showing relevant knowledge instead."` - Timeout
328
+ - `"[sagedesk] Support service error. Showing relevant knowledge instead."` - Generic API error
329
+ - `"[sagedesk] Support service returned invalid response. Showing relevant knowledge instead."` - Malformed response
330
+
331
+ 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.
332
+
333
+ ### Configuring Timeout
334
+
335
+ Adjust the LLM request timeout based on your provider's typical response time:
336
+
337
+ ```ts
338
+ // Next.js
339
+ export const POST = createSageDeskHandler({
340
+ indexPath: './public/support-index.json',
341
+ provider: 'deepseek',
342
+ apiKey: process.env.SAGEDESK_LLM_API_KEY!,
343
+ model: 'deepseek-chat',
344
+ llmTimeoutMs: 8000, // 8 seconds
345
+ });
346
+ ```
347
+
348
+ ```ts
349
+ // Express
350
+ app.use('/api/sagedesk', createSageDeskMiddleware({
351
+ indexPath: './public/support-index.json',
352
+ provider: 'openai',
353
+ apiKey: process.env.SAGEDESK_LLM_API_KEY!,
354
+ model: 'gpt-4o-mini',
355
+ llmTimeoutMs: 10000, // 10 seconds
356
+ }));
357
+ ```
358
+
359
+ ---
360
+
361
+ ## Widget Configuration (`AgentConfig`)
362
+
363
+ Applies to both modes.
152
364
 
153
365
  | Field | Type | Default | Description |
154
- |---|:---:|:---:|:---:|
366
+ |---|:---:|:---:|---|
155
367
  | `name` | `string` | **Required** | Display name in the chat header. |
156
368
  | `theme` | `classic`, `light`, `dark` | `classic` | Visual style of the widget. |
157
- | `model` | `string` | `all-MiniLM-L6-v2` | **Must match build-time model.** |
369
+ | `model` | `string` | `all-MiniLM-L6-v2` | Embedding model. Must match build-time model. Local mode only. |
158
370
  | `accentColor` | `string` | `#534AB7` | Hex color for primary UI elements. |
159
371
  | `greeting` | `string` | - | Initial message shown to visitors. |
160
- | `fallback` | `string` | - | Message shown when no match is found. |
372
+ | `fallback` | `string` | - | Message shown when no answer is found. |
373
+ | `fallbackPool` | `string[]` | - | Array of fallback messages. One is randomly selected when no answer is found. |
161
374
  | `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. |
375
+ | `avatarUrl` | `string` | - | URL for the agent's avatar image. |
376
+ | `contactUrl` | `string` | - | Link appended to fallback responses. |
165
377
  | `suggestedChips` | `string[]` | - | Override auto-generated suggested questions. |
166
378
 
167
379
  ---
168
380
 
381
+ ## Search Configuration (`SearchConfig`)
382
+
383
+ Optional. Applies to both modes. Controls how semantic search matches answers.
384
+
385
+ | Field | Type | Default | Description |
386
+ |---|:---:|:---:|---|
387
+ | `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. |
388
+ | `topK` | `number` | `5` | Maximum number of chunks to retrieve and consider for the answer. |
389
+
390
+ ### Example: Custom Search Settings
391
+
392
+ ```tsx
393
+ // Local mode
394
+ <SageDeskWidget
395
+ indexUrl="/support-index.json"
396
+ agent={{ name: 'Support' }}
397
+ search={{ minScore: 0.5, topK: 3 }}
398
+ />
399
+
400
+ // LLM mode
401
+ <SageDeskWidget
402
+ mode="llm"
403
+ endpoint="/api/sagedesk"
404
+ agent={{ name: 'Support' }}
405
+ search={{ minScore: 0.6, topK: 5 }}
406
+ />
407
+ ```
408
+
409
+ ---
410
+
169
411
  ## Model Selection
170
412
 
171
- sagedesk defaults to `all-MiniLM-L6-v2` (~22MB), which offers an excellent balance of speed and quality for English.
413
+ 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
414
 
173
415
  | Model | Dimensions | Size | Best For |
174
- |---|:---:|:---:|:---:|
416
+ |---|:---:|:---:|---|
175
417
  | `all-MiniLM-L6-v2` | 384 | ~22 MB | Most English sites. |
176
418
  | `bge-small-en-v1-5` | 384 | ~25 MB | High-precision English. |
177
419
  | `paraphrase-multilingual-MiniLM-L12-v2` | 384 | ~45 MB | 50+ languages. |
178
420
  | `all-mpnet-base-v2` | 768 | ~85 MB | Maximum semantic quality. |
179
421
 
180
- > **Note:** The model used in `npx sagedesk build --model <name>` must match the `agent.model` property in your runtime configuration.
422
+ > **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
423
 
182
424
  ---
183
425
 
184
426
  ## Browser Support
185
427
 
186
- Requires **WebAssembly** support.
428
+ Requires **WebAssembly** support (local mode only - LLM mode has no browser requirements beyond `fetch`).
187
429
 
188
430
  - Chrome 90+
189
431
  - Firefox 89+