@sales-bot-llm/sdk 0.2.0 → 0.2.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 +271 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# @sales-bot-llm/sdk
|
|
2
|
+
|
|
3
|
+
Frontend SDK for the [Sales Bot](../sales_bot) chat API. A framework-agnostic core (~8 KB) plus thin adapters for React, Vue, a drop-in floating widget, and a plain `<script>` tag.
|
|
4
|
+
|
|
5
|
+
Sub-project #2 of 5 in the Sales Bot platform. Consumes the SSE wire format defined by sub-project #1 ([sales_bot/](../sales_bot)).
|
|
6
|
+
|
|
7
|
+
## What you get
|
|
8
|
+
|
|
9
|
+
| Entry point | Use when |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `@sales-bot-llm/sdk/core` | Custom UI, Node integration, or any framework not covered below. Returns a `SalesBotClient` with an async-iterable `ask()`. |
|
|
12
|
+
| `@sales-bot-llm/sdk/react` | React 18+ apps. `useSalesBot()` hook returns `{ ask, messages, isStreaming, error, conversationId, visitorToken, reset }`. |
|
|
13
|
+
| `@sales-bot-llm/sdk/vue` | Vue 3.4+ apps. Same shape as React, but state is exposed as `Ref<…>`. |
|
|
14
|
+
| `@sales-bot-llm/sdk/widget` | Drop-in floating launcher + chat panel rendered into a shadow DOM. `createWidget({ embedKey, theme }).mount()`. |
|
|
15
|
+
| `@sales-bot-llm/sdk/vanilla` | Plain HTML page. Load `dist/vanilla.global.js` via `<script>` → `window.SalesBot.default.widget({ … })`. |
|
|
16
|
+
|
|
17
|
+
All entry points talk to the same `SalesBotClient` and consume the same SSE event stream.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pnpm add @sales-bot-llm/sdk
|
|
23
|
+
# or: npm install @sales-bot-llm/sdk / yarn add @sales-bot-llm/sdk
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
React and Vue are optional peer deps — only install the one(s) you use.
|
|
27
|
+
|
|
28
|
+
## Quickstart
|
|
29
|
+
|
|
30
|
+
### Core (framework-agnostic)
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { SalesBotClient } from '@sales-bot-llm/sdk/core'
|
|
34
|
+
|
|
35
|
+
const client = new SalesBotClient({
|
|
36
|
+
embedKey: 'pk_live_...', // from the Bot config in the admin UI
|
|
37
|
+
baseUrl: 'https://api.example.com', // your sales_bot deployment
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
for await (const event of client.ask('What does your trial include?')) {
|
|
41
|
+
if (event.event === 'delta') {
|
|
42
|
+
process.stdout.write(event.data.content)
|
|
43
|
+
} else if (event.event === 'done') {
|
|
44
|
+
break
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`client.ask()` is an async iterable over the typed SSE union (`turn_started`, `delta`, `tool_call_started`, `tool_call_finished`, `message_complete`, `usage`, `done`, `error`). Each event type maps to a strongly-typed payload — see [src/core/types.ts](src/core/types.ts).
|
|
50
|
+
|
|
51
|
+
The client also offers:
|
|
52
|
+
|
|
53
|
+
- `client.identify({ externalId, email, name, traits })` — merges into the next `ask()` call.
|
|
54
|
+
- `client.on(eventName, handler)` — event-bus subscription, returns an unsubscribe.
|
|
55
|
+
- `client.getConversationId()` / `setConversationId(id | null)` — persisted in storage so reloads continue the same conversation.
|
|
56
|
+
- `client.loadHistory()` — fetch the last 20 messages of the current conversation.
|
|
57
|
+
- `client.endConversation()` — close the conversation server-side and clear local state.
|
|
58
|
+
- `client.getBotConfig()` — public bot config (title, greeting, etc.), cached for the client's lifetime.
|
|
59
|
+
|
|
60
|
+
### React
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { useSalesBot } from '@sales-bot-llm/sdk/react'
|
|
64
|
+
|
|
65
|
+
function Chat() {
|
|
66
|
+
const { ask, messages, isStreaming, error } = useSalesBot({
|
|
67
|
+
embedKey: 'pk_live_...',
|
|
68
|
+
baseUrl: 'https://api.example.com',
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<>
|
|
73
|
+
{messages.map(m => (
|
|
74
|
+
<div key={m.id} data-role={m.role}>{m.content}</div>
|
|
75
|
+
))}
|
|
76
|
+
<button onClick={() => ask('Show me the pricing')} disabled={isStreaming}>
|
|
77
|
+
Ask
|
|
78
|
+
</button>
|
|
79
|
+
{error && <p role="alert">{error.message}</p>}
|
|
80
|
+
</>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Vue
|
|
86
|
+
|
|
87
|
+
```vue
|
|
88
|
+
<script setup lang="ts">
|
|
89
|
+
import { useSalesBot } from '@sales-bot-llm/sdk/vue'
|
|
90
|
+
|
|
91
|
+
const { ask, messages, isStreaming, error } = useSalesBot({
|
|
92
|
+
embedKey: 'pk_live_...',
|
|
93
|
+
baseUrl: 'https://api.example.com',
|
|
94
|
+
})
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<template>
|
|
98
|
+
<div v-for="m in messages" :key="m.id" :data-role="m.role">{{ m.content }}</div>
|
|
99
|
+
<button @click="ask('Show me the pricing')" :disabled="isStreaming">Ask</button>
|
|
100
|
+
<p v-if="error" role="alert">{{ error.message }}</p>
|
|
101
|
+
</template>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Floating widget
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { createWidget } from '@sales-bot-llm/sdk/widget'
|
|
108
|
+
|
|
109
|
+
createWidget({
|
|
110
|
+
embedKey: 'pk_live_...',
|
|
111
|
+
baseUrl: 'https://api.example.com',
|
|
112
|
+
title: 'Ask us anything',
|
|
113
|
+
position: 'bottom-right',
|
|
114
|
+
theme: {
|
|
115
|
+
primary: '#3b82f6',
|
|
116
|
+
radius: '16px',
|
|
117
|
+
fontFamily: 'Inter, system-ui, sans-serif',
|
|
118
|
+
},
|
|
119
|
+
}).mount()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The widget renders into a shadow root, so it can't be styled (or broken) by host-page CSS. Use the typed `theme` for common knobs and `customCss` for anything else. Every theme key maps to a `--sb-<kebab-case>` CSS variable on the shadow host — see [src/core/types.ts](src/core/types.ts) for the full list.
|
|
123
|
+
|
|
124
|
+
### Vanilla `<script>` tag
|
|
125
|
+
|
|
126
|
+
```html
|
|
127
|
+
<script src="https://your-cdn/sales-bot/vanilla.global.js"></script>
|
|
128
|
+
<script>
|
|
129
|
+
window.SalesBot.default.widget({
|
|
130
|
+
embedKey: 'pk_live_...',
|
|
131
|
+
baseUrl: 'https://api.example.com',
|
|
132
|
+
})
|
|
133
|
+
</script>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Note the `.default.widget` indirection — the IIFE bundle exposes the module's exports wrapper, not the module itself. `window.SalesBot.default.widget(...)` is the recommended path; `window.SalesBot.SalesBot.widget(...)` also works.
|
|
137
|
+
|
|
138
|
+
## Configuration
|
|
139
|
+
|
|
140
|
+
All entry points accept the same `SalesBotClientOptions`:
|
|
141
|
+
|
|
142
|
+
| Option | Required | Default | Purpose |
|
|
143
|
+
| --- | --- | --- | --- |
|
|
144
|
+
| `embedKey` | yes | — | Public bot key, `pk_live_…`. Created in the admin UI. |
|
|
145
|
+
| `baseUrl` | no | `http://localhost:3000` | Sales Bot backend URL. |
|
|
146
|
+
| `storage` | no | `localStorage` with `MemoryStorage` fallback | Custom `StorageAdapter` for the visitor token + conversation id. |
|
|
147
|
+
| `customHeaders` | no | — | Extra headers merged into every request. Useful in Node where the browser-style `Origin` header must be set manually. |
|
|
148
|
+
|
|
149
|
+
Widget-only extras (see [src/core/types.ts](src/core/types.ts) for the full `WidgetOptions` shape):
|
|
150
|
+
|
|
151
|
+
- `container?: HTMLElement` — mount target (default: `document.body`)
|
|
152
|
+
- `position?: 'bottom-right' | 'bottom-left'`
|
|
153
|
+
- `title?`, `placeholder?`
|
|
154
|
+
- `theme?: WidgetTheme` — typed CSS-variable overrides (colors, sizing, fonts, effects)
|
|
155
|
+
- `customCss?: string` — raw CSS appended inside the shadow root
|
|
156
|
+
|
|
157
|
+
## Errors
|
|
158
|
+
|
|
159
|
+
Every error surfaces as a `SalesBotError` with a typed `code` you can switch on:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { SalesBotError } from '@sales-bot-llm/sdk/core'
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
for await (const ev of client.ask('hi')) { /* … */ }
|
|
166
|
+
} catch (e) {
|
|
167
|
+
if (e instanceof SalesBotError) {
|
|
168
|
+
switch (e.code) {
|
|
169
|
+
case 'origin_not_allowed': /* domain not verified for this Bot */
|
|
170
|
+
case 'rate_limited': /* back off */
|
|
171
|
+
case 'out_of_credits': /* show top-up CTA */
|
|
172
|
+
case 'network_error': /* retry if e.retryable */
|
|
173
|
+
// …
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Backend codes mirror the platform error catalog; SDK-only codes are `network_error` and `parse_error`.
|
|
180
|
+
|
|
181
|
+
## Origin header — the most common gotcha
|
|
182
|
+
|
|
183
|
+
The backend validates `Origin` on every chat request. Browsers send it automatically, but the Bot's [Resource](../sales_bot/README.md) must list your origin as a verified domain (e.g. `localhost`, or your production host). A `403 origin_not_allowed` almost always means the Resource isn't configured for the host that's calling the SDK.
|
|
184
|
+
|
|
185
|
+
In Node (no browser, no automatic `Origin`), pass `customHeaders: { Origin: 'https://yourapp.example' }`.
|
|
186
|
+
|
|
187
|
+
## Repo layout
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
sales_bot_sdk/
|
|
191
|
+
├── src/
|
|
192
|
+
│ ├── core/ framework-agnostic client, types, transport, SSE parser, storage
|
|
193
|
+
│ ├── react/ useSalesBot hook
|
|
194
|
+
│ ├── vue/ useSalesBot composable
|
|
195
|
+
│ ├── widget/ createWidget — shadow-DOM floating panel
|
|
196
|
+
│ └── vanilla/ IIFE entry, exposes window.SalesBot
|
|
197
|
+
├── tests/ vitest specs incl. SSE contract, framework adapters, widget
|
|
198
|
+
├── example/ Vite + React playground (see example/README.md)
|
|
199
|
+
├── tsup.config.ts bundling: ESM + CJS for libs, IIFE for vanilla
|
|
200
|
+
├── biome.json lint + format
|
|
201
|
+
└── package.json exports map driving the public surface
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Development
|
|
205
|
+
|
|
206
|
+
Node 20+ and pnpm 10+.
|
|
207
|
+
|
|
208
|
+
```sh
|
|
209
|
+
pnpm install
|
|
210
|
+
pnpm build # tsup → dist/*.{js,cjs,d.ts} + vanilla.global.js (IIFE)
|
|
211
|
+
pnpm dev # tsup --watch
|
|
212
|
+
pnpm test # vitest run (unit + SSE-contract + adapter tests)
|
|
213
|
+
pnpm test:watch
|
|
214
|
+
pnpm test:coverage
|
|
215
|
+
pnpm typecheck # tsc --noEmit
|
|
216
|
+
pnpm lint # biome check .
|
|
217
|
+
pnpm lint:fix
|
|
218
|
+
pnpm size # size-limit budgets: core 8KB, react/vue 12KB, widget 25KB, vanilla 20KB
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The `tests/` tree mirrors the `src/` tree, plus a `contract/` folder that pins the SSE event taxonomy against the backend's `sse-events.contract.spec.ts`. **If the contract tests fail, do not loosen them — coordinate the change with the backend team.** The wire format is the integration boundary.
|
|
222
|
+
|
|
223
|
+
### Run against a local backend
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
# In ../sales_bot/:
|
|
227
|
+
pnpm start:dev # API on :3000
|
|
228
|
+
|
|
229
|
+
# In sales_bot_sdk/:
|
|
230
|
+
pnpm build
|
|
231
|
+
pnpm --filter @sales-bot/sdk-example dev # http://localhost:5173
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
See [example/README.md](example/README.md) for the full set-up, including how to verify a `localhost` Resource and Bot in the admin UI.
|
|
235
|
+
|
|
236
|
+
## SSE wire contract
|
|
237
|
+
|
|
238
|
+
The SDK is locked to the backend's SSE event taxonomy. The full list and payload shapes live in [src/core/types.ts](src/core/types.ts) — search for `SalesBotEvent`. Any change here is a breaking change for both sides and must be coordinated.
|
|
239
|
+
|
|
240
|
+
Event names: `turn_started`, `delta`, `tool_call_started`, `tool_call_finished`, `message_complete`, `usage`, `done`, `error`.
|
|
241
|
+
|
|
242
|
+
## Bundle outputs
|
|
243
|
+
|
|
244
|
+
`pnpm build` produces (in `dist/`):
|
|
245
|
+
|
|
246
|
+
| File | Format | Purpose |
|
|
247
|
+
| --- | --- | --- |
|
|
248
|
+
| `core.{js,cjs,d.ts}` | ESM / CJS | Framework-agnostic client |
|
|
249
|
+
| `react.{js,cjs,d.ts}` | ESM / CJS | React adapter |
|
|
250
|
+
| `vue.{js,cjs,d.ts}` | ESM / CJS | Vue adapter |
|
|
251
|
+
| `widget.{js,cjs,d.ts}` | ESM / CJS | Floating widget |
|
|
252
|
+
| `vanilla.global.js` | IIFE, minified | `<script>`-loadable, exposes `window.SalesBot` |
|
|
253
|
+
|
|
254
|
+
All ESM/CJS bundles are side-effect-free and tree-shakable.
|
|
255
|
+
|
|
256
|
+
## Publishing
|
|
257
|
+
|
|
258
|
+
The package is published as `@sales-bot-llm/sdk` on the public npm registry under `access=public`.
|
|
259
|
+
|
|
260
|
+
```sh
|
|
261
|
+
pnpm build
|
|
262
|
+
pnpm test
|
|
263
|
+
pnpm size
|
|
264
|
+
npm publish # NPM_TOKEN must be set in the environment
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
In CI, set `NPM_TOKEN` as a repo secret; npm picks it up automatically from the `.npmrc` directive.
|
|
268
|
+
|
|
269
|
+
## License
|
|
270
|
+
|
|
271
|
+
MIT — see [package.json](package.json).
|
package/package.json
CHANGED