@nabeh/chat-widget 0.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 ADDED
@@ -0,0 +1,452 @@
1
+ # Chat Widget Library
2
+
3
+ Production-oriented TypeScript library for embedding an AI chat assistant into an Angular website or any browser-based application.
4
+
5
+ ## Publish to npm
6
+
7
+ This package is configured for the npm organization scope `@nabeh` and for public publishing.
8
+
9
+ ### 1. Sign in with the CTO npm account
10
+
11
+ If the CTO does not already have an npm account, create one first at `https://www.npmjs.com/signup`, then sign in.
12
+
13
+ ### 2. Create the npm organization
14
+
15
+ While signed in to npm with the CTO account:
16
+
17
+ - open `https://www.npmjs.com/`
18
+ - click the profile menu in the top-right
19
+ - click `Add an Organization`
20
+ - enter the organization name `nabeh`
21
+
22
+ The npm scope will be `@nabeh`.
23
+
24
+ ### 3. Log in on the publish machine
25
+
26
+ Use the CTO account in the terminal:
27
+
28
+ ```bash
29
+ npm logout
30
+ npm login
31
+ npm whoami
32
+ ```
33
+
34
+ ### 4. Build and verify the package
35
+
36
+ ```bash
37
+ npm run clean
38
+ npm run check
39
+ npm run build
40
+ npm pack --dry-run
41
+ ```
42
+
43
+ ### 5. Publish the package publicly
44
+
45
+ ```bash
46
+ npm publish
47
+ ```
48
+
49
+ For the next release:
50
+
51
+ ```bash
52
+ npm version patch
53
+ npm publish
54
+ ```
55
+
56
+ Use `patch` for fixes, `minor` for new backward-compatible features, and `major` for breaking changes.
57
+
58
+ Notes:
59
+
60
+ - this package uses `"publishConfig": { "access": "public" }`
61
+ - public org-scoped packages still use the scoped package name
62
+ - npm docs for this flow:
63
+ - https://docs.npmjs.com/creating-an-organization
64
+ - https://docs.npmjs.com/creating-and-publishing-an-organization-scoped-package/
65
+
66
+ ## Project layout
67
+
68
+ - `src/` - production TypeScript source
69
+ - `docs/implementation-plan.md` - architecture and rollout notes
70
+ - `local-test/mock-ai-server.js` - local mock backend
71
+ - `local-test/chat-widget.js` - original quick prototype
72
+ - `local-test/angular-usage-example.ts` - Angular init example
73
+
74
+ ## Library API
75
+
76
+ Package usage:
77
+
78
+ ```ts
79
+ import { createChatWidget } from "@nabeh/chat-widget";
80
+
81
+ const widget = createChatWidget({
82
+ apiBaseUrl: "https://api.example.com",
83
+ endpoints: {
84
+ ask: "/knowledge_rag/ask",
85
+ history: "/knowledge_rag/get_chat_history",
86
+ deleteChat: "/knowledge_rag/delete_chat",
87
+ deleteLastQa: "/knowledge_rag/delete_last_qa_pair"
88
+ },
89
+ rag: {
90
+ chatId: "customer-portal-session-123",
91
+ knowledgeNames: ["employee-handbook", "policies"],
92
+ enableReferences: true
93
+ },
94
+ getAccessToken: async () => authService.getAccessToken(),
95
+ getUserContext: async () => ({
96
+ userId: currentUser.id,
97
+ displayName: currentUser.name
98
+ })
99
+ });
100
+ ```
101
+
102
+ Browser-global usage after loading the browser bundle:
103
+
104
+ ```html
105
+ <script src="https://your-cdn.example.com/browser.iife.js"></script>
106
+ <script>
107
+ window.ChatWidget.init({
108
+ apiBaseUrl: "https://api.example.com",
109
+ endpoints: {
110
+ ask: "/knowledge_rag/ask",
111
+ history: "/knowledge_rag/get_chat_history"
112
+ },
113
+ rag: {
114
+ chatId: "external-client-user-42",
115
+ knowledgeNames: ["client-kb-public", "client-kb-private"]
116
+ },
117
+ getAccessToken: async function () {
118
+ return window.appAccessToken;
119
+ },
120
+ getUserContext: async function () {
121
+ return {
122
+ userId: window.currentUser?.id,
123
+ displayName: window.currentUser?.name
124
+ };
125
+ }
126
+ });
127
+ </script>
128
+ ```
129
+
130
+ The browser bundle exposes:
131
+
132
+ - `window.ChatWidget.init(config)`
133
+ - `window.ChatWidget.createChatWidget(config)`
134
+
135
+ `init()` returns a widget instance with methods like `open()`, `close()`, `toggle()`, `destroy()`, `loadChats()`, and `loadHistory()`.
136
+
137
+ ## Angular integration
138
+
139
+ There are two supported ways to use this widget in Angular:
140
+
141
+ 1. load the browser bundle and call `window.ChatWidget.init(...)`
142
+ 2. install the npm package and import `createChatWidget(...)`
143
+
144
+ ### Option 1: Script-based Angular integration
145
+
146
+ This is the closest match to your current setup.
147
+
148
+ #### 1. Install or host the built bundle
149
+
150
+ After `npm run build`, use:
151
+
152
+ - `dist/browser.iife.js`
153
+
154
+ Copy it into your Angular app assets folder, for example:
155
+
156
+ - `src/assets/chat-widget/browser.iife.js`
157
+
158
+ #### 2. Add the script in Angular
159
+
160
+ In `angular.json`, add the built file under `architect > build > options > scripts`:
161
+
162
+ ```json
163
+ [
164
+ "src/assets/chat-widget/browser.iife.js"
165
+ ]
166
+ ```
167
+
168
+ Or add it directly in `src/index.html`:
169
+
170
+ ```html
171
+ <script src="assets/chat-widget/browser.iife.js"></script>
172
+ ```
173
+
174
+ #### 3. Initialize it after login
175
+
176
+ Call it from a component or auth-ready service after the user session is available:
177
+
178
+ ```ts
179
+ declare global {
180
+ interface Window {
181
+ ChatWidget?: {
182
+ init: (config: any) => any;
183
+ };
184
+ }
185
+ }
186
+
187
+ window.ChatWidget?.init({
188
+ apiBaseUrl: "http://192.168.0.126:8788",
189
+ endpoints: {
190
+ ask: "/my-chats/:chatId/messages",
191
+ history: "/my-chats/:chatId/messages",
192
+ listChats: "/my-chats",
193
+ createChat: "/my-chats",
194
+ updateChat: "/my-chats/:chatId",
195
+ deleteChat: "/my-chats/:chatId"
196
+ },
197
+ rag: {
198
+ knowledgeNames: ["sample-kb"],
199
+ loadHistoryOnOpen: true
200
+ },
201
+ getUserContext: async () => ({
202
+ userId: "demo-user-8",
203
+ email: "demo@example.com"
204
+ })
205
+ });
206
+ ```
207
+
208
+ Important:
209
+
210
+ - `apiBaseUrl` must be your backend base URL, not the Angular frontend URL
211
+ - if your backend runs on port `8788`, use `http://192.168.0.126:8788`
212
+ - if authentication is required, also pass `getAccessToken`
213
+
214
+ Example with token:
215
+
216
+ ```ts
217
+ window.ChatWidget?.init({
218
+ apiBaseUrl: "http://192.168.0.126:8788",
219
+ endpoints: {
220
+ ask: "/my-chats/:chatId/messages",
221
+ history: "/my-chats/:chatId/messages",
222
+ listChats: "/my-chats",
223
+ createChat: "/my-chats",
224
+ updateChat: "/my-chats/:chatId",
225
+ deleteChat: "/my-chats/:chatId"
226
+ },
227
+ rag: {
228
+ knowledgeNames: ["sample-kb"],
229
+ loadHistoryOnOpen: true
230
+ },
231
+ getAccessToken: async () => localStorage.getItem("access_token"),
232
+ getUserContext: async () => ({
233
+ userId: "demo-user-8",
234
+ email: "demo@example.com"
235
+ })
236
+ });
237
+ ```
238
+
239
+ ### Option 2: Install from npm in Angular
240
+
241
+ Install the package:
242
+
243
+ ```bash
244
+ npm install @nabeh/chat-widget
245
+ ```
246
+
247
+ Then use it in Angular:
248
+
249
+ ```ts
250
+ import { AfterViewInit, Component, OnDestroy } from "@angular/core";
251
+ import { createChatWidget } from "@nabeh/chat-widget";
252
+
253
+ @Component({
254
+ selector: "app-root",
255
+ template: ""
256
+ })
257
+ export class AppComponent implements AfterViewInit, OnDestroy {
258
+ private widget?: ReturnType<typeof createChatWidget>;
259
+
260
+ ngAfterViewInit(): void {
261
+ this.widget = createChatWidget({
262
+ apiBaseUrl: "http://192.168.0.126:8788",
263
+ endpoints: {
264
+ ask: "/my-chats/:chatId/messages",
265
+ history: "/my-chats/:chatId/messages",
266
+ listChats: "/my-chats",
267
+ createChat: "/my-chats",
268
+ updateChat: "/my-chats/:chatId",
269
+ deleteChat: "/my-chats/:chatId"
270
+ },
271
+ rag: {
272
+ knowledgeNames: ["sample-kb"],
273
+ loadHistoryOnOpen: true
274
+ },
275
+ getUserContext: async () => ({
276
+ userId: "demo-user-8",
277
+ email: "demo@example.com"
278
+ })
279
+ });
280
+ }
281
+
282
+ ngOnDestroy(): void {
283
+ this.widget?.destroy();
284
+ }
285
+ }
286
+ ```
287
+
288
+ ## Configuration reference
289
+
290
+ These are the main fields you should pass from Angular:
291
+
292
+ - `apiBaseUrl`: backend base URL, for example `http://192.168.0.126:8788`
293
+ - `endpoints.ask`: send-message endpoint
294
+ - `endpoints.history`: fetch-history endpoint
295
+ - `endpoints.listChats`: list sidebar chats
296
+ - `endpoints.createChat`: create a new chat
297
+ - `endpoints.updateChat`: update chat metadata like pin state
298
+ - `endpoints.deleteChat`: delete a chat
299
+ - `rag.knowledgeNames`: knowledge bases to query
300
+ - `rag.loadHistoryOnOpen`: whether to load chat history automatically when opened
301
+ - `getAccessToken`: function returning bearer token
302
+ - `getUserContext`: function returning user identity information
303
+
304
+ Example user context:
305
+
306
+ ```ts
307
+ getUserContext: async () => ({
308
+ userId: "demo-user-8",
309
+ displayName: "Demo User",
310
+ email: "demo@example.com",
311
+ roles: ["researcher"]
312
+ })
313
+ ```
314
+
315
+ ## Recommended Angular timing
316
+
317
+ Initialize the widget:
318
+
319
+ - after the user logs in
320
+ - after token/user information is available
321
+ - once per page/app shell, not on every route change
322
+
323
+ Destroy the widget when the hosting Angular component is destroyed if you are using the npm import approach.
324
+
325
+ ## Knowledge RAG integration
326
+
327
+ The widget is now aligned to the `knowledge_rag` endpoints:
328
+
329
+ - `POST /knowledge_rag/ask`
330
+ - `GET /knowledge_rag/get_chat_history`
331
+
332
+ Production guidance:
333
+
334
+ - pass the bearer token with `getAccessToken()`
335
+ - do not hardcode tokens into the shipped widget
336
+ - use a stable `rag.chatId` per user/session if you want conversation continuity
337
+ - provide `rag.knowledgeNames` from the host application, not from the widget bundle
338
+ - version your hosted script URL, for example:
339
+ - `/chat-widget/v1.0.0/browser.iife.js`
340
+ - `/chat-widget/latest/browser.iife.js`
341
+
342
+ ## Backend adapter integration
343
+
344
+ When using the Postgres-backed adapter added in `backend/`, point the widget to the adapter endpoints instead of the raw RAG endpoints:
345
+
346
+ ```ts
347
+ createChatWidget({
348
+ apiBaseUrl: "http://localhost:8788",
349
+ endpoints: {
350
+ ask: "/my-chats/:chatId/messages",
351
+ history: "/my-chats/:chatId/messages",
352
+ listChats: "/my-chats",
353
+ createChat: "/my-chats",
354
+ updateChat: "/my-chats/:chatId"
355
+ },
356
+ rag: {
357
+ knowledgeNames: ["sample-kb"],
358
+ loadHistoryOnOpen: false
359
+ },
360
+ getAccessToken: async () => authService.getAccessToken(),
361
+ getUserContext: async () => ({
362
+ userId: "demo-user-1",
363
+ displayName: "Demo User",
364
+ email: "demo@example.com",
365
+ roles: ["researcher"]
366
+ })
367
+ });
368
+ ```
369
+
370
+ The widget forwards `getUserContext()` to the adapter in the `X-Chat-User-Context` header. In your dummy site you can hardcode that object for testing; in the real client integration they should populate it from their logged-in user/session data.
371
+
372
+ In the hybrid adapter flow, Postgres stores chat sidebar metadata only. Full message history and chat deletion are proxied to the AI backend.
373
+
374
+ ## Build outputs
375
+
376
+ The library is configured to build:
377
+
378
+ - `esm` for app imports
379
+ - `cjs` for Node/CommonJS consumers
380
+ - `iife` for script injection into an already deployed website
381
+
382
+ Build tooling:
383
+
384
+ ```bash
385
+ npm install
386
+ npm run build
387
+ ```
388
+
389
+ Output will be generated in `dist/`.
390
+
391
+ ## Local test flow
392
+
393
+ ### 1. Start the mock backend
394
+
395
+ ```bash
396
+ node local-test/mock-ai-server.js
397
+ ```
398
+
399
+ ### 2. Build the browser bundle
400
+
401
+ ```bash
402
+ npm install
403
+ npm run build
404
+ ```
405
+
406
+ ### 3. Inject the built script into an Angular dummy app
407
+
408
+ Copy the built browser file from `dist/` into Angular `src/assets/`, then add:
409
+
410
+ ```html
411
+ <script src="/assets/browser.iife.js"></script>
412
+ ```
413
+
414
+ ### 4. Initialize after authentication
415
+
416
+ ```ts
417
+ window.ChatWidget?.init({
418
+ apiBaseUrl: "http://localhost:8787",
419
+ endpoints: {
420
+ ask: "/knowledge_rag/ask",
421
+ history: "/knowledge_rag/get_chat_history"
422
+ },
423
+ rag: {
424
+ chatId: "angular-local-test-user-123",
425
+ knowledgeNames: ["sample-kb"]
426
+ },
427
+ getAccessToken: async () => "dummy-sso-token",
428
+ getUserContext: async () => ({
429
+ userId: "u-1001",
430
+ displayName: "Angular Test User"
431
+ })
432
+ });
433
+ ```
434
+
435
+ ## Production integration guidance
436
+
437
+ For the live Angular site:
438
+
439
+ - host the built `iife` bundle on your CDN or app server
440
+ - load it only after the user is authenticated
441
+ - pass access token and user context from the host Angular app
442
+ - keep RBAC validation in the AI backend
443
+
444
+ Do not embed a separate login flow inside the widget.
445
+
446
+ ## Chat persistence adapter
447
+
448
+ This repo now also includes a starter backend adapter in `backend/` for storing chat metadata in Postgres and wrapping the existing RAG APIs.
449
+
450
+ Setup guide:
451
+
452
+ - [docs/chat-backend-setup.md](/home/tarun/learnings/knowledge-platform-cb/docs/chat-backend-setup.md)