@data-netmonk/mona-chat-widget 2.6.8 → 2.6.9

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,943 +1,943 @@
1
- # Mona Chat Widget
2
-
3
- Chat widget package developed by Netmonk data & solution team to be imported in Netmonk products
4
-
5
- ---
6
-
7
- ### Recent Updates & Breaking Changes
8
-
9
- ---
10
-
11
- **Latest Version Changes (`v2.6.6`):**
12
-
13
- ✅ **Patch Updates:**
14
- 1. **Voice mode now fully controls TTS playback** - Disabling voice mode clears queued audio and stops active playback cleanly
15
- 2. **Library build now resolves phoneme assets correctly** - Avatar viseme images are bundled through module URLs so published packages can load them reliably
16
- 3. **Message rendering is more stable** - Messages now receive deterministic IDs and React keys to avoid collisions during history load and streaming updates
17
- 4. **Streaming flow is more consistent** - Mock responses, webhook responses, and TTS playback now use the same streaming helper to keep text timing aligned
18
-
19
- ✅ **Previous Non-breaking Changes (`v2.6.1`):**
20
- 1. **Built-in voice mode button** - Chat widget now includes a microphone button to toggle voice mode directly from the input area
21
- 2. **Speech-to-text flow for voice mode** - Recorded user audio is sent to the configured STT endpoint, then the transcription is forwarded as a normal chat message
22
- 3. **Phoneme-driven avatar animation** - The widget can animate Mona's avatar during TTS playback based on phoneme/viseme IDs returned by the backend
23
- 4. **Supported phoneme IDs** - `A`, `BP`, `ChJ`, `E`, `FV`, `I`, `KG`, `L`, `M`, `O`, `SZ`, `U`
24
-
25
- ⚠️ **Previous Breaking Changes:**
26
- 1. **Removed `type` and `agentType` props** - These parameters are no longer used and have been removed from all components
27
- 2. **Renamed `botServerUrl` to `webhookUrl`** - For better clarity and consistency
28
- 3. **`webhookUrl` is now required** - Must be provided as a prop
29
- 4. **`authUrl` and `username` are now direct props** - No longer part of `data` prop for better clarity and type safety
30
- 5. **`userId` is now optional** - Widget automatically generates visitor ID for guest users via browser fingerprinting when `userId` is not provided
31
-
32
- ✨ **New Features:**
33
- 1. **Guest user support** - Users can chat without logging in
34
- - Automatic visitor ID generation using browser fingerprinting (FingerprintJS + SHA256)
35
- - `auth: false` flag automatically added to API requests for guest users
36
- - Persistent sessions across page reloads for anonymous visitors
37
- 2. **Authentication support** - Automatic token management with refresh on 401 errors
38
- - Pass `authUrl` as a direct prop
39
- - Widget handles token lifecycle automatically
40
- 3. **Enhanced user authentication handling** - Smart detection of authenticated vs guest users
41
- - Compares `userId` with visitor ID to determine authentication status
42
- - Automatic `auth` flag management in all API requests
43
- 4. **Enhanced data prop** - Support for custom variables passed to backend via `data` prop
44
- 5. **Improved error handling** - Better fallback mechanisms and error messages
45
- 6. **Direct `username` prop** - Pass username as a top-level prop instead of in data string
46
-
47
- 📝 **Migration Guide:**
48
- ```jsx
49
- // Old usage (deprecated)
50
- <ChatWidget
51
- userId="user123"
52
- sourceId="source456"
53
- type="prime"
54
- botServerUrl="https://api.example.com"
55
- />
56
-
57
- // Previous version (with data prop)
58
- <ChatWidget
59
- userId="user123"
60
- sourceId="source456"
61
- webhookUrl="https://api.example.com/webhook"
62
- data="authUrl=https://api.example.com/login/chatwidget~username=John"
63
- />
64
-
65
- // New usage - Authenticated user (current - recommended)
66
- <ChatWidget
67
- userId="user123"
68
- sourceId="source456"
69
- webhookUrl="https://api.example.com/webhook"
70
- authUrl="https://api.example.com/login/chatwidget"
71
- username="John"
72
- data="email=john@example.com~phone=+1234567890"
73
- />
74
-
75
- // New usage - Guest user (without login)
76
- <ChatWidget
77
- sourceId="source456"
78
- webhookUrl="https://api.example.com/webhook"
79
- username="Guest"
80
- />
81
- // Widget automatically generates visitor ID and adds auth: false flag
82
-
83
- // New usage - Conditional (handles both logged-in and guest users)
84
- <ChatWidget
85
- userId={currentUser?.id} // undefined for guests
86
- sourceId="source456"
87
- webhookUrl="https://api.example.com/webhook"
88
- username={currentUser?.name || "Guest"}
89
- />
90
- ```
91
-
92
- ---
93
-
94
- ## 🚅 Quick start
95
-
96
- ---
97
-
98
- 1. Install the package
99
- ```bash
100
- npm install @data-netmonk/mona-chat-widget
101
- ```
102
- 2. Render the widget with the minimum required props
103
- ```jsx
104
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
105
-
106
- export default function App() {
107
- return (
108
- <ChatWidget
109
- sourceId="source-123"
110
- webhookUrl="https://api.example.com/webhook"
111
- />
112
- );
113
- }
114
- ```
115
- 3. Choose the usage mode you need
116
-
117
- **1. Basic mode**
118
-
119
- ```jsx
120
- <ChatWidget
121
- sourceId="source-123"
122
- webhookUrl="https://api.example.com/webhook"
123
- />
124
- ```
125
-
126
- **2. With auth mode**
127
-
128
- ```jsx
129
- <ChatWidget
130
- sourceId="source-123"
131
- webhookUrl="https://api.example.com/webhook"
132
- userId="user-123"
133
- username="John"
134
- authUrl="https://api.example.com/login/chatwidget"
135
- />
136
- ```
137
-
138
- **3. With voice mode**
139
-
140
- ```jsx
141
- <ChatWidget
142
- sourceId="source-123"
143
- webhookUrl="https://api.example.com/webhook"
144
- voiceConfig={{
145
- sttEndpoint: "https://voice.example.com/stt",
146
- sttApiKey: "your-stt-key",
147
- ttsEndpoint: "https://voice.example.com/tts",
148
- ttsApiKey: "your-tts-key",
149
- ttsMinioObjectEndpoint: "https://voice.example.com/minio/object",
150
- ttsMinioApiKey: "your-minio-key",
151
- ttsMinioBucket: "chatbot-tts",
152
- }}
153
- />
154
- ```
155
-
156
- **4. Full props example**
157
-
158
- ```jsx
159
- <ChatWidget
160
- sourceId="source-123"
161
- webhookUrl="https://api.example.com/webhook"
162
- userId="user-123"
163
- authUrl="https://api.example.com/login/chatwidget"
164
- username="John Doe"
165
- voiceConfig={{
166
- sttEndpoint: "https://voice.example.com/stt",
167
- sttApiKey: "your-stt-key",
168
- ttsEndpoint: "https://voice.example.com/tts",
169
- ttsApiKey: "your-tts-key",
170
- ttsMinioObjectEndpoint: "https://voice.example.com/minio/object",
171
- ttsMinioApiKey: "your-minio-key",
172
- ttsMinioBucket: "chatbot-tts",
173
- }}
174
- />
175
- ```
176
-
177
- **Common optional props**
178
-
179
- - `userId`: ID user login. Jika tidak diisi, widget akan memakai guest visitor ID.
180
- - `username`: nama yang dikirim bersama session chat.
181
- - `authUrl`: endpoint auth/refresh khusus chat widget.
182
- - `data`: custom payload tambahan untuk backend.
183
- - `voiceConfig`: aktifkan voice mode, STT, TTS, dan phoneme avatar bila dibutuhkan.
184
- - `showLauncher`, `autoOpen`, `enabled`, `position`, `width`, `height`: untuk perilaku dan layout widget.
185
-
186
- By default, STT and TTS requests send the API key using `x-api-key` without any prefix. MinIO object fetch uses `X-API-Key`. If your provider needs a different auth format, override it through `voiceConfig`.
187
-
188
- ---
189
-
190
- ### Mock Mode (Demo without Backend)
191
-
192
- ---
193
-
194
- The chat widget includes a built-in mock mode for testing and demonstrations without requiring a backend server.
195
-
196
- **To enable mock mode:**
197
- 1. Pass `widgetConfig={{ useMockResponses: true }}`
198
- 2. Render the widget normally
199
-
200
- **Mock responses include:**
201
- - Greetings (hello, hi, halo, start) - with interactive buttons
202
- - Help commands
203
- - Device list with clickable buttons (type "devices" or "show devices")
204
- - Thank you acknowledgments
205
- - Farewells
206
- - Time-based greetings (good morning, etc.)
207
- - And automatically falls back to mock mode if backend fails
208
-
209
- **Interactive Buttons Demo:**
210
- Type these messages to see button responses:
211
- - "start" - Welcome message with action buttons
212
- - "show devices" or "devices" - List of devices as clickable buttons
213
- - Click any button to trigger the next action
214
-
215
- **To add custom mock responses:**
216
- Edit the `mockBotResponses` object in `src/components/ChatWidget/utils/mockBotResponses.js`
217
-
218
- For text-only responses:
219
- ```javascript
220
- "trigger": "Response text"
221
- ```
222
-
223
- For responses with buttons:
224
- ```javascript
225
- "trigger": {
226
- text: "Your message here",
227
- buttons: [
228
- { title: "Button 1", payload: "action_1" },
229
- { title: "Button 2", payload: "action_2" },
230
- { title: "Link Button", url: "https://example.com" }
231
- ]
232
- }
233
- ```
234
-
235
- ---
236
-
237
- ### Storybook
238
-
239
- ---
240
-
241
- 1. **How to run Storybook locally** (access at http://localhost:5177)
242
-
243
- ```
244
- npm run storybook
245
- ```
246
-
247
- 2. **How to build Storybook**
248
-
249
- ```
250
- npm run build-storybook
251
- ```
252
-
253
- 3. **How to serve Storybook**
254
-
255
- ```
256
- npm run serve-storybook
257
- ```
258
-
259
- ---
260
-
261
- ### Library (how to update and publish)
262
-
263
- ---
264
-
265
- 1. **Commit changes**
266
- ```
267
- git add .
268
- git commit -m "Your commit message"
269
- ```
270
- 2. **Update version**
271
-
272
- ```bash
273
- npm version patch # for bug fixes (1.0.0 -> 1.0.1)
274
- ```
275
-
276
- ```bash
277
- npm version minor # for new features (1.0.0 -> 1.1.0)
278
- ```
279
-
280
- ```bash
281
- npm version major # for breaking changes (1.0.0 -> 2.0.0)
282
- ```
283
-
284
- 3. **Build as a library** (build file at `/dist` directory)
285
-
286
- ```
287
- npm run build
288
- ```
289
-
290
- 4. **Copy declaration file to `/dist`**
291
-
292
- ```
293
- cp ./src/declarations/index.d.ts ./dist/index.d.ts
294
- ```
295
-
296
- 5. **Publish**
297
-
298
- ```
299
- npm publish
300
- ```
301
-
302
- ---
303
-
304
- ### Library (how to import on your project)
305
-
306
- ---
307
-
308
- 1. **Install package**
309
- ```bash
310
- npm install @data-netmonk/mona-chat-widget
311
- ```
312
-
313
- 2. **Import styles on your `App.jsx` or `index.jsx`**
314
-
315
- ```jsx
316
- import "@data-netmonk/mona-chat-widget/dist/style.css";
317
- ```
318
-
319
- 3. **Import & use component**
320
-
321
- ```jsx
322
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
323
-
324
- function App() {
325
- return (
326
- <ChatWidget
327
- userId="user123"
328
- sourceId="691e1b5952068ff7aaeccffc9"
329
- webhookUrl="https://your-backend-url.com"
330
- />
331
- );
332
- }
333
- ```
334
-
335
- ---
336
-
337
- ### Component Props
338
-
339
- ---
340
-
341
- #### Required Props
342
-
343
- | Prop | Type | Description |
344
- |------|------|-------------|
345
- | `sourceId` | `string` | **Required.** Source/channel identifier for the chat |
346
- | `webhookUrl` | `string` | **Required.** Backend webhook URL |
347
-
348
- #### Optional Props
349
-
350
- | Prop | Type | Default | Description |
351
- |------|------|---------|-------------|
352
- | `userId` | `string` | Generated visitor ID | Unique identifier for the user. **Optional** - if not provided, a unique visitor ID is automatically generated using browser fingerprinting for guest users |
353
- | `authUrl` | `string` | - | Authentication endpoint URL for token-based authentication |
354
- | `username` | `string` | - | Username for the session (sent to backend in variables) |
355
- | `data` | `string` | - | Additional custom variables in format `key1=value1~key2=value2` |
356
- | `width` | `string` | `"25vw"` | Widget width (CSS value) |
357
- | `height` | `string` | `"90vh"` | Widget height (CSS value) |
358
- | `right` | `string` | `"1.25rem"` | Distance from right edge |
359
- | `bottom` | `string` | `"1.25rem"` | Distance from bottom edge |
360
- | `zIndex` | `number` | `2000` | CSS z-index for widget positioning |
361
- | `position` | `string` | `"fixed"` | CSS position (`"fixed"` or `"relative"`) |
362
- | `onToggle` | `function` | - | Callback when widget opens/closes: `(isOpen: boolean) => void` |
363
-
364
- ---
365
-
366
- ### Voice Mode & Phoneme Support
367
-
368
- ---
369
-
370
- The widget now includes a built-in microphone button in the input area. Clicking the button toggles voice mode, requests microphone access, records speech, sends the recorded audio to `voiceConfig.sttEndpoint`, and forwards the returned transcription as a regular user message.
371
-
372
- If `voiceConfig.sttApiKey` is set, the STT request includes `x-api-key: <key>` by default. If another provider needs a different auth format, override it through `voiceConfig`.
373
-
374
- **Expected STT request/response shape:**
375
-
376
- - Request body: `multipart/form-data`
377
- - Audio field name: `audio`
378
- - Request headers: `Accept: application/json`, plus `x-api-key: <key>` when configured for the Netmonk STT service
379
- - Expected JSON response:
380
-
381
- ```json
382
- {
383
- "text": "Halo, saya mau tanya status tiket saya"
384
- }
385
- ```
386
-
387
- During TTS playback, the header avatar can switch between phoneme images using the metadata returned by the TTS service. The widget currently supports these phoneme IDs:
388
-
389
- - `A`
390
- - `BP`
391
- - `ChJ`
392
- - `E`
393
- - `FV`
394
- - `I`
395
- - `KG`
396
- - `L`
397
- - `M`
398
- - `O`
399
- - `SZ`
400
- - `U`
401
-
402
- Phoneme IDs are normalized case-insensitively in the widget, so values such as `ChJ` and `CHJ` resolve to the same avatar image.
403
-
404
- **Preferred webhook TTS asset shape (MinIO-first):**
405
-
406
- ```json
407
- {
408
- "messages": [{ "type": "text", "text": "Halo, ada yang bisa saya bantu?" }],
409
- "tts_assets": {
410
- "items": [
411
- {
412
- "bucket": "chatbot-tts",
413
- "response_index": 1,
414
- "audio_object_key": "tts/...wav",
415
- "phoneme_object_key": "tts/...phonemes.txt",
416
- "phoneme_timeline_object_key": "tts/...phoneme-timeline.json",
417
- "viseme_timeline_object_key": "tts/...viseme-timeline.json"
418
- }
419
- ]
420
- }
421
- }
422
- ```
423
-
424
- When this payload is available, the widget resolves MinIO object keys first for audio and timeline metadata. If no usable asset is found, it falls back to `voiceConfig.ttsEndpoint`.
425
-
426
- **Expected TTS response shape:**
427
-
428
- ```text
429
- body: <raw WAV or other audio binary>
430
- content-type: audio/wav
431
- x-tts-metadata-id: 7d8f6a0f0f684e97b7b8d8db67f4f3a1
432
- x-tts-metadata-path: /tts/metadata/7d8f6a0f0f684e97b7b8d8db67f4f3a1
433
- x-tts-duration-ms: 120
434
- ```
435
-
436
- Metadata lengkap diambil dari endpoint metadata yang ditunjuk header tadi:
437
-
438
- ```json
439
- GET /tts/metadata/7d8f6a0f0f684e97b7b8d8db67f4f3a1
440
- {
441
- "id": "7d8f6a0f0f684e97b7b8d8db67f4f3a1",
442
- "phonemes": "M A",
443
- "phoneme_timeline": [
444
- { "phoneme": "M", "startMs": 0, "endMs": 120 }
445
- ],
446
- "visemes": [
447
- { "id": "M", "startMs": 0, "endMs": 120 },
448
- { "id": "A", "startMs": 121, "endMs": 260 },
449
- { "id": "SZ", "startMs": 261, "endMs": 420 }
450
- ],
451
- "duration_ms": 420
452
- }
453
- ```
454
-
455
- Widget ini masih mendukung header base64 lama sebagai fallback:
456
-
457
- ```text
458
- x-tts-visemes-b64
459
- x-tts-phonemes-b64
460
- x-tts-phoneme-timeline-b64
461
- ```
462
-
463
- Widget juga masih mendukung format JSON body lama sebagai fallback, tetapi untuk audio binary format metadata endpoint di atas sekarang jadi jalur utama yang direkomendasikan.
464
-
465
- ---
466
-
467
- ### Guest User Support
468
-
469
- ---
470
-
471
- The widget now supports **guest users** (users who haven't logged in or before logging in). When `userId` is not provided, the widget automatically generates a unique visitor ID using browser fingerprinting.
472
-
473
- **How it works:**
474
-
475
- 1. **Browser Fingerprinting**: Uses FingerprintJS to generate a unique identifier based on:
476
- - Browser characteristics (user agent, screen resolution, timezone, etc.)
477
- - Incognito/private browsing detection
478
- - Combined into a SHA256 hash for consistency
479
-
480
- 2. **Automatic Guest Detection**: The widget automatically detects guest users and:
481
- - Generates a visitor ID if `userId` is not provided
482
- - Adds `auth: false` flag to all API requests for guest users
483
- - Uses the visitor ID as the effective user ID throughout the session
484
-
485
- 3. **Persistent Sessions**: The visitor ID remains consistent across page reloads (unless browser fingerprint changes or user clears data)
486
-
487
- **When to use guest mode:**
488
- - Public-facing websites where users can chat without logging in
489
- - Support widgets for anonymous visitors
490
- - Pre-login customer service interactions
491
- - Any scenario where user authentication is optional
492
-
493
- **When to provide userId:**
494
- - Users who are logged into your application
495
- - When you need to track chat history across devices
496
- - When authentication is required for personalized responses
497
- - Enterprise or internal applications
498
-
499
- ---
500
-
501
- ### Usage Examples
502
-
503
- ---
504
-
505
- #### Basic Usage (Authenticated User)
506
-
507
- ```jsx
508
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
509
- import "@data-netmonk/mona-chat-widget/dist/style.css";
510
-
511
- function App() {
512
- return (
513
- <ChatWidget
514
- userId="user123"
515
- sourceId="691e1b5952068ff7aaeccffc9"
516
- webhookUrl="https://api.example.com/webhook"
517
- />
518
- );
519
- }
520
- ```
521
-
522
- #### Guest User (Without Login)
523
-
524
- For anonymous visitors or users who haven't logged in:
525
-
526
- ```jsx
527
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
528
- import "@data-netmonk/mona-chat-widget/dist/style.css";
529
-
530
- function App() {
531
- return (
532
- <ChatWidget
533
- sourceId="691e1b5952068ff7aaeccffc9"
534
- webhookUrl="https://api.example.com/webhook"
535
- />
536
- );
537
- }
538
- ```
539
-
540
- **What happens:**
541
- - Widget automatically generates a visitor ID using browser fingerprinting
542
- - All API requests include `auth: false` in variables to indicate guest user
543
- - No login required - users can start chatting immediately
544
-
545
- #### Conditional userId (Logged in or Guest)
546
-
547
- Handle both logged-in users and guests dynamically:
548
-
549
- ```jsx
550
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
551
- import "@data-netmonk/mona-chat-widget/dist/style.css";
552
-
553
- function App() {
554
- const currentUser = getCurrentUser(); // Your auth function
555
-
556
- return (
557
- <ChatWidget
558
- userId={currentUser?.id} // Provide userId if logged in, undefined if not
559
- sourceId="691e1b5952068ff7aaeccffc9"
560
- webhookUrl="https://api.example.com/webhook"
561
- username={currentUser?.name}
562
- />
563
- );
564
- }
565
- ```
566
-
567
- **Behavior:**
568
- - If `currentUser.id` exists → Uses provided userId (authenticated user)
569
- - If `currentUser.id` is `null`/`undefined` → Generates visitor ID (guest user)
570
- - Backend receives `auth: false` flag only for guest users
571
-
572
- #### With Custom Variables
573
-
574
- Pass custom user data like email, phone number, etc. to the backend:
575
-
576
- ```jsx
577
- <ChatWidget
578
- userId="user123"
579
- sourceId="691e1b5952068ff7aaeccffc9"
580
- webhookUrl="https://api.example.com/webhook"
581
- username="John Doe"
582
- data="telephone_number=+628123456789~email=john@example.com"
583
- />
584
- ```
585
-
586
- **Variables sent to backend:**
587
- ```json
588
- {
589
- "chat_id": "...",
590
- "session_id": "...",
591
- "user_id": "user123",
592
- "message": "Hello",
593
- "type": "text",
594
- "variables": {
595
- "username": "John Doe",
596
- "telephone_number": "+628123456789",
597
- "email": "john@example.com"
598
- }
599
- }
600
- ```
601
- }
602
- ```
603
- #### With Authentication (NEW!)
604
-
605
- The widget supports automatic authentication with token refresh on expiry:
606
-
607
- ```jsx
608
- <ChatWidget
609
- userId="user123"
610
- sourceId="691e1b5952068ff7aaeccffc9"
611
- webhookUrl="https://api.example.com/webhook"
612
- authUrl="https://api.example.com/login/chatwidget"
613
- username="John Doe"
614
- />
615
- ```
616
-
617
- **How authentication works:**
618
-
619
- 1. **Initial Authentication**: When the widget loads (on Launcher mount), if `authUrl` is provided as a prop, it automatically calls the auth API:
620
- ```
621
- POST {authUrl}
622
- Body: { "user_id": "user123" }
623
- Response: { "token": "eyJ..." }
624
- ```
625
-
626
- 2. **Session Initialization**: After getting the token, the widget calls the init endpoint to establish the session:
627
- ```
628
- POST {webhookUrl}/{sourceId}/init
629
- Body: {
630
- "session_id": "...",
631
- "user_id": "user123",
632
- "token": "eyJ...",
633
- "username": "John Doe"
634
- }
635
- ```
636
-
637
- 3. **Automatic Token Refresh**: If the webhook returns **401 Unauthorized** (token expired/revoked), the widget automatically:
638
- - Calls the `authUrl` again to get a fresh token
639
- - Re-initializes the session with the new token
640
- - Updates the `authToken` in variables
641
- - Retries the failed request with the new token
642
- - This happens transparently without user interaction
643
-
644
- **Combined example with auth and custom variables:**
645
- ```jsx
646
- <ChatWidget
647
- userId="user123"
648
- sourceId="691e1b5952068ff7aaeccffc9"
649
- webhookUrl="https://api.example.com/webhook"
650
- authUrl="https://api.example.com/login/chatwidget"
651
- username="John"
652
- data="email=john@example.com~phone=+628123456789"
653
- />
654
- ```
655
-
656
- **Auth Flag (`auth` variable):**
657
- The widget automatically adds an `auth: false` flag to the `variables` object when the user is a guest (not authenticated):
658
- - When `userId === visitorId` (browser fingerprint), the widget adds `auth: false` to variables
659
- - When `userId !== visitorId` (authenticated user), no `auth` flag is added to variables
660
-
661
- **Guest user example** (userId equals browser fingerprint):
662
- ```json
663
- {
664
- "chat_id": "...",
665
- "session_id": "...",
666
- "user_id": "visitor_abc123",
667
- "message": "Hello",
668
- "type": "text",
669
- "variables": {
670
- "username": "Guest",
671
- "auth": false
672
- }
673
- }
674
- ```
675
-
676
- **Authenticated user example** (userId is different from browser fingerprint):
677
- ```json
678
- {
679
- "chat_id": "...",
680
- "session_id": "...",
681
- "user_id": "user123",
682
- "message": "Hello",
683
- "type": "text",
684
- "variables": {
685
- "username": "John Doe",
686
- "email": "john@example.com"
687
- }
688
- }
689
- ```
690
-
691
- This `auth: false` flag is automatically added in:
692
- - Session initialization payload (when guest)
693
- - All message requests (when guest)
694
- - Button postback requests (when guest)
695
-
696
- **Note:** The `data` prop is for additional custom variables only. Required parameters (`userId`, `sourceId`, `webhookUrl`) and common optional parameters (`authUrl`, `username`) should be passed as direct props for better type safety and clarity.
697
-
698
- #### Custom Styling
699
-
700
- ```jsx
701
- <ChatWidget
702
- userId="user123" // Optional
703
- sourceId="691e1b5952068ff7aaeccffc9"
704
- webhookUrl="https://api.example.com/webhook"
705
- width="400px"
706
- height="600px"
707
- right="20px"
708
- bottom="20px"
709
- zIndex={9999}
710
- />
711
- ```
712
-
713
- #### Embedded in Layout (Relative Positioning)
714
-
715
- ```jsx
716
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 400px' }}>
717
- <div>Main content</div>
718
- <ChatWidget
719
- userId="user123" // Optional
720
- sourceId="691e1b5952068ff7aaeccffc9"
721
- webhookUrl="https://api.example.com/webhook"
722
- position="relative"
723
- width="100%"
724
- height="100vh"
725
- />
726
- </div>
727
- ```
728
-
729
- #### With Toggle Callback
730
-
731
- ```jsx
732
- function App() {
733
- const handleToggle = (isOpen) => {
734
- console.log('Chat widget is', isOpen ? 'open' : 'closed');
735
- // Track analytics, update UI, etc.
736
- };
737
-
738
- return (
739
- <ChatWidget
740
- userId="user123" // Optional
741
- sourceId="691e1b5952068ff7aaeccffc9"
742
- webhookUrl="https://api.example.com/webhook"
743
- onToggle={handleToggle}
744
- />
745
- );
746
- }
747
- ```
748
-
749
- #### Full-Screen Widget
750
-
751
- ```jsx
752
- <ChatWidget
753
- userId="user123" // Optional - supports guest users
754
- sourceId="691e1b5952068ff7aaeccffc9"
755
- webhookUrl="https://api.example.com/webhook"
756
- width="100vw"
757
- height="100vh"
758
- right="0"
759
- bottom="0"
760
- />
761
- ```
762
-
763
- ---
764
-
765
- ### Data Prop Format & Helper Function
766
-
767
- ---
768
-
769
- The `data` prop allows you to pass additional custom variables via a single string. This is especially useful when you need to dynamically pass user-specific data or pass it through URL parameters.
770
-
771
- **Format:** `key=value` pairs separated by `~`
772
-
773
- **Example data string:**
774
- ```
775
- email=john@example.com~phone=+1234567890~department=Engineering
776
- ```
777
-
778
- **Note:** `authUrl` and `username` are now **direct props** and should not be included in the `data` string.
779
-
780
- #### Building Data String Programmatically
781
-
782
- Instead of manually constructing the data string, you can create a helper function to build it dynamically. This approach is recommended for production applications as it:
783
- - Prevents syntax errors in the data string format
784
- - Makes the code more maintainable and readable
785
- - Allows for conditional inclusion of parameters
786
- - Handles URL encoding automatically if needed
787
-
788
- **Create a helper function** (`src/helpers/chatWidget.js` or similar):
789
-
790
- ```jsx
791
- /**
792
- * Builds the data string for ChatWidget component
793
- * @param {Object} params - Object containing all parameters
794
- * @param {string} params.email - Optional: User's email address
795
- * @param {string} params.phone - Optional: User's phone number
796
- * @param {Object} params.customFields - Optional: Any additional custom fields as key-value pairs
797
- * @returns {string} Formatted data string for ChatWidget (always returns a string)
798
- *
799
- * @example
800
- * // Returns: "email=john@example.com~phone=+1234567890~department=Engineering"
801
- * buildChatWidgetData({
802
- * email: "john@example.com",
803
- * phone: "+1234567890",
804
- * customFields: { department: "Engineering" }
805
- * });
806
- */
807
- export const buildChatWidgetData = ({
808
- email,
809
- phone,
810
- customFields = {}
811
- }) => {
812
- const parts = [];
813
-
814
- // Add standard fields
815
- if (email) {
816
- parts.push(`email=${encodeURIComponent(email)}`);
817
- }
818
-
819
- if (phone) {
820
- parts.push(`phone=${encodeURIComponent(phone)}`);
821
- }
822
-
823
- // Add any custom fields dynamically
824
- // Note: customFields is just a convenience parameter for the helper function
825
- // Each field will be flattened into the string format: key=value~key2=value2
826
- Object.entries(customFields).forEach(([key, value]) => {
827
- if (value) {
828
- parts.push(`${key}=${encodeURIComponent(String(value))}`);
829
- }
830
- });
831
-
832
- // Returns a string like: "email=john@example.com~phone=+1234567890~department=Engineering"
833
- return parts.join("~");
834
- };
835
- ```
836
-
837
- **Important:** The `customFields` parameter is **NOT** passed as an object to the widget. It's just a convenient way to pass multiple additional fields to the helper function. The function flattens everything into a single string format.
838
-
839
- **Usage in your application:**
840
-
841
- ```jsx
842
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
843
- import "@data-netmonk/mona-chat-widget/dist/style.css";
844
- import { buildChatWidgetData } from "./helpers/chatWidget";
845
-
846
- function App() {
847
- // Get user data from your auth system, state, or props
848
- const user = {
849
- id: "user123",
850
- name: "John Doe",
851
- email: "john@example.com",
852
- phone: "+628123456789"
853
- };
854
-
855
- // Build the data string with custom variables only
856
- const customData = buildChatWidgetData({
857
- email: user.email,
858
- phone: user.phone,
859
- customFields: {
860
- department: "Engineering",
861
- role: "Developer",
862
- company: "Acme Corp"
863
- }
864
- });
865
-
866
- // customData is now a STRING like:
867
- // "email=john@example.com~phone=%2B628123456789~department=Engineering~role=Developer~company=Acme%20Corp"
868
-
869
- return (
870
- <ChatWidget
871
- userId={user.id}
872
- sourceId="691e1b5952068ff7aaeccffc9"
873
- webhookUrl="https://api.example.com/webhook"
874
- authUrl="https://api.example.com/login/chatwidget" // Direct prop
875
- username={user.name} // Direct prop
876
- data={customData} // Only additional variables
877
- />
878
- );
879
- }
880
- ```
881
-
882
- **With conditional authentication:**
883
-
884
- ```jsx
885
- function App() {
886
- const authUrl = import.meta.env.VITE_CHAT_AUTH_ENABLED === "true"
887
- ? import.meta.env.VITE_CHAT_AUTH_URL
888
- : null;
889
-
890
- const customData = buildChatWidgetData({
891
- email: getCurrentUser().email,
892
- customFields: {
893
- department: "Sales"
894
- }
895
- });
896
-
897
- return (
898
- <ChatWidget
899
- userId={getCurrentUser().id}
900
- sourceId="691e1b5952068ff7aaeccffc9"
901
- webhookUrl={import.meta.env.VITE_WEBHOOK_URL}
902
- authUrl={authUrl} // Direct prop (conditionally set)
903
- username={getCurrentUser().name} // Direct prop
904
- data={customData} // Only additional variables
905
- />
906
- );
907
- }
908
- ```
909
-
910
- **The helper function handles:**
911
- - ✅ Proper formatting with `~` separators
912
- - ✅ URL encoding for special characters
913
- - ✅ Conditional parameter inclusion (only adds if value exists)
914
- - ✅ Support for dynamic custom fields
915
- - ✅ Type safety and documentation via JSDoc comments
916
-
917
- ### Standalone app (for demonstration)
918
-
919
- 1. **How to run locally** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
920
-
921
- ```
922
- npm run dev
923
- ```
924
-
925
- 2. **How to build as a standalone app** (build file at `/dist-app` directory)
926
-
927
- ```
928
- npm run build-app
929
- ```
930
-
931
- 3. **How to serve standalone app** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
932
-
933
- ```
934
- npm run serve
935
- ```
936
-
937
- 4. **How to run on docker** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
938
-
939
- ```
940
- docker-compose up --build
941
- ```
942
-
943
- ---
1
+ # Mona Chat Widget
2
+
3
+ Chat widget package developed by Netmonk data & solution team to be imported in Netmonk products
4
+
5
+ ---
6
+
7
+ ### Recent Updates & Breaking Changes
8
+
9
+ ---
10
+
11
+ **Latest Version Changes (`v2.6.6`):**
12
+
13
+ ✅ **Patch Updates:**
14
+ 1. **Voice mode now fully controls TTS playback** - Disabling voice mode clears queued audio and stops active playback cleanly
15
+ 2. **Library build now resolves phoneme assets correctly** - Avatar viseme images are bundled through module URLs so published packages can load them reliably
16
+ 3. **Message rendering is more stable** - Messages now receive deterministic IDs and React keys to avoid collisions during history load and streaming updates
17
+ 4. **Streaming flow is more consistent** - Mock responses, webhook responses, and TTS playback now use the same streaming helper to keep text timing aligned
18
+
19
+ ✅ **Previous Non-breaking Changes (`v2.6.1`):**
20
+ 1. **Built-in voice mode button** - Chat widget now includes a microphone button to toggle voice mode directly from the input area
21
+ 2. **Speech-to-text flow for voice mode** - Recorded user audio is sent to the configured STT endpoint, then the transcription is forwarded as a normal chat message
22
+ 3. **Phoneme-driven avatar animation** - The widget can animate Mona's avatar during TTS playback based on phoneme/viseme IDs returned by the backend
23
+ 4. **Supported phoneme IDs** - `A`, `BP`, `ChJ`, `E`, `FV`, `I`, `KG`, `L`, `M`, `O`, `SZ`, `U`
24
+
25
+ ⚠️ **Previous Breaking Changes:**
26
+ 1. **Removed `type` and `agentType` props** - These parameters are no longer used and have been removed from all components
27
+ 2. **Renamed `botServerUrl` to `webhookUrl`** - For better clarity and consistency
28
+ 3. **`webhookUrl` is now required** - Must be provided as a prop
29
+ 4. **`authUrl` and `username` are now direct props** - No longer part of `data` prop for better clarity and type safety
30
+ 5. **`userId` is now optional** - Widget automatically generates visitor ID for guest users via browser fingerprinting when `userId` is not provided
31
+
32
+ ✨ **New Features:**
33
+ 1. **Guest user support** - Users can chat without logging in
34
+ - Automatic visitor ID generation using browser fingerprinting (FingerprintJS + SHA256)
35
+ - `auth: false` flag automatically added to API requests for guest users
36
+ - Persistent sessions across page reloads for anonymous visitors
37
+ 2. **Authentication support** - Automatic token management with refresh on 401 errors
38
+ - Pass `authUrl` as a direct prop
39
+ - Widget handles token lifecycle automatically
40
+ 3. **Enhanced user authentication handling** - Smart detection of authenticated vs guest users
41
+ - Compares `userId` with visitor ID to determine authentication status
42
+ - Automatic `auth` flag management in all API requests
43
+ 4. **Enhanced data prop** - Support for custom variables passed to backend via `data` prop
44
+ 5. **Improved error handling** - Better fallback mechanisms and error messages
45
+ 6. **Direct `username` prop** - Pass username as a top-level prop instead of in data string
46
+
47
+ 📝 **Migration Guide:**
48
+ ```jsx
49
+ // Old usage (deprecated)
50
+ <ChatWidget
51
+ userId="user123"
52
+ sourceId="source456"
53
+ type="prime"
54
+ botServerUrl="https://api.example.com"
55
+ />
56
+
57
+ // Previous version (with data prop)
58
+ <ChatWidget
59
+ userId="user123"
60
+ sourceId="source456"
61
+ webhookUrl="https://api.example.com/webhook"
62
+ data="authUrl=https://api.example.com/login/chatwidget~username=John"
63
+ />
64
+
65
+ // New usage - Authenticated user (current - recommended)
66
+ <ChatWidget
67
+ userId="user123"
68
+ sourceId="source456"
69
+ webhookUrl="https://api.example.com/webhook"
70
+ authUrl="https://api.example.com/login/chatwidget"
71
+ username="John"
72
+ data="email=john@example.com~phone=+1234567890"
73
+ />
74
+
75
+ // New usage - Guest user (without login)
76
+ <ChatWidget
77
+ sourceId="source456"
78
+ webhookUrl="https://api.example.com/webhook"
79
+ username="Guest"
80
+ />
81
+ // Widget automatically generates visitor ID and adds auth: false flag
82
+
83
+ // New usage - Conditional (handles both logged-in and guest users)
84
+ <ChatWidget
85
+ userId={currentUser?.id} // undefined for guests
86
+ sourceId="source456"
87
+ webhookUrl="https://api.example.com/webhook"
88
+ username={currentUser?.name || "Guest"}
89
+ />
90
+ ```
91
+
92
+ ---
93
+
94
+ ## 🚅 Quick start
95
+
96
+ ---
97
+
98
+ 1. Install the package
99
+ ```bash
100
+ npm install @data-netmonk/mona-chat-widget
101
+ ```
102
+ 2. Render the widget with the minimum required props
103
+ ```jsx
104
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
105
+
106
+ export default function App() {
107
+ return (
108
+ <ChatWidget
109
+ sourceId="source-123"
110
+ webhookUrl="https://api.example.com/webhook"
111
+ />
112
+ );
113
+ }
114
+ ```
115
+ 3. Choose the usage mode you need
116
+
117
+ **1. Basic mode**
118
+
119
+ ```jsx
120
+ <ChatWidget
121
+ sourceId="source-123"
122
+ webhookUrl="https://api.example.com/webhook"
123
+ />
124
+ ```
125
+
126
+ **2. With auth mode**
127
+
128
+ ```jsx
129
+ <ChatWidget
130
+ sourceId="source-123"
131
+ webhookUrl="https://api.example.com/webhook"
132
+ userId="user-123"
133
+ username="John"
134
+ authUrl="https://api.example.com/login/chatwidget"
135
+ />
136
+ ```
137
+
138
+ **3. With voice mode**
139
+
140
+ ```jsx
141
+ <ChatWidget
142
+ sourceId="source-123"
143
+ webhookUrl="https://api.example.com/webhook"
144
+ voiceConfig={{
145
+ sttEndpoint: "https://voice.example.com/stt",
146
+ sttApiKey: "your-stt-key",
147
+ ttsEndpoint: "https://voice.example.com/tts",
148
+ ttsApiKey: "your-tts-key",
149
+ ttsMinioObjectEndpoint: "https://voice.example.com/minio/object",
150
+ ttsMinioApiKey: "your-minio-key",
151
+ ttsMinioBucket: "chatbot-tts",
152
+ }}
153
+ />
154
+ ```
155
+
156
+ **4. Full props example**
157
+
158
+ ```jsx
159
+ <ChatWidget
160
+ sourceId="source-123"
161
+ webhookUrl="https://api.example.com/webhook"
162
+ userId="user-123"
163
+ authUrl="https://api.example.com/login/chatwidget"
164
+ username="John Doe"
165
+ voiceConfig={{
166
+ sttEndpoint: "https://voice.example.com/stt",
167
+ sttApiKey: "your-stt-key",
168
+ ttsEndpoint: "https://voice.example.com/tts",
169
+ ttsApiKey: "your-tts-key",
170
+ ttsMinioObjectEndpoint: "https://voice.example.com/minio/object",
171
+ ttsMinioApiKey: "your-minio-key",
172
+ ttsMinioBucket: "chatbot-tts",
173
+ }}
174
+ />
175
+ ```
176
+
177
+ **Common optional props**
178
+
179
+ - `userId`: ID user login. Jika tidak diisi, widget akan memakai guest visitor ID.
180
+ - `username`: nama yang dikirim bersama session chat.
181
+ - `authUrl`: endpoint auth/refresh khusus chat widget.
182
+ - `data`: custom payload tambahan untuk backend.
183
+ - `voiceConfig`: aktifkan voice mode, STT, TTS, dan phoneme avatar bila dibutuhkan.
184
+ - `showLauncher`, `autoOpen`, `enabled`, `position`, `width`, `height`: untuk perilaku dan layout widget.
185
+
186
+ By default, STT and TTS requests send the API key using `x-api-key` without any prefix. MinIO object fetch uses `X-API-Key`. If your provider needs a different auth format, override it through `voiceConfig`.
187
+
188
+ ---
189
+
190
+ ### Mock Mode (Demo without Backend)
191
+
192
+ ---
193
+
194
+ The chat widget includes a built-in mock mode for testing and demonstrations without requiring a backend server.
195
+
196
+ **To enable mock mode:**
197
+ 1. Pass `widgetConfig={{ useMockResponses: true }}`
198
+ 2. Render the widget normally
199
+
200
+ **Mock responses include:**
201
+ - Greetings (hello, hi, halo, start) - with interactive buttons
202
+ - Help commands
203
+ - Device list with clickable buttons (type "devices" or "show devices")
204
+ - Thank you acknowledgments
205
+ - Farewells
206
+ - Time-based greetings (good morning, etc.)
207
+ - And automatically falls back to mock mode if backend fails
208
+
209
+ **Interactive Buttons Demo:**
210
+ Type these messages to see button responses:
211
+ - "start" - Welcome message with action buttons
212
+ - "show devices" or "devices" - List of devices as clickable buttons
213
+ - Click any button to trigger the next action
214
+
215
+ **To add custom mock responses:**
216
+ Edit the `mockBotResponses` object in `src/components/ChatWidget/utils/mockBotResponses.js`
217
+
218
+ For text-only responses:
219
+ ```javascript
220
+ "trigger": "Response text"
221
+ ```
222
+
223
+ For responses with buttons:
224
+ ```javascript
225
+ "trigger": {
226
+ text: "Your message here",
227
+ buttons: [
228
+ { title: "Button 1", payload: "action_1" },
229
+ { title: "Button 2", payload: "action_2" },
230
+ { title: "Link Button", url: "https://example.com" }
231
+ ]
232
+ }
233
+ ```
234
+
235
+ ---
236
+
237
+ ### Storybook
238
+
239
+ ---
240
+
241
+ 1. **How to run Storybook locally** (access at http://localhost:5177)
242
+
243
+ ```
244
+ npm run storybook
245
+ ```
246
+
247
+ 2. **How to build Storybook**
248
+
249
+ ```
250
+ npm run build-storybook
251
+ ```
252
+
253
+ 3. **How to serve Storybook**
254
+
255
+ ```
256
+ npm run serve-storybook
257
+ ```
258
+
259
+ ---
260
+
261
+ ### Library (how to update and publish)
262
+
263
+ ---
264
+
265
+ 1. **Commit changes**
266
+ ```
267
+ git add .
268
+ git commit -m "Your commit message"
269
+ ```
270
+ 2. **Update version**
271
+
272
+ ```bash
273
+ npm version patch # for bug fixes (1.0.0 -> 1.0.1)
274
+ ```
275
+
276
+ ```bash
277
+ npm version minor # for new features (1.0.0 -> 1.1.0)
278
+ ```
279
+
280
+ ```bash
281
+ npm version major # for breaking changes (1.0.0 -> 2.0.0)
282
+ ```
283
+
284
+ 3. **Build as a library** (build file at `/dist` directory)
285
+
286
+ ```
287
+ npm run build
288
+ ```
289
+
290
+ 4. **Copy declaration file to `/dist`**
291
+
292
+ ```
293
+ cp ./src/declarations/index.d.ts ./dist/index.d.ts
294
+ ```
295
+
296
+ 5. **Publish**
297
+
298
+ ```
299
+ npm publish
300
+ ```
301
+
302
+ ---
303
+
304
+ ### Library (how to import on your project)
305
+
306
+ ---
307
+
308
+ 1. **Install package**
309
+ ```bash
310
+ npm install @data-netmonk/mona-chat-widget
311
+ ```
312
+
313
+ 2. **Import styles on your `App.jsx` or `index.jsx`**
314
+
315
+ ```jsx
316
+ import "@data-netmonk/mona-chat-widget/dist/style.css";
317
+ ```
318
+
319
+ 3. **Import & use component**
320
+
321
+ ```jsx
322
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
323
+
324
+ function App() {
325
+ return (
326
+ <ChatWidget
327
+ userId="user123"
328
+ sourceId="691e1b5952068ff7aaeccffc9"
329
+ webhookUrl="https://your-backend-url.com"
330
+ />
331
+ );
332
+ }
333
+ ```
334
+
335
+ ---
336
+
337
+ ### Component Props
338
+
339
+ ---
340
+
341
+ #### Required Props
342
+
343
+ | Prop | Type | Description |
344
+ |------|------|-------------|
345
+ | `sourceId` | `string` | **Required.** Source/channel identifier for the chat |
346
+ | `webhookUrl` | `string` | **Required.** Backend webhook URL |
347
+
348
+ #### Optional Props
349
+
350
+ | Prop | Type | Default | Description |
351
+ |------|------|---------|-------------|
352
+ | `userId` | `string` | Generated visitor ID | Unique identifier for the user. **Optional** - if not provided, a unique visitor ID is automatically generated using browser fingerprinting for guest users |
353
+ | `authUrl` | `string` | - | Authentication endpoint URL for token-based authentication |
354
+ | `username` | `string` | - | Username for the session (sent to backend in variables) |
355
+ | `data` | `string` | - | Additional custom variables in format `key1=value1~key2=value2` |
356
+ | `width` | `string` | `"25vw"` | Widget width (CSS value) |
357
+ | `height` | `string` | `"90vh"` | Widget height (CSS value) |
358
+ | `right` | `string` | `"1.25rem"` | Distance from right edge |
359
+ | `bottom` | `string` | `"1.25rem"` | Distance from bottom edge |
360
+ | `zIndex` | `number` | `2000` | CSS z-index for widget positioning |
361
+ | `position` | `string` | `"fixed"` | CSS position (`"fixed"` or `"relative"`) |
362
+ | `onToggle` | `function` | - | Callback when widget opens/closes: `(isOpen: boolean) => void` |
363
+
364
+ ---
365
+
366
+ ### Voice Mode & Phoneme Support
367
+
368
+ ---
369
+
370
+ The widget now includes a built-in microphone button in the input area. Clicking the button toggles voice mode, requests microphone access, records speech, sends the recorded audio to `voiceConfig.sttEndpoint`, and forwards the returned transcription as a regular user message.
371
+
372
+ If `voiceConfig.sttApiKey` is set, the STT request includes `x-api-key: <key>` by default. If another provider needs a different auth format, override it through `voiceConfig`.
373
+
374
+ **Expected STT request/response shape:**
375
+
376
+ - Request body: `multipart/form-data`
377
+ - Audio field name: `audio`
378
+ - Request headers: `Accept: application/json`, plus `x-api-key: <key>` when configured for the Netmonk STT service
379
+ - Expected JSON response:
380
+
381
+ ```json
382
+ {
383
+ "text": "Halo, saya mau tanya status tiket saya"
384
+ }
385
+ ```
386
+
387
+ During TTS playback, the header avatar can switch between phoneme images using the metadata returned by the TTS service. The widget currently supports these phoneme IDs:
388
+
389
+ - `A`
390
+ - `BP`
391
+ - `ChJ`
392
+ - `E`
393
+ - `FV`
394
+ - `I`
395
+ - `KG`
396
+ - `L`
397
+ - `M`
398
+ - `O`
399
+ - `SZ`
400
+ - `U`
401
+
402
+ Phoneme IDs are normalized case-insensitively in the widget, so values such as `ChJ` and `CHJ` resolve to the same avatar image.
403
+
404
+ **Preferred webhook TTS asset shape (MinIO-first):**
405
+
406
+ ```json
407
+ {
408
+ "messages": [{ "type": "text", "text": "Halo, ada yang bisa saya bantu?" }],
409
+ "tts_assets": {
410
+ "items": [
411
+ {
412
+ "bucket": "chatbot-tts",
413
+ "response_index": 1,
414
+ "audio_object_key": "tts/...wav",
415
+ "phoneme_object_key": "tts/...phonemes.txt",
416
+ "phoneme_timeline_object_key": "tts/...phoneme-timeline.json",
417
+ "viseme_timeline_object_key": "tts/...viseme-timeline.json"
418
+ }
419
+ ]
420
+ }
421
+ }
422
+ ```
423
+
424
+ When this payload is available, the widget resolves MinIO object keys first for audio and timeline metadata. If no usable asset is found, it falls back to `voiceConfig.ttsEndpoint`.
425
+
426
+ **Expected TTS response shape:**
427
+
428
+ ```text
429
+ body: <raw WAV or other audio binary>
430
+ content-type: audio/wav
431
+ x-tts-metadata-id: 7d8f6a0f0f684e97b7b8d8db67f4f3a1
432
+ x-tts-metadata-path: /tts/metadata/7d8f6a0f0f684e97b7b8d8db67f4f3a1
433
+ x-tts-duration-ms: 120
434
+ ```
435
+
436
+ Metadata lengkap diambil dari endpoint metadata yang ditunjuk header tadi:
437
+
438
+ ```json
439
+ GET /tts/metadata/7d8f6a0f0f684e97b7b8d8db67f4f3a1
440
+ {
441
+ "id": "7d8f6a0f0f684e97b7b8d8db67f4f3a1",
442
+ "phonemes": "M A",
443
+ "phoneme_timeline": [
444
+ { "phoneme": "M", "startMs": 0, "endMs": 120 }
445
+ ],
446
+ "visemes": [
447
+ { "id": "M", "startMs": 0, "endMs": 120 },
448
+ { "id": "A", "startMs": 121, "endMs": 260 },
449
+ { "id": "SZ", "startMs": 261, "endMs": 420 }
450
+ ],
451
+ "duration_ms": 420
452
+ }
453
+ ```
454
+
455
+ Widget ini masih mendukung header base64 lama sebagai fallback:
456
+
457
+ ```text
458
+ x-tts-visemes-b64
459
+ x-tts-phonemes-b64
460
+ x-tts-phoneme-timeline-b64
461
+ ```
462
+
463
+ Widget juga masih mendukung format JSON body lama sebagai fallback, tetapi untuk audio binary format metadata endpoint di atas sekarang jadi jalur utama yang direkomendasikan.
464
+
465
+ ---
466
+
467
+ ### Guest User Support
468
+
469
+ ---
470
+
471
+ The widget now supports **guest users** (users who haven't logged in or before logging in). When `userId` is not provided, the widget automatically generates a unique visitor ID using browser fingerprinting.
472
+
473
+ **How it works:**
474
+
475
+ 1. **Browser Fingerprinting**: Uses FingerprintJS to generate a unique identifier based on:
476
+ - Browser characteristics (user agent, screen resolution, timezone, etc.)
477
+ - Incognito/private browsing detection
478
+ - Combined into a SHA256 hash for consistency
479
+
480
+ 2. **Automatic Guest Detection**: The widget automatically detects guest users and:
481
+ - Generates a visitor ID if `userId` is not provided
482
+ - Adds `auth: false` flag to all API requests for guest users
483
+ - Uses the visitor ID as the effective user ID throughout the session
484
+
485
+ 3. **Persistent Sessions**: The visitor ID remains consistent across page reloads (unless browser fingerprint changes or user clears data)
486
+
487
+ **When to use guest mode:**
488
+ - Public-facing websites where users can chat without logging in
489
+ - Support widgets for anonymous visitors
490
+ - Pre-login customer service interactions
491
+ - Any scenario where user authentication is optional
492
+
493
+ **When to provide userId:**
494
+ - Users who are logged into your application
495
+ - When you need to track chat history across devices
496
+ - When authentication is required for personalized responses
497
+ - Enterprise or internal applications
498
+
499
+ ---
500
+
501
+ ### Usage Examples
502
+
503
+ ---
504
+
505
+ #### Basic Usage (Authenticated User)
506
+
507
+ ```jsx
508
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
509
+ import "@data-netmonk/mona-chat-widget/dist/style.css";
510
+
511
+ function App() {
512
+ return (
513
+ <ChatWidget
514
+ userId="user123"
515
+ sourceId="691e1b5952068ff7aaeccffc9"
516
+ webhookUrl="https://api.example.com/webhook"
517
+ />
518
+ );
519
+ }
520
+ ```
521
+
522
+ #### Guest User (Without Login)
523
+
524
+ For anonymous visitors or users who haven't logged in:
525
+
526
+ ```jsx
527
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
528
+ import "@data-netmonk/mona-chat-widget/dist/style.css";
529
+
530
+ function App() {
531
+ return (
532
+ <ChatWidget
533
+ sourceId="691e1b5952068ff7aaeccffc9"
534
+ webhookUrl="https://api.example.com/webhook"
535
+ />
536
+ );
537
+ }
538
+ ```
539
+
540
+ **What happens:**
541
+ - Widget automatically generates a visitor ID using browser fingerprinting
542
+ - All API requests include `auth: false` in variables to indicate guest user
543
+ - No login required - users can start chatting immediately
544
+
545
+ #### Conditional userId (Logged in or Guest)
546
+
547
+ Handle both logged-in users and guests dynamically:
548
+
549
+ ```jsx
550
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
551
+ import "@data-netmonk/mona-chat-widget/dist/style.css";
552
+
553
+ function App() {
554
+ const currentUser = getCurrentUser(); // Your auth function
555
+
556
+ return (
557
+ <ChatWidget
558
+ userId={currentUser?.id} // Provide userId if logged in, undefined if not
559
+ sourceId="691e1b5952068ff7aaeccffc9"
560
+ webhookUrl="https://api.example.com/webhook"
561
+ username={currentUser?.name}
562
+ />
563
+ );
564
+ }
565
+ ```
566
+
567
+ **Behavior:**
568
+ - If `currentUser.id` exists → Uses provided userId (authenticated user)
569
+ - If `currentUser.id` is `null`/`undefined` → Generates visitor ID (guest user)
570
+ - Backend receives `auth: false` flag only for guest users
571
+
572
+ #### With Custom Variables
573
+
574
+ Pass custom user data like email, phone number, etc. to the backend:
575
+
576
+ ```jsx
577
+ <ChatWidget
578
+ userId="user123"
579
+ sourceId="691e1b5952068ff7aaeccffc9"
580
+ webhookUrl="https://api.example.com/webhook"
581
+ username="John Doe"
582
+ data="telephone_number=+628123456789~email=john@example.com"
583
+ />
584
+ ```
585
+
586
+ **Variables sent to backend:**
587
+ ```json
588
+ {
589
+ "chat_id": "...",
590
+ "session_id": "...",
591
+ "user_id": "user123",
592
+ "message": "Hello",
593
+ "type": "text",
594
+ "variables": {
595
+ "username": "John Doe",
596
+ "telephone_number": "+628123456789",
597
+ "email": "john@example.com"
598
+ }
599
+ }
600
+ ```
601
+ }
602
+ ```
603
+ #### With Authentication (NEW!)
604
+
605
+ The widget supports automatic authentication with token refresh on expiry:
606
+
607
+ ```jsx
608
+ <ChatWidget
609
+ userId="user123"
610
+ sourceId="691e1b5952068ff7aaeccffc9"
611
+ webhookUrl="https://api.example.com/webhook"
612
+ authUrl="https://api.example.com/login/chatwidget"
613
+ username="John Doe"
614
+ />
615
+ ```
616
+
617
+ **How authentication works:**
618
+
619
+ 1. **Initial Authentication**: When the widget loads (on Launcher mount), if `authUrl` is provided as a prop, it automatically calls the auth API:
620
+ ```
621
+ POST {authUrl}
622
+ Body: { "user_id": "user123" }
623
+ Response: { "token": "eyJ..." }
624
+ ```
625
+
626
+ 2. **Session Initialization**: After getting the token, the widget calls the init endpoint to establish the session:
627
+ ```
628
+ POST {webhookUrl}/{sourceId}/init
629
+ Body: {
630
+ "session_id": "...",
631
+ "user_id": "user123",
632
+ "token": "eyJ...",
633
+ "username": "John Doe"
634
+ }
635
+ ```
636
+
637
+ 3. **Automatic Token Refresh**: If the webhook returns **401 Unauthorized** (token expired/revoked), the widget automatically:
638
+ - Calls the `authUrl` again to get a fresh token
639
+ - Re-initializes the session with the new token
640
+ - Updates the `authToken` in variables
641
+ - Retries the failed request with the new token
642
+ - This happens transparently without user interaction
643
+
644
+ **Combined example with auth and custom variables:**
645
+ ```jsx
646
+ <ChatWidget
647
+ userId="user123"
648
+ sourceId="691e1b5952068ff7aaeccffc9"
649
+ webhookUrl="https://api.example.com/webhook"
650
+ authUrl="https://api.example.com/login/chatwidget"
651
+ username="John"
652
+ data="email=john@example.com~phone=+628123456789"
653
+ />
654
+ ```
655
+
656
+ **Auth Flag (`auth` variable):**
657
+ The widget automatically adds an `auth: false` flag to the `variables` object when the user is a guest (not authenticated):
658
+ - When `userId === visitorId` (browser fingerprint), the widget adds `auth: false` to variables
659
+ - When `userId !== visitorId` (authenticated user), no `auth` flag is added to variables
660
+
661
+ **Guest user example** (userId equals browser fingerprint):
662
+ ```json
663
+ {
664
+ "chat_id": "...",
665
+ "session_id": "...",
666
+ "user_id": "visitor_abc123",
667
+ "message": "Hello",
668
+ "type": "text",
669
+ "variables": {
670
+ "username": "Guest",
671
+ "auth": false
672
+ }
673
+ }
674
+ ```
675
+
676
+ **Authenticated user example** (userId is different from browser fingerprint):
677
+ ```json
678
+ {
679
+ "chat_id": "...",
680
+ "session_id": "...",
681
+ "user_id": "user123",
682
+ "message": "Hello",
683
+ "type": "text",
684
+ "variables": {
685
+ "username": "John Doe",
686
+ "email": "john@example.com"
687
+ }
688
+ }
689
+ ```
690
+
691
+ This `auth: false` flag is automatically added in:
692
+ - Session initialization payload (when guest)
693
+ - All message requests (when guest)
694
+ - Button postback requests (when guest)
695
+
696
+ **Note:** The `data` prop is for additional custom variables only. Required parameters (`userId`, `sourceId`, `webhookUrl`) and common optional parameters (`authUrl`, `username`) should be passed as direct props for better type safety and clarity.
697
+
698
+ #### Custom Styling
699
+
700
+ ```jsx
701
+ <ChatWidget
702
+ userId="user123" // Optional
703
+ sourceId="691e1b5952068ff7aaeccffc9"
704
+ webhookUrl="https://api.example.com/webhook"
705
+ width="400px"
706
+ height="600px"
707
+ right="20px"
708
+ bottom="20px"
709
+ zIndex={9999}
710
+ />
711
+ ```
712
+
713
+ #### Embedded in Layout (Relative Positioning)
714
+
715
+ ```jsx
716
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 400px' }}>
717
+ <div>Main content</div>
718
+ <ChatWidget
719
+ userId="user123" // Optional
720
+ sourceId="691e1b5952068ff7aaeccffc9"
721
+ webhookUrl="https://api.example.com/webhook"
722
+ position="relative"
723
+ width="100%"
724
+ height="100vh"
725
+ />
726
+ </div>
727
+ ```
728
+
729
+ #### With Toggle Callback
730
+
731
+ ```jsx
732
+ function App() {
733
+ const handleToggle = (isOpen) => {
734
+ console.log('Chat widget is', isOpen ? 'open' : 'closed');
735
+ // Track analytics, update UI, etc.
736
+ };
737
+
738
+ return (
739
+ <ChatWidget
740
+ userId="user123" // Optional
741
+ sourceId="691e1b5952068ff7aaeccffc9"
742
+ webhookUrl="https://api.example.com/webhook"
743
+ onToggle={handleToggle}
744
+ />
745
+ );
746
+ }
747
+ ```
748
+
749
+ #### Full-Screen Widget
750
+
751
+ ```jsx
752
+ <ChatWidget
753
+ userId="user123" // Optional - supports guest users
754
+ sourceId="691e1b5952068ff7aaeccffc9"
755
+ webhookUrl="https://api.example.com/webhook"
756
+ width="100vw"
757
+ height="100vh"
758
+ right="0"
759
+ bottom="0"
760
+ />
761
+ ```
762
+
763
+ ---
764
+
765
+ ### Data Prop Format & Helper Function
766
+
767
+ ---
768
+
769
+ The `data` prop allows you to pass additional custom variables via a single string. This is especially useful when you need to dynamically pass user-specific data or pass it through URL parameters.
770
+
771
+ **Format:** `key=value` pairs separated by `~`
772
+
773
+ **Example data string:**
774
+ ```
775
+ email=john@example.com~phone=+1234567890~department=Engineering
776
+ ```
777
+
778
+ **Note:** `authUrl` and `username` are now **direct props** and should not be included in the `data` string.
779
+
780
+ #### Building Data String Programmatically
781
+
782
+ Instead of manually constructing the data string, you can create a helper function to build it dynamically. This approach is recommended for production applications as it:
783
+ - Prevents syntax errors in the data string format
784
+ - Makes the code more maintainable and readable
785
+ - Allows for conditional inclusion of parameters
786
+ - Handles URL encoding automatically if needed
787
+
788
+ **Create a helper function** (`src/helpers/chatWidget.js` or similar):
789
+
790
+ ```jsx
791
+ /**
792
+ * Builds the data string for ChatWidget component
793
+ * @param {Object} params - Object containing all parameters
794
+ * @param {string} params.email - Optional: User's email address
795
+ * @param {string} params.phone - Optional: User's phone number
796
+ * @param {Object} params.customFields - Optional: Any additional custom fields as key-value pairs
797
+ * @returns {string} Formatted data string for ChatWidget (always returns a string)
798
+ *
799
+ * @example
800
+ * // Returns: "email=john@example.com~phone=+1234567890~department=Engineering"
801
+ * buildChatWidgetData({
802
+ * email: "john@example.com",
803
+ * phone: "+1234567890",
804
+ * customFields: { department: "Engineering" }
805
+ * });
806
+ */
807
+ export const buildChatWidgetData = ({
808
+ email,
809
+ phone,
810
+ customFields = {}
811
+ }) => {
812
+ const parts = [];
813
+
814
+ // Add standard fields
815
+ if (email) {
816
+ parts.push(`email=${encodeURIComponent(email)}`);
817
+ }
818
+
819
+ if (phone) {
820
+ parts.push(`phone=${encodeURIComponent(phone)}`);
821
+ }
822
+
823
+ // Add any custom fields dynamically
824
+ // Note: customFields is just a convenience parameter for the helper function
825
+ // Each field will be flattened into the string format: key=value~key2=value2
826
+ Object.entries(customFields).forEach(([key, value]) => {
827
+ if (value) {
828
+ parts.push(`${key}=${encodeURIComponent(String(value))}`);
829
+ }
830
+ });
831
+
832
+ // Returns a string like: "email=john@example.com~phone=+1234567890~department=Engineering"
833
+ return parts.join("~");
834
+ };
835
+ ```
836
+
837
+ **Important:** The `customFields` parameter is **NOT** passed as an object to the widget. It's just a convenient way to pass multiple additional fields to the helper function. The function flattens everything into a single string format.
838
+
839
+ **Usage in your application:**
840
+
841
+ ```jsx
842
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
843
+ import "@data-netmonk/mona-chat-widget/dist/style.css";
844
+ import { buildChatWidgetData } from "./helpers/chatWidget";
845
+
846
+ function App() {
847
+ // Get user data from your auth system, state, or props
848
+ const user = {
849
+ id: "user123",
850
+ name: "John Doe",
851
+ email: "john@example.com",
852
+ phone: "+628123456789"
853
+ };
854
+
855
+ // Build the data string with custom variables only
856
+ const customData = buildChatWidgetData({
857
+ email: user.email,
858
+ phone: user.phone,
859
+ customFields: {
860
+ department: "Engineering",
861
+ role: "Developer",
862
+ company: "Acme Corp"
863
+ }
864
+ });
865
+
866
+ // customData is now a STRING like:
867
+ // "email=john@example.com~phone=%2B628123456789~department=Engineering~role=Developer~company=Acme%20Corp"
868
+
869
+ return (
870
+ <ChatWidget
871
+ userId={user.id}
872
+ sourceId="691e1b5952068ff7aaeccffc9"
873
+ webhookUrl="https://api.example.com/webhook"
874
+ authUrl="https://api.example.com/login/chatwidget" // Direct prop
875
+ username={user.name} // Direct prop
876
+ data={customData} // Only additional variables
877
+ />
878
+ );
879
+ }
880
+ ```
881
+
882
+ **With conditional authentication:**
883
+
884
+ ```jsx
885
+ function App() {
886
+ const authUrl = import.meta.env.VITE_CHAT_AUTH_ENABLED === "true"
887
+ ? import.meta.env.VITE_CHAT_AUTH_URL
888
+ : null;
889
+
890
+ const customData = buildChatWidgetData({
891
+ email: getCurrentUser().email,
892
+ customFields: {
893
+ department: "Sales"
894
+ }
895
+ });
896
+
897
+ return (
898
+ <ChatWidget
899
+ userId={getCurrentUser().id}
900
+ sourceId="691e1b5952068ff7aaeccffc9"
901
+ webhookUrl={import.meta.env.VITE_WEBHOOK_URL}
902
+ authUrl={authUrl} // Direct prop (conditionally set)
903
+ username={getCurrentUser().name} // Direct prop
904
+ data={customData} // Only additional variables
905
+ />
906
+ );
907
+ }
908
+ ```
909
+
910
+ **The helper function handles:**
911
+ - ✅ Proper formatting with `~` separators
912
+ - ✅ URL encoding for special characters
913
+ - ✅ Conditional parameter inclusion (only adds if value exists)
914
+ - ✅ Support for dynamic custom fields
915
+ - ✅ Type safety and documentation via JSDoc comments
916
+
917
+ ### Standalone app (for demonstration)
918
+
919
+ 1. **How to run locally** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
920
+
921
+ ```
922
+ npm run dev
923
+ ```
924
+
925
+ 2. **How to build as a standalone app** (build file at `/dist-app` directory)
926
+
927
+ ```
928
+ npm run build-app
929
+ ```
930
+
931
+ 3. **How to serve standalone app** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
932
+
933
+ ```
934
+ npm run serve
935
+ ```
936
+
937
+ 4. **How to run on docker** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
938
+
939
+ ```
940
+ docker-compose up --build
941
+ ```
942
+
943
+ ---