@data-netmonk/mona-chat-widget 2.6.6 → 2.6.7

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,928 +1,942 @@
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 `VITE_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
- ### Prerequisites
97
-
98
- ---
99
-
100
- 1. Install dependencies
101
- ```
102
- npm install --legacy-peer-deps
103
- ```
104
- 2. Copy .env.example
105
- ```
106
- cp .env.example .env
107
- ```
108
- 3. Populate .env
109
- 4. Optional speech endpoints
110
-
111
- To enable voice mode and phoneme-based avatar animation, set these environment variables in your `.env` file:
112
- ```
113
- VITE_STT_ENDPOINT=https://voice.netmonk-ai.tech/stt
114
- VITE_STT_API_KEY=
115
- VITE_STT_API_KEY_HEADER=x-api-key
116
- VITE_STT_API_KEY_PREFIX=
117
- VITE_TTS_ENDPOINT=https://your-tts-service.example.com/synthesize
118
- VITE_TTS_API_KEY=
119
- VITE_TTS_API_KEY_HEADER=Authorization
120
- VITE_TTS_API_KEY_PREFIX=Bearer
121
- VITE_PREFER_WEBHOOK_TTS=true
122
- VITE_TTS_MINIO_OBJECT_ENDPOINT=http://localhost:8000/minio/object
123
- VITE_TTS_MINIO_API_KEY=
124
- VITE_TTS_MINIO_API_KEY_HEADER=X-API-Key
125
- VITE_TTS_MINIO_API_KEY_PREFIX=
126
- VITE_TTS_MINIO_BUCKET=chatbot-tts
127
- VITE_TTS_MINIO_DOWNLOAD=false
128
- ```
129
-
130
- `VITE_STT_ENDPOINT` is used by the built-in mic button to transcribe recorded audio.
131
- For the Netmonk STT service, set `VITE_STT_API_KEY_HEADER=x-api-key` and leave `VITE_STT_API_KEY_PREFIX` empty so the widget sends the raw key value without `Bearer`.
132
- `VITE_PREFER_WEBHOOK_TTS=true` makes the widget prioritize TTS assets from webhook response (for example `tts_assets.items[].audio_object_key`) and use `VITE_TTS_ENDPOINT` only as fallback.
133
- Widget tidak lagi konek langsung ke MinIO. Object diambil lewat endpoint voice-engine:
134
- `GET /minio/object?object_key=<key>&bucket=<bucket>&download=<true|false>`.
135
- `VITE_TTS_MINIO_API_KEY` opsional. Jika diisi, widget mengirim header `X-API-Key` (atau header custom via `VITE_TTS_MINIO_API_KEY_HEADER`).
136
- `VITE_TTS_MINIO_BUCKET` dipakai sebagai default bucket bila payload webhook tidak mengirim bucket.
137
- Set `VITE_TTS_MINIO_DOWNLOAD=true` jika ingin force download mode saat fetch object.
138
- `VITE_TTS_ENDPOINT` is used for fallback TTS playback.
139
- The widget expects the TTS response body to contain raw audio binary such as `audio/wav`, and reads lip-sync metadata from `x-tts-visemes-b64`, `x-tts-phonemes-b64`, and `x-tts-phoneme-timeline-b64` response headers.
140
- If your TTS endpoint is called cross-origin, the server must expose those headers with `Access-Control-Expose-Headers`.
141
- If your TTS service uses a different header such as `x-api-key`, set `VITE_TTS_API_KEY_HEADER=x-api-key` and leave the prefix empty.
142
- If you are consuming the published package inside another project, prefer passing these values via the `voiceConfig` or `widgetConfig` props because the host app `.env` is not injected into an already-built library bundle.
143
- You can also set runtime defaults on the host page before mounting the widget via `window.__MONA_WIDGET_RUNTIME_CONFIG__`.
144
- Example:
145
- ```jsx
146
- <ChatWidget
147
- sourceId="source456"
148
- webhookUrl="https://api.example.com/webhook"
149
- voiceConfig={{
150
- sttEndpoint: "https://voice.netmonk-ai.tech/stt",
151
- sttApiKey: "your-key",
152
- sttApiKeyHeader: "x-api-key",
153
- sttApiKeyPrefix: "",
154
- ttsEndpoint: "https://voice.example.com/tts",
155
- ttsApiKey: "your-key",
156
- ttsApiKeyHeader: "Authorization",
157
- ttsApiKeyPrefix: "Bearer",
158
- }}
159
- />
160
- ```
161
- 5. Optional TTS debug logging
162
-
163
- To inspect TTS queue and playback lifecycle in browser console, set `VITE_DEBUG_TTS=true` in your `.env` file.
164
- Keep this disabled in production to avoid noisy logs.
165
- 6. Enable mock mode (optional)
166
-
167
- To test the chat widget without a backend server, set `VITE_USE_MOCK_RESPONSES=true` in your `.env` file. The widget will respond to messages like:
168
- - "start", "hello", "hi", "halo" - Greeting messages
169
- - "help", "bantuan" - Help information
170
- - "terima kasih", "thank you" - Acknowledgments
171
- - "bye", "goodbye" - Farewell messages
172
- - And more! Check `src/components/ChatWidget/utils/helpers.js` for full list
173
-
174
- ---
175
-
176
- ### Mock Mode (Demo without Backend)
177
-
178
- ---
179
-
180
- The chat widget includes a built-in mock mode for testing and demonstrations without requiring a backend server.
181
-
182
- **To enable mock mode:**
183
- 1. Set `VITE_USE_MOCK_RESPONSES=true` in your `.env` file
184
- 2. Run the app normally with `npm run dev`
185
-
186
- **Mock responses include:**
187
- - Greetings (hello, hi, halo, start) - with interactive buttons
188
- - Help commands
189
- - Device list with clickable buttons (type "devices" or "show devices")
190
- - Thank you acknowledgments
191
- - Farewells
192
- - Time-based greetings (good morning, etc.)
193
- - And automatically falls back to mock mode if backend fails
194
-
195
- **Interactive Buttons Demo:**
196
- Type these messages to see button responses:
197
- - "start" - Welcome message with action buttons
198
- - "show devices" or "devices" - List of devices as clickable buttons
199
- - Click any button to trigger the next action
200
-
201
- **To add custom mock responses:**
202
- Edit the `mockBotResponses` object in `src/components/ChatWidget/utils/mockBotResponses.js`
203
-
204
- For text-only responses:
205
- ```javascript
206
- "trigger": "Response text"
207
- ```
208
-
209
- For responses with buttons:
210
- ```javascript
211
- "trigger": {
212
- text: "Your message here",
213
- buttons: [
214
- { title: "Button 1", payload: "action_1" },
215
- { title: "Button 2", payload: "action_2" },
216
- { title: "Link Button", url: "https://example.com" }
217
- ]
218
- }
219
- ```
220
-
221
- ---
222
-
223
- ### Storybook
224
-
225
- ---
226
-
227
- 1. **How to run Storybook locally** (access at http://localhost:5177)
228
-
229
- ```
230
- npm run storybook
231
- ```
232
-
233
- 2. **How to build Storybook**
234
-
235
- ```
236
- npm run build-storybook
237
- ```
238
-
239
- 3. **How to serve Storybook**
240
-
241
- ```
242
- npm run serve-storybook
243
- ```
244
-
245
- ---
246
-
247
- ### Library (how to update and publish)
248
-
249
- ---
250
-
251
- 1. **Commit changes**
252
- ```
253
- git add .
254
- git commit -m "Your commit message"
255
- ```
256
- 2. **Update version**
257
-
258
- ```bash
259
- npm version patch # for bug fixes (1.0.0 -> 1.0.1)
260
- ```
261
-
262
- ```bash
263
- npm version minor # for new features (1.0.0 -> 1.1.0)
264
- ```
265
-
266
- ```bash
267
- npm version major # for breaking changes (1.0.0 -> 2.0.0)
268
- ```
269
-
270
- 3. **Build as a library** (build file at `/dist` directory)
271
-
272
- ```
273
- npm run build
274
- ```
275
-
276
- 4. **Copy declaration file to `/dist`**
277
-
278
- ```
279
- cp ./src/declarations/index.d.ts ./dist/index.d.ts
280
- ```
281
-
282
- 5. **Publish**
283
-
284
- ```
285
- npm publish
286
- ```
287
-
288
- ---
289
-
290
- ### Library (how to import on your project)
291
-
292
- ---
293
-
294
- 1. **Install package**
295
- ```bash
296
- npm install @data-netmonk/mona-chat-widget
297
- ```
298
-
299
- 2. **Import styles on your `App.jsx` or `index.jsx`**
300
-
301
- ```jsx
302
- import "@data-netmonk/mona-chat-widget/dist/style.css";
303
- ```
304
-
305
- 3. **Import & use component**
306
-
307
- ```jsx
308
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
309
-
310
- function App() {
311
- return (
312
- <ChatWidget
313
- userId="user123"
314
- sourceId="691e1b5952068ff7aaeccffc9"
315
- webhookUrl="https://your-backend-url.com"
316
- />
317
- );
318
- }
319
- ```
320
-
321
- ---
322
-
323
- ### Component Props
324
-
325
- ---
326
-
327
- #### Required Props
328
-
329
- | Prop | Type | Description |
330
- |------|------|-------------|
331
- | `sourceId` | `string` | **Required.** Source/channel identifier for the chat |
332
- | `webhookUrl` | `string` | **Required.** Backend webhook URL |
333
-
334
- #### Optional Props
335
-
336
- | Prop | Type | Default | Description |
337
- |------|------|---------|-------------|
338
- | `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 |
339
- | `authUrl` | `string` | - | Authentication endpoint URL for token-based authentication |
340
- | `username` | `string` | - | Username for the session (sent to backend in variables) |
341
- | `data` | `string` | - | Additional custom variables in format `key1=value1~key2=value2` |
342
- | `width` | `string` | `"25vw"` | Widget width (CSS value) |
343
- | `height` | `string` | `"90vh"` | Widget height (CSS value) |
344
- | `right` | `string` | `"1.25rem"` | Distance from right edge |
345
- | `bottom` | `string` | `"1.25rem"` | Distance from bottom edge |
346
- | `zIndex` | `number` | `2000` | CSS z-index for widget positioning |
347
- | `position` | `string` | `"fixed"` | CSS position (`"fixed"` or `"relative"`) |
348
- | `onToggle` | `function` | - | Callback when widget opens/closes: `(isOpen: boolean) => void` |
349
-
350
- ---
351
-
352
- ### Voice Mode & Phoneme Support
353
-
354
- ---
355
-
356
- 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 `VITE_STT_ENDPOINT`, and forwards the returned transcription as a regular user message.
357
-
358
- If `VITE_STT_API_KEY` is set, the STT request also includes the configured auth header. For `https://voice.netmonk-ai.tech/stt`, use `x-api-key` without any prefix. Other providers can still use `Authorization: Bearer <key>` or any custom header via `.env`.
359
-
360
- **Expected STT request/response shape:**
361
-
362
- - Request body: `multipart/form-data`
363
- - Audio field name: `audio`
364
- - Request headers: `Accept: application/json`, plus `x-api-key: <key>` when configured for the Netmonk STT service
365
- - Expected JSON response:
366
-
367
- ```json
368
- {
369
- "text": "Halo, saya mau tanya status tiket saya"
370
- }
371
- ```
372
-
373
- 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:
374
-
375
- - `A`
376
- - `BP`
377
- - `ChJ`
378
- - `E`
379
- - `FV`
380
- - `I`
381
- - `KG`
382
- - `L`
383
- - `M`
384
- - `O`
385
- - `SZ`
386
- - `U`
387
-
388
- Phoneme IDs are normalized case-insensitively in the widget, so values such as `ChJ` and `CHJ` resolve to the same avatar image.
389
-
390
- **Preferred webhook TTS asset shape (MinIO-first):**
391
-
392
- ```json
393
- {
394
- "messages": [{ "type": "text", "text": "Halo, ada yang bisa saya bantu?" }],
395
- "tts_assets": {
396
- "items": [
397
- {
398
- "bucket": "chatbot-tts",
399
- "response_index": 1,
400
- "audio_object_key": "tts/...wav",
401
- "phoneme_object_key": "tts/...phonemes.txt",
402
- "phoneme_timeline_object_key": "tts/...phoneme-timeline.json",
403
- "viseme_timeline_object_key": "tts/...viseme-timeline.json"
404
- }
405
- ]
406
- }
407
- }
408
- ```
409
-
410
- 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 `VITE_TTS_ENDPOINT`.
411
-
412
- **Expected TTS response shape:**
413
-
414
- ```text
415
- body: <raw WAV or other audio binary>
416
- content-type: audio/wav
417
- x-tts-visemes-b64: W3siaWQiOiJNIiwic3RhcnRNcyI6MCwiZW5kTXMiOjEyMH1d
418
- x-tts-phonemes-b64: TSBBCg==
419
- x-tts-phoneme-timeline-b64: W3sicGhvbmVtZSI6Ik0iLCJzdGFydE1zIjowLCJlbmRNcyI6MTIwfV0=
420
- ```
421
-
422
- `x-tts-phonemes-b64` is the legacy phoneme string, while `x-tts-phoneme-timeline-b64` is the timed phoneme timeline. The base64 headers should decode as:
423
-
424
- Decoded `x-tts-phonemes-b64`:
425
-
426
- ```text
427
- M A
428
- ```
429
-
430
- Decoded `x-tts-phoneme-timeline-b64`:
431
-
432
- ```json
433
- [
434
- { "phoneme": "M", "startMs": 0, "endMs": 120 }
435
- ]
436
- ```
437
-
438
- Decoded `x-tts-visemes-b64`:
439
-
440
- ```json
441
- [
442
- { "id": "M", "startMs": 0, "endMs": 120 },
443
- { "id": "A", "startMs": 121, "endMs": 260 },
444
- { "id": "SZ", "startMs": 261, "endMs": 420 }
445
- ]
446
- ```
447
-
448
- The widget still supports the older JSON body format as a fallback, but header-based metadata is now the preferred format for binary audio responses.
449
-
450
- ---
451
-
452
- ### Guest User Support
453
-
454
- ---
455
-
456
- 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.
457
-
458
- **How it works:**
459
-
460
- 1. **Browser Fingerprinting**: Uses FingerprintJS to generate a unique identifier based on:
461
- - Browser characteristics (user agent, screen resolution, timezone, etc.)
462
- - Incognito/private browsing detection
463
- - Combined into a SHA256 hash for consistency
464
-
465
- 2. **Automatic Guest Detection**: The widget automatically detects guest users and:
466
- - Generates a visitor ID if `userId` is not provided
467
- - Adds `auth: false` flag to all API requests for guest users
468
- - Uses the visitor ID as the effective user ID throughout the session
469
-
470
- 3. **Persistent Sessions**: The visitor ID remains consistent across page reloads (unless browser fingerprint changes or user clears data)
471
-
472
- **When to use guest mode:**
473
- - Public-facing websites where users can chat without logging in
474
- - Support widgets for anonymous visitors
475
- - Pre-login customer service interactions
476
- - Any scenario where user authentication is optional
477
-
478
- **When to provide userId:**
479
- - Users who are logged into your application
480
- - When you need to track chat history across devices
481
- - When authentication is required for personalized responses
482
- - Enterprise or internal applications
483
-
484
- ---
485
-
486
- ### Usage Examples
487
-
488
- ---
489
-
490
- #### Basic Usage (Authenticated User)
491
-
492
- ```jsx
493
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
494
- import "@data-netmonk/mona-chat-widget/dist/style.css";
495
-
496
- function App() {
497
- return (
498
- <ChatWidget
499
- userId="user123"
500
- sourceId="691e1b5952068ff7aaeccffc9"
501
- webhookUrl="https://api.example.com/webhook"
502
- />
503
- );
504
- }
505
- ```
506
-
507
- #### Guest User (Without Login)
508
-
509
- For anonymous visitors or users who haven't logged in:
510
-
511
- ```jsx
512
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
513
- import "@data-netmonk/mona-chat-widget/dist/style.css";
514
-
515
- function App() {
516
- return (
517
- <ChatWidget
518
- sourceId="691e1b5952068ff7aaeccffc9"
519
- webhookUrl="https://api.example.com/webhook"
520
- />
521
- );
522
- }
523
- ```
524
-
525
- **What happens:**
526
- - Widget automatically generates a visitor ID using browser fingerprinting
527
- - All API requests include `auth: false` in variables to indicate guest user
528
- - No login required - users can start chatting immediately
529
-
530
- #### Conditional userId (Logged in or Guest)
531
-
532
- Handle both logged-in users and guests dynamically:
533
-
534
- ```jsx
535
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
536
- import "@data-netmonk/mona-chat-widget/dist/style.css";
537
-
538
- function App() {
539
- const currentUser = getCurrentUser(); // Your auth function
540
-
541
- return (
542
- <ChatWidget
543
- userId={currentUser?.id} // Provide userId if logged in, undefined if not
544
- sourceId="691e1b5952068ff7aaeccffc9"
545
- webhookUrl="https://api.example.com/webhook"
546
- username={currentUser?.name}
547
- />
548
- );
549
- }
550
- ```
551
-
552
- **Behavior:**
553
- - If `currentUser.id` exists Uses provided userId (authenticated user)
554
- - If `currentUser.id` is `null`/`undefined` → Generates visitor ID (guest user)
555
- - Backend receives `auth: false` flag only for guest users
556
-
557
- #### With Custom Variables
558
-
559
- Pass custom user data like email, phone number, etc. to the backend:
560
-
561
- ```jsx
562
- <ChatWidget
563
- userId="user123"
564
- sourceId="691e1b5952068ff7aaeccffc9"
565
- webhookUrl="https://api.example.com/webhook"
566
- username="John Doe"
567
- data="telephone_number=+628123456789~email=john@example.com"
568
- />
569
- ```
570
-
571
- **Variables sent to backend:**
572
- ```json
573
- {
574
- "chat_id": "...",
575
- "session_id": "...",
576
- "user_id": "user123",
577
- "message": "Hello",
578
- "type": "text",
579
- "variables": {
580
- "username": "John Doe",
581
- "telephone_number": "+628123456789",
582
- "email": "john@example.com"
583
- }
584
- }
585
- ```
586
- }
587
- ```
588
- #### With Authentication (NEW!)
589
-
590
- The widget supports automatic authentication with token refresh on expiry:
591
-
592
- ```jsx
593
- <ChatWidget
594
- userId="user123"
595
- sourceId="691e1b5952068ff7aaeccffc9"
596
- webhookUrl="https://api.example.com/webhook"
597
- authUrl="https://api.example.com/login/chatwidget"
598
- username="John Doe"
599
- />
600
- ```
601
-
602
- **How authentication works:**
603
-
604
- 1. **Initial Authentication**: When the widget loads (on Launcher mount), if `authUrl` is provided as a prop, it automatically calls the auth API:
605
- ```
606
- POST {authUrl}
607
- Body: { "user_id": "user123" }
608
- Response: { "token": "eyJ..." }
609
- ```
610
-
611
- 2. **Session Initialization**: After getting the token, the widget calls the init endpoint to establish the session:
612
- ```
613
- POST {webhookUrl}/{sourceId}/init
614
- Body: {
615
- "session_id": "...",
616
- "user_id": "user123",
617
- "token": "eyJ...",
618
- "username": "John Doe"
619
- }
620
- ```
621
-
622
- 3. **Automatic Token Refresh**: If the webhook returns **401 Unauthorized** (token expired/revoked), the widget automatically:
623
- - Calls the `authUrl` again to get a fresh token
624
- - Re-initializes the session with the new token
625
- - Updates the `authToken` in variables
626
- - Retries the failed request with the new token
627
- - This happens transparently without user interaction
628
-
629
- **Combined example with auth and custom variables:**
630
- ```jsx
631
- <ChatWidget
632
- userId="user123"
633
- sourceId="691e1b5952068ff7aaeccffc9"
634
- webhookUrl="https://api.example.com/webhook"
635
- authUrl="https://api.example.com/login/chatwidget"
636
- username="John"
637
- data="email=john@example.com~phone=+628123456789"
638
- />
639
- ```
640
-
641
- **Auth Flag (`auth` variable):**
642
- The widget automatically adds an `auth: false` flag to the `variables` object when the user is a guest (not authenticated):
643
- - When `userId === visitorId` (browser fingerprint), the widget adds `auth: false` to variables
644
- - When `userId !== visitorId` (authenticated user), no `auth` flag is added to variables
645
-
646
- **Guest user example** (userId equals browser fingerprint):
647
- ```json
648
- {
649
- "chat_id": "...",
650
- "session_id": "...",
651
- "user_id": "visitor_abc123",
652
- "message": "Hello",
653
- "type": "text",
654
- "variables": {
655
- "username": "Guest",
656
- "auth": false
657
- }
658
- }
659
- ```
660
-
661
- **Authenticated user example** (userId is different from browser fingerprint):
662
- ```json
663
- {
664
- "chat_id": "...",
665
- "session_id": "...",
666
- "user_id": "user123",
667
- "message": "Hello",
668
- "type": "text",
669
- "variables": {
670
- "username": "John Doe",
671
- "email": "john@example.com"
672
- }
673
- }
674
- ```
675
-
676
- This `auth: false` flag is automatically added in:
677
- - Session initialization payload (when guest)
678
- - All message requests (when guest)
679
- - Button postback requests (when guest)
680
-
681
- **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.
682
-
683
- #### Custom Styling
684
-
685
- ```jsx
686
- <ChatWidget
687
- userId="user123" // Optional
688
- sourceId="691e1b5952068ff7aaeccffc9"
689
- webhookUrl="https://api.example.com/webhook"
690
- width="400px"
691
- height="600px"
692
- right="20px"
693
- bottom="20px"
694
- zIndex={9999}
695
- />
696
- ```
697
-
698
- #### Embedded in Layout (Relative Positioning)
699
-
700
- ```jsx
701
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 400px' }}>
702
- <div>Main content</div>
703
- <ChatWidget
704
- userId="user123" // Optional
705
- sourceId="691e1b5952068ff7aaeccffc9"
706
- webhookUrl="https://api.example.com/webhook"
707
- position="relative"
708
- width="100%"
709
- height="100vh"
710
- />
711
- </div>
712
- ```
713
-
714
- #### With Toggle Callback
715
-
716
- ```jsx
717
- function App() {
718
- const handleToggle = (isOpen) => {
719
- console.log('Chat widget is', isOpen ? 'open' : 'closed');
720
- // Track analytics, update UI, etc.
721
- };
722
-
723
- return (
724
- <ChatWidget
725
- userId="user123" // Optional
726
- sourceId="691e1b5952068ff7aaeccffc9"
727
- webhookUrl="https://api.example.com/webhook"
728
- onToggle={handleToggle}
729
- />
730
- );
731
- }
732
- ```
733
-
734
- #### Full-Screen Widget
735
-
736
- ```jsx
737
- <ChatWidget
738
- userId="user123" // Optional - supports guest users
739
- sourceId="691e1b5952068ff7aaeccffc9"
740
- webhookUrl="https://api.example.com/webhook"
741
- width="100vw"
742
- height="100vh"
743
- right="0"
744
- bottom="0"
745
- />
746
- ```
747
-
748
- ---
749
-
750
- ### Data Prop Format & Helper Function
751
-
752
- ---
753
-
754
- 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.
755
-
756
- **Format:** `key=value` pairs separated by `~`
757
-
758
- **Example data string:**
759
- ```
760
- email=john@example.com~phone=+1234567890~department=Engineering
761
- ```
762
-
763
- **Note:** `authUrl` and `username` are now **direct props** and should not be included in the `data` string.
764
-
765
- #### Building Data String Programmatically
766
-
767
- 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:
768
- - Prevents syntax errors in the data string format
769
- - Makes the code more maintainable and readable
770
- - Allows for conditional inclusion of parameters
771
- - Handles URL encoding automatically if needed
772
-
773
- **Create a helper function** (`src/helpers/chatWidget.js` or similar):
774
-
775
- ```jsx
776
- /**
777
- * Builds the data string for ChatWidget component
778
- * @param {Object} params - Object containing all parameters
779
- * @param {string} params.email - Optional: User's email address
780
- * @param {string} params.phone - Optional: User's phone number
781
- * @param {Object} params.customFields - Optional: Any additional custom fields as key-value pairs
782
- * @returns {string} Formatted data string for ChatWidget (always returns a string)
783
- *
784
- * @example
785
- * // Returns: "email=john@example.com~phone=+1234567890~department=Engineering"
786
- * buildChatWidgetData({
787
- * email: "john@example.com",
788
- * phone: "+1234567890",
789
- * customFields: { department: "Engineering" }
790
- * });
791
- */
792
- export const buildChatWidgetData = ({
793
- email,
794
- phone,
795
- customFields = {}
796
- }) => {
797
- const parts = [];
798
-
799
- // Add standard fields
800
- if (email) {
801
- parts.push(`email=${encodeURIComponent(email)}`);
802
- }
803
-
804
- if (phone) {
805
- parts.push(`phone=${encodeURIComponent(phone)}`);
806
- }
807
-
808
- // Add any custom fields dynamically
809
- // Note: customFields is just a convenience parameter for the helper function
810
- // Each field will be flattened into the string format: key=value~key2=value2
811
- Object.entries(customFields).forEach(([key, value]) => {
812
- if (value) {
813
- parts.push(`${key}=${encodeURIComponent(String(value))}`);
814
- }
815
- });
816
-
817
- // Returns a string like: "email=john@example.com~phone=+1234567890~department=Engineering"
818
- return parts.join("~");
819
- };
820
- ```
821
-
822
- **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.
823
-
824
- **Usage in your application:**
825
-
826
- ```jsx
827
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
828
- import "@data-netmonk/mona-chat-widget/dist/style.css";
829
- import { buildChatWidgetData } from "./helpers/chatWidget";
830
-
831
- function App() {
832
- // Get user data from your auth system, state, or props
833
- const user = {
834
- id: "user123",
835
- name: "John Doe",
836
- email: "john@example.com",
837
- phone: "+628123456789"
838
- };
839
-
840
- // Build the data string with custom variables only
841
- const customData = buildChatWidgetData({
842
- email: user.email,
843
- phone: user.phone,
844
- customFields: {
845
- department: "Engineering",
846
- role: "Developer",
847
- company: "Acme Corp"
848
- }
849
- });
850
-
851
- // customData is now a STRING like:
852
- // "email=john@example.com~phone=%2B628123456789~department=Engineering~role=Developer~company=Acme%20Corp"
853
-
854
- return (
855
- <ChatWidget
856
- userId={user.id}
857
- sourceId="691e1b5952068ff7aaeccffc9"
858
- webhookUrl="https://api.example.com/webhook"
859
- authUrl="https://api.example.com/login/chatwidget" // Direct prop
860
- username={user.name} // Direct prop
861
- data={customData} // Only additional variables
862
- />
863
- );
864
- }
865
- ```
866
-
867
- **With conditional authentication:**
868
-
869
- ```jsx
870
- function App() {
871
- const authUrl = import.meta.env.VITE_CHAT_AUTH_ENABLED === "true"
872
- ? import.meta.env.VITE_CHAT_AUTH_URL
873
- : null;
874
-
875
- const customData = buildChatWidgetData({
876
- email: getCurrentUser().email,
877
- customFields: {
878
- department: "Sales"
879
- }
880
- });
881
-
882
- return (
883
- <ChatWidget
884
- userId={getCurrentUser().id}
885
- sourceId="691e1b5952068ff7aaeccffc9"
886
- webhookUrl={import.meta.env.VITE_WEBHOOK_URL}
887
- authUrl={authUrl} // Direct prop (conditionally set)
888
- username={getCurrentUser().name} // Direct prop
889
- data={customData} // Only additional variables
890
- />
891
- );
892
- }
893
- ```
894
-
895
- **The helper function handles:**
896
- - ✅ Proper formatting with `~` separators
897
- - ✅ URL encoding for special characters
898
- - ✅ Conditional parameter inclusion (only adds if value exists)
899
- - ✅ Support for dynamic custom fields
900
- - ✅ Type safety and documentation via JSDoc comments
901
-
902
- ### Standalone app (for demonstration)
903
-
904
- 1. **How to run locally** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
905
-
906
- ```
907
- npm run dev
908
- ```
909
-
910
- 2. **How to build as a standalone app** (build file at `/dist-app` directory)
911
-
912
- ```
913
- npm run build-app
914
- ```
915
-
916
- 3. **How to serve standalone app** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
917
-
918
- ```
919
- npm run serve
920
- ```
921
-
922
- 4. **How to run on docker** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
923
-
924
- ```
925
- docker-compose up --build
926
- ```
927
-
928
- ---
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-visemes-b64: W3siaWQiOiJNIiwic3RhcnRNcyI6MCwiZW5kTXMiOjEyMH1d
432
+ x-tts-phonemes-b64: TSBBCg==
433
+ x-tts-phoneme-timeline-b64: W3sicGhvbmVtZSI6Ik0iLCJzdGFydE1zIjowLCJlbmRNcyI6MTIwfV0=
434
+ ```
435
+
436
+ `x-tts-phonemes-b64` is the legacy phoneme string, while `x-tts-phoneme-timeline-b64` is the timed phoneme timeline. The base64 headers should decode as:
437
+
438
+ Decoded `x-tts-phonemes-b64`:
439
+
440
+ ```text
441
+ M A
442
+ ```
443
+
444
+ Decoded `x-tts-phoneme-timeline-b64`:
445
+
446
+ ```json
447
+ [
448
+ { "phoneme": "M", "startMs": 0, "endMs": 120 }
449
+ ]
450
+ ```
451
+
452
+ Decoded `x-tts-visemes-b64`:
453
+
454
+ ```json
455
+ [
456
+ { "id": "M", "startMs": 0, "endMs": 120 },
457
+ { "id": "A", "startMs": 121, "endMs": 260 },
458
+ { "id": "SZ", "startMs": 261, "endMs": 420 }
459
+ ]
460
+ ```
461
+
462
+ The widget still supports the older JSON body format as a fallback, but header-based metadata is now the preferred format for binary audio responses.
463
+
464
+ ---
465
+
466
+ ### Guest User Support
467
+
468
+ ---
469
+
470
+ 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.
471
+
472
+ **How it works:**
473
+
474
+ 1. **Browser Fingerprinting**: Uses FingerprintJS to generate a unique identifier based on:
475
+ - Browser characteristics (user agent, screen resolution, timezone, etc.)
476
+ - Incognito/private browsing detection
477
+ - Combined into a SHA256 hash for consistency
478
+
479
+ 2. **Automatic Guest Detection**: The widget automatically detects guest users and:
480
+ - Generates a visitor ID if `userId` is not provided
481
+ - Adds `auth: false` flag to all API requests for guest users
482
+ - Uses the visitor ID as the effective user ID throughout the session
483
+
484
+ 3. **Persistent Sessions**: The visitor ID remains consistent across page reloads (unless browser fingerprint changes or user clears data)
485
+
486
+ **When to use guest mode:**
487
+ - Public-facing websites where users can chat without logging in
488
+ - Support widgets for anonymous visitors
489
+ - Pre-login customer service interactions
490
+ - Any scenario where user authentication is optional
491
+
492
+ **When to provide userId:**
493
+ - Users who are logged into your application
494
+ - When you need to track chat history across devices
495
+ - When authentication is required for personalized responses
496
+ - Enterprise or internal applications
497
+
498
+ ---
499
+
500
+ ### Usage Examples
501
+
502
+ ---
503
+
504
+ #### Basic Usage (Authenticated User)
505
+
506
+ ```jsx
507
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
508
+ import "@data-netmonk/mona-chat-widget/dist/style.css";
509
+
510
+ function App() {
511
+ return (
512
+ <ChatWidget
513
+ userId="user123"
514
+ sourceId="691e1b5952068ff7aaeccffc9"
515
+ webhookUrl="https://api.example.com/webhook"
516
+ />
517
+ );
518
+ }
519
+ ```
520
+
521
+ #### Guest User (Without Login)
522
+
523
+ For anonymous visitors or users who haven't logged in:
524
+
525
+ ```jsx
526
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
527
+ import "@data-netmonk/mona-chat-widget/dist/style.css";
528
+
529
+ function App() {
530
+ return (
531
+ <ChatWidget
532
+ sourceId="691e1b5952068ff7aaeccffc9"
533
+ webhookUrl="https://api.example.com/webhook"
534
+ />
535
+ );
536
+ }
537
+ ```
538
+
539
+ **What happens:**
540
+ - Widget automatically generates a visitor ID using browser fingerprinting
541
+ - All API requests include `auth: false` in variables to indicate guest user
542
+ - No login required - users can start chatting immediately
543
+
544
+ #### Conditional userId (Logged in or Guest)
545
+
546
+ Handle both logged-in users and guests dynamically:
547
+
548
+ ```jsx
549
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
550
+ import "@data-netmonk/mona-chat-widget/dist/style.css";
551
+
552
+ function App() {
553
+ const currentUser = getCurrentUser(); // Your auth function
554
+
555
+ return (
556
+ <ChatWidget
557
+ userId={currentUser?.id} // Provide userId if logged in, undefined if not
558
+ sourceId="691e1b5952068ff7aaeccffc9"
559
+ webhookUrl="https://api.example.com/webhook"
560
+ username={currentUser?.name}
561
+ />
562
+ );
563
+ }
564
+ ```
565
+
566
+ **Behavior:**
567
+ - If `currentUser.id` exists → Uses provided userId (authenticated user)
568
+ - If `currentUser.id` is `null`/`undefined` → Generates visitor ID (guest user)
569
+ - Backend receives `auth: false` flag only for guest users
570
+
571
+ #### With Custom Variables
572
+
573
+ Pass custom user data like email, phone number, etc. to the backend:
574
+
575
+ ```jsx
576
+ <ChatWidget
577
+ userId="user123"
578
+ sourceId="691e1b5952068ff7aaeccffc9"
579
+ webhookUrl="https://api.example.com/webhook"
580
+ username="John Doe"
581
+ data="telephone_number=+628123456789~email=john@example.com"
582
+ />
583
+ ```
584
+
585
+ **Variables sent to backend:**
586
+ ```json
587
+ {
588
+ "chat_id": "...",
589
+ "session_id": "...",
590
+ "user_id": "user123",
591
+ "message": "Hello",
592
+ "type": "text",
593
+ "variables": {
594
+ "username": "John Doe",
595
+ "telephone_number": "+628123456789",
596
+ "email": "john@example.com"
597
+ }
598
+ }
599
+ ```
600
+ }
601
+ ```
602
+ #### With Authentication (NEW!)
603
+
604
+ The widget supports automatic authentication with token refresh on expiry:
605
+
606
+ ```jsx
607
+ <ChatWidget
608
+ userId="user123"
609
+ sourceId="691e1b5952068ff7aaeccffc9"
610
+ webhookUrl="https://api.example.com/webhook"
611
+ authUrl="https://api.example.com/login/chatwidget"
612
+ username="John Doe"
613
+ />
614
+ ```
615
+
616
+ **How authentication works:**
617
+
618
+ 1. **Initial Authentication**: When the widget loads (on Launcher mount), if `authUrl` is provided as a prop, it automatically calls the auth API:
619
+ ```
620
+ POST {authUrl}
621
+ Body: { "user_id": "user123" }
622
+ Response: { "token": "eyJ..." }
623
+ ```
624
+
625
+ 2. **Session Initialization**: After getting the token, the widget calls the init endpoint to establish the session:
626
+ ```
627
+ POST {webhookUrl}/{sourceId}/init
628
+ Body: {
629
+ "session_id": "...",
630
+ "user_id": "user123",
631
+ "token": "eyJ...",
632
+ "username": "John Doe"
633
+ }
634
+ ```
635
+
636
+ 3. **Automatic Token Refresh**: If the webhook returns **401 Unauthorized** (token expired/revoked), the widget automatically:
637
+ - Calls the `authUrl` again to get a fresh token
638
+ - Re-initializes the session with the new token
639
+ - Updates the `authToken` in variables
640
+ - Retries the failed request with the new token
641
+ - This happens transparently without user interaction
642
+
643
+ **Combined example with auth and custom variables:**
644
+ ```jsx
645
+ <ChatWidget
646
+ userId="user123"
647
+ sourceId="691e1b5952068ff7aaeccffc9"
648
+ webhookUrl="https://api.example.com/webhook"
649
+ authUrl="https://api.example.com/login/chatwidget"
650
+ username="John"
651
+ data="email=john@example.com~phone=+628123456789"
652
+ />
653
+ ```
654
+
655
+ **Auth Flag (`auth` variable):**
656
+ The widget automatically adds an `auth: false` flag to the `variables` object when the user is a guest (not authenticated):
657
+ - When `userId === visitorId` (browser fingerprint), the widget adds `auth: false` to variables
658
+ - When `userId !== visitorId` (authenticated user), no `auth` flag is added to variables
659
+
660
+ **Guest user example** (userId equals browser fingerprint):
661
+ ```json
662
+ {
663
+ "chat_id": "...",
664
+ "session_id": "...",
665
+ "user_id": "visitor_abc123",
666
+ "message": "Hello",
667
+ "type": "text",
668
+ "variables": {
669
+ "username": "Guest",
670
+ "auth": false
671
+ }
672
+ }
673
+ ```
674
+
675
+ **Authenticated user example** (userId is different from browser fingerprint):
676
+ ```json
677
+ {
678
+ "chat_id": "...",
679
+ "session_id": "...",
680
+ "user_id": "user123",
681
+ "message": "Hello",
682
+ "type": "text",
683
+ "variables": {
684
+ "username": "John Doe",
685
+ "email": "john@example.com"
686
+ }
687
+ }
688
+ ```
689
+
690
+ This `auth: false` flag is automatically added in:
691
+ - Session initialization payload (when guest)
692
+ - All message requests (when guest)
693
+ - Button postback requests (when guest)
694
+
695
+ **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.
696
+
697
+ #### Custom Styling
698
+
699
+ ```jsx
700
+ <ChatWidget
701
+ userId="user123" // Optional
702
+ sourceId="691e1b5952068ff7aaeccffc9"
703
+ webhookUrl="https://api.example.com/webhook"
704
+ width="400px"
705
+ height="600px"
706
+ right="20px"
707
+ bottom="20px"
708
+ zIndex={9999}
709
+ />
710
+ ```
711
+
712
+ #### Embedded in Layout (Relative Positioning)
713
+
714
+ ```jsx
715
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 400px' }}>
716
+ <div>Main content</div>
717
+ <ChatWidget
718
+ userId="user123" // Optional
719
+ sourceId="691e1b5952068ff7aaeccffc9"
720
+ webhookUrl="https://api.example.com/webhook"
721
+ position="relative"
722
+ width="100%"
723
+ height="100vh"
724
+ />
725
+ </div>
726
+ ```
727
+
728
+ #### With Toggle Callback
729
+
730
+ ```jsx
731
+ function App() {
732
+ const handleToggle = (isOpen) => {
733
+ console.log('Chat widget is', isOpen ? 'open' : 'closed');
734
+ // Track analytics, update UI, etc.
735
+ };
736
+
737
+ return (
738
+ <ChatWidget
739
+ userId="user123" // Optional
740
+ sourceId="691e1b5952068ff7aaeccffc9"
741
+ webhookUrl="https://api.example.com/webhook"
742
+ onToggle={handleToggle}
743
+ />
744
+ );
745
+ }
746
+ ```
747
+
748
+ #### Full-Screen Widget
749
+
750
+ ```jsx
751
+ <ChatWidget
752
+ userId="user123" // Optional - supports guest users
753
+ sourceId="691e1b5952068ff7aaeccffc9"
754
+ webhookUrl="https://api.example.com/webhook"
755
+ width="100vw"
756
+ height="100vh"
757
+ right="0"
758
+ bottom="0"
759
+ />
760
+ ```
761
+
762
+ ---
763
+
764
+ ### Data Prop Format & Helper Function
765
+
766
+ ---
767
+
768
+ 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.
769
+
770
+ **Format:** `key=value` pairs separated by `~`
771
+
772
+ **Example data string:**
773
+ ```
774
+ email=john@example.com~phone=+1234567890~department=Engineering
775
+ ```
776
+
777
+ **Note:** `authUrl` and `username` are now **direct props** and should not be included in the `data` string.
778
+
779
+ #### Building Data String Programmatically
780
+
781
+ 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:
782
+ - Prevents syntax errors in the data string format
783
+ - Makes the code more maintainable and readable
784
+ - Allows for conditional inclusion of parameters
785
+ - Handles URL encoding automatically if needed
786
+
787
+ **Create a helper function** (`src/helpers/chatWidget.js` or similar):
788
+
789
+ ```jsx
790
+ /**
791
+ * Builds the data string for ChatWidget component
792
+ * @param {Object} params - Object containing all parameters
793
+ * @param {string} params.email - Optional: User's email address
794
+ * @param {string} params.phone - Optional: User's phone number
795
+ * @param {Object} params.customFields - Optional: Any additional custom fields as key-value pairs
796
+ * @returns {string} Formatted data string for ChatWidget (always returns a string)
797
+ *
798
+ * @example
799
+ * // Returns: "email=john@example.com~phone=+1234567890~department=Engineering"
800
+ * buildChatWidgetData({
801
+ * email: "john@example.com",
802
+ * phone: "+1234567890",
803
+ * customFields: { department: "Engineering" }
804
+ * });
805
+ */
806
+ export const buildChatWidgetData = ({
807
+ email,
808
+ phone,
809
+ customFields = {}
810
+ }) => {
811
+ const parts = [];
812
+
813
+ // Add standard fields
814
+ if (email) {
815
+ parts.push(`email=${encodeURIComponent(email)}`);
816
+ }
817
+
818
+ if (phone) {
819
+ parts.push(`phone=${encodeURIComponent(phone)}`);
820
+ }
821
+
822
+ // Add any custom fields dynamically
823
+ // Note: customFields is just a convenience parameter for the helper function
824
+ // Each field will be flattened into the string format: key=value~key2=value2
825
+ Object.entries(customFields).forEach(([key, value]) => {
826
+ if (value) {
827
+ parts.push(`${key}=${encodeURIComponent(String(value))}`);
828
+ }
829
+ });
830
+
831
+ // Returns a string like: "email=john@example.com~phone=+1234567890~department=Engineering"
832
+ return parts.join("~");
833
+ };
834
+ ```
835
+
836
+ **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.
837
+
838
+ **Usage in your application:**
839
+
840
+ ```jsx
841
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
842
+ import "@data-netmonk/mona-chat-widget/dist/style.css";
843
+ import { buildChatWidgetData } from "./helpers/chatWidget";
844
+
845
+ function App() {
846
+ // Get user data from your auth system, state, or props
847
+ const user = {
848
+ id: "user123",
849
+ name: "John Doe",
850
+ email: "john@example.com",
851
+ phone: "+628123456789"
852
+ };
853
+
854
+ // Build the data string with custom variables only
855
+ const customData = buildChatWidgetData({
856
+ email: user.email,
857
+ phone: user.phone,
858
+ customFields: {
859
+ department: "Engineering",
860
+ role: "Developer",
861
+ company: "Acme Corp"
862
+ }
863
+ });
864
+
865
+ // customData is now a STRING like:
866
+ // "email=john@example.com~phone=%2B628123456789~department=Engineering~role=Developer~company=Acme%20Corp"
867
+
868
+ return (
869
+ <ChatWidget
870
+ userId={user.id}
871
+ sourceId="691e1b5952068ff7aaeccffc9"
872
+ webhookUrl="https://api.example.com/webhook"
873
+ authUrl="https://api.example.com/login/chatwidget" // Direct prop
874
+ username={user.name} // Direct prop
875
+ data={customData} // Only additional variables
876
+ />
877
+ );
878
+ }
879
+ ```
880
+
881
+ **With conditional authentication:**
882
+
883
+ ```jsx
884
+ function App() {
885
+ const authUrl = import.meta.env.VITE_CHAT_AUTH_ENABLED === "true"
886
+ ? import.meta.env.VITE_CHAT_AUTH_URL
887
+ : null;
888
+
889
+ const customData = buildChatWidgetData({
890
+ email: getCurrentUser().email,
891
+ customFields: {
892
+ department: "Sales"
893
+ }
894
+ });
895
+
896
+ return (
897
+ <ChatWidget
898
+ userId={getCurrentUser().id}
899
+ sourceId="691e1b5952068ff7aaeccffc9"
900
+ webhookUrl={import.meta.env.VITE_WEBHOOK_URL}
901
+ authUrl={authUrl} // Direct prop (conditionally set)
902
+ username={getCurrentUser().name} // Direct prop
903
+ data={customData} // Only additional variables
904
+ />
905
+ );
906
+ }
907
+ ```
908
+
909
+ **The helper function handles:**
910
+ - Proper formatting with `~` separators
911
+ - ✅ URL encoding for special characters
912
+ - ✅ Conditional parameter inclusion (only adds if value exists)
913
+ - Support for dynamic custom fields
914
+ - ✅ Type safety and documentation via JSDoc comments
915
+
916
+ ### Standalone app (for demonstration)
917
+
918
+ 1. **How to run locally** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
919
+
920
+ ```
921
+ npm run dev
922
+ ```
923
+
924
+ 2. **How to build as a standalone app** (build file at `/dist-app` directory)
925
+
926
+ ```
927
+ npm run build-app
928
+ ```
929
+
930
+ 3. **How to serve standalone app** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
931
+
932
+ ```
933
+ npm run serve
934
+ ```
935
+
936
+ 4. **How to run on docker** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
937
+
938
+ ```
939
+ docker-compose up --build
940
+ ```
941
+
942
+ ---