teko-chat-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,462 @@
1
+ # TekoChatSDK
2
+
3
+ React component library giúp các ECOM app của Teko tích hợp AI chatbot với khả năng **bidirectional communication** — right panel có thể push context ngược lại cho AI.
4
+
5
+ ## Tính năng
6
+
7
+ - **3 UI states**: `bubble` → `mini` popup → `fullscreen` split layout (35/65)
8
+ - **Mobile mode**: `layoutMode="mobile"` — mini popup chuyển thành bottom sheet full-width; fullscreen chuyển thành single-column với floating toggle icon + right panel bottom sheet overlay
9
+ - **Streaming responses**: AI response xuất hiện dần theo chunks (typewriter effect)
10
+ - **Pluggable transport**: Auto-detect giao thức từ `chatBffUrl` scheme — WebSocket (`wss://`), HTTP Streaming (`https://`), hoặc mock built-in (không có URL)
11
+ - **Bidirectional context**: Right-side app UI push context ngược về AI qua `ref.sendContext()`
12
+ - **Intent-driven UI**: BFF trả về intent → SDK tự điều phối giao diện
13
+ - **Quick reply**: Suggest options từ BFF hiển thị dưới dạng clickable buttons
14
+ - **Mock handler**: Không cần backend thật, truyền `chatBffUrl=""` để dùng mock built-in với streaming giả lập
15
+ - **Debug panel**: `onDebugEvent` callback theo dõi toàn bộ request/response/intent/context
16
+ - **Customizable bubble**: Truyền `renderBubble` để dùng UI bubble tùy chỉnh
17
+
18
+ ## Cài đặt
19
+
20
+ ```bash
21
+ yarn add teko-chat-sdk
22
+ # hoặc
23
+ npm install teko-chat-sdk
24
+ ```
25
+
26
+ **Peer dependencies:**
27
+
28
+ ```bash
29
+ yarn add react react-dom
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ```tsx
35
+ import { useRef } from 'react';
36
+ import { TekoChatWidget } from 'teko-chat-sdk';
37
+ import type { TekoChatWidgetRef } from 'teko-chat-sdk';
38
+
39
+ export default function App() {
40
+ const chatRef = useRef<TekoChatWidgetRef>(null);
41
+
42
+ return (
43
+ <>
44
+ <YourApp />
45
+
46
+ <TekoChatWidget
47
+ ref={chatRef}
48
+ appId="your-app-id"
49
+ chatBffUrl="https://your-bff.example.com/chat"
50
+ onIntent={(action, componentKey) => {
51
+ if (action === 'show_ui' && componentKey) {
52
+ chatRef.current?.openFullscreen(componentKey);
53
+ }
54
+ }}
55
+ renderRightPanel={(componentKey) => {
56
+ if (componentKey === 'cart') return <CartPage />;
57
+ return null;
58
+ }}
59
+ />
60
+ </>
61
+ );
62
+ }
63
+ ```
64
+
65
+ ## API
66
+
67
+ ### `<TekoChatWidget>` Props
68
+
69
+ **Core**
70
+
71
+ | Prop | Type | Required | Mô tả |
72
+ | ------------ | -------- | -------- | ------------------------------------------------------------------------- |
73
+ | `appId` | `string` | ✅ | App identifier, gửi lên BFF để phân biệt context |
74
+ | `chatBffUrl` | `string` | ✅ | BFF endpoint. `wss://` → WebSocket, `https://` → HTTP stream, `""` → mock |
75
+
76
+ **Business logic**
77
+
78
+ | Prop | Type | Mô tả |
79
+ | ------------------ | ------------------------------------------------- | ----------------------------------------------------------------------- |
80
+ | `onIntent` | `(action: string, componentKey?: string) => void` | Callback khi BFF trả về intent action |
81
+ | `renderRightPanel` | `(componentKey: string) => ReactNode` | Render content cho right panel ở fullscreen state |
82
+ | `getAppContext` | `() => Record<string, unknown> \| Promise<...>` | Inject app metadata vào mỗi BFF request, gọi tại send-time (luôn fresh) |
83
+
84
+ **UI customization**
85
+
86
+ | Prop | Type | Default | Mô tả |
87
+ | -------------- | -------------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------- |
88
+ | `renderBubble` | `(onClick: () => void) => ReactNode` | — | Custom bubble UI, thay thế bubble mặc định |
89
+ | `bubbleAnchor` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | `'bottom-right'` | Vị trí bubble, ảnh hưởng hướng animation fullscreen |
90
+ | `layoutMode` | `'desktop' \| 'mobile'` | `'desktop'` | Desktop: mini popup + split fullscreen. Mobile: bottom sheet + single column |
91
+ | `primaryColor` | `string` | `'#1a73e8'` | Primary color (hex) cho toàn bộ widget |
92
+ | `botAvatar` | `string` | — | Avatar URL cho AI bubble |
93
+ | `locale` | `'vi' \| 'en'` | `'vi'` | Ngôn ngữ hiển thị |
94
+ | `labels` | `Partial<ChatLabels>` | — | Override từng label cụ thể, kết hợp được với `locale` |
95
+
96
+ **Layout / spacing**
97
+
98
+ | Prop | Type | Default | Mô tả |
99
+ | -------------- | -------- | ------- | -------------------------------------------------- |
100
+ | `offsetTop` | `number` | `0` | Offset từ top (px) — tránh che fixed header |
101
+ | `offsetBottom` | `number` | `0` | Offset từ bottom (px) — tránh che fixed bottom nav |
102
+ | `miniWidth` | `number` | `360` | Chiều rộng mini popup (px) |
103
+ | `miniHeight` | `number` | `480` | Chiều cao mini popup (px) |
104
+ | `zIndex` | `number` | `1031` | CSS z-index. Fullscreen backdrop = zIndex + 9 |
105
+
106
+ **Dev only**
107
+
108
+ | Prop | Type | Mô tả |
109
+ | -------------- | ----------------------------- | --------------------------- |
110
+ | `onDebugEvent` | `(event: DebugEvent) => void` | Nhận mọi SDK event để debug |
111
+
112
+ ### `TekoChatWidgetRef` Methods
113
+
114
+ Truy cập qua `ref.current`:
115
+
116
+ | Method | Signature | Mô tả |
117
+ | ----------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
118
+ | `open` | `() => void` | Mở mini popup |
119
+ | `close` | `() => void` | Đóng chat, về bubble state |
120
+ | `openFullscreen` | `(componentKey: string) => void` | Mở fullscreen split với component key |
121
+ | `closeFullscreen` | `() => void` | Thu nhỏ về mini popup |
122
+ | `sendMessage` | `(text: string, context?: Record<string, unknown>) => void` | Gửi message lên BFF programmatically; `context` được forward kèm request (dùng cho quick reply payload) |
123
+ | `sendContext` | `(data: Record<string, unknown>) => void` | Push context từ right panel ngược về AI |
124
+
125
+ ## Customization
126
+
127
+ ```tsx
128
+ <TekoChatWidget
129
+ appId="your-app-id"
130
+ chatBffUrl="https://your-bff.example.com/chat"
131
+ primaryColor="#e53e3e" // thay màu chủ đạo
132
+ zIndex={2000} // tăng z-index nếu app có overlay khác
133
+ offsetTop={64} // chừa 64px cho fixed header
134
+ offsetBottom={56} // chừa 56px cho bottom navigation bar
135
+ miniWidth={400}
136
+ miniHeight={520}
137
+ />
138
+ ```
139
+
140
+ ## Mobile Mode
141
+
142
+ SDK không tự detect viewport — consumer app tự detect và truyền `layoutMode` vào:
143
+
144
+ ```tsx
145
+ function App() {
146
+ const [layoutMode, setLayoutMode] = useState<'desktop' | 'mobile'>(
147
+ () => (window.innerWidth < 768 ? 'mobile' : 'desktop'),
148
+ );
149
+
150
+ useEffect(() => {
151
+ const handler = () =>
152
+ setLayoutMode(window.innerWidth < 768 ? 'mobile' : 'desktop');
153
+ window.addEventListener('resize', handler);
154
+ return () => window.removeEventListener('resize', handler);
155
+ }, []);
156
+
157
+ return (
158
+ <TekoChatWidget
159
+ layoutMode={layoutMode}
160
+ renderRightPanel={(key) =>
161
+ layoutMode === 'mobile' ? <MobileCartPage /> : <DesktopCartPage />
162
+ }
163
+ // ...
164
+ />
165
+ );
166
+ }
167
+ ```
168
+
169
+ **Mobile behavior:**
170
+ - `mini` state: full-width bottom sheet slide up từ đáy (thay vì popup 360px); click vùng tối bên ngoài để đóng
171
+ - `fullscreen` state: chat luôn hiển thị full-screen; khi BFF trigger `show_ui`, right panel slide up dạng bottom sheet overlay phía trên chat; click vùng tối để đóng overlay về chat; tap floating toggle icon để mở lại right panel
172
+
173
+ ## Bidirectional Communication
174
+
175
+ Điểm khác biệt chính của SDK: right panel có thể push context ngược về AI để cải thiện intent detection.
176
+
177
+ ```tsx
178
+ // App.tsx
179
+ <TekoChatWidget
180
+ ref={chatRef}
181
+ renderRightPanel={(componentKey) => (
182
+ <CartPage
183
+ onContextUpdate={(data) => chatRef.current?.sendContext(data)}
184
+ />
185
+ )}
186
+ />
187
+
188
+ // CartPage.tsx
189
+ function CartPage({ onContextUpdate }) {
190
+ const handleQtyChange = (qty: number) => {
191
+ updateCart(qty);
192
+ onContextUpdate({
193
+ type: 'cart_updated',
194
+ items: cartItems,
195
+ totalAmount: calculateTotal(),
196
+ });
197
+ };
198
+ }
199
+ ```
200
+
201
+ ## BFF Protocol
202
+
203
+ SDK tự chọn giao thức dựa vào scheme của `chatBffUrl`:
204
+
205
+ | `chatBffUrl` | Giao thức | Ghi chú |
206
+ | ------------------------------- | -------------- | --------------------------------------------------------------------- |
207
+ | `wss://...` hoặc `ws://...` | WebSocket | Persistent connection, BFF push `TransportFrame` JSON qua `ws.send()` |
208
+ | `https://...` hoặc `http://...` | HTTP Streaming | POST request, BFF trả newline-delimited JSON (`application/x-ndjson`) |
209
+ | `""` hoặc không truyền | Mock handler | Built-in, không cần backend — simulate streaming word-by-word |
210
+
211
+ ### TransportFrame (BFF → SDK streaming format)
212
+
213
+ BFF cần gửi các frames theo format sau (mỗi frame là một JSON object):
214
+
215
+ ```typescript
216
+ type TransportFrame =
217
+ | { type: 'chunk'; text: string } // một đoạn text AI đang generate
218
+ | { type: 'done'; data: BFFResponseData } // kết thúc, kèm full response data
219
+ | { type: 'error'; message: string } // lỗi
220
+ ```
221
+
222
+ **WebSocket:** Mỗi `ws.send()` là một frame JSON.
223
+
224
+ **HTTP Streaming:** Mỗi dòng (`\n`-delimited) là một frame JSON.
225
+
226
+ ---
227
+
228
+ ## BFF JSON Contract
229
+
230
+ ### BFF → SDK (Response)
231
+
232
+ ```json
233
+ {
234
+ "code": 200,
235
+ "message": "ok",
236
+ "data": {
237
+ "conversationId": "conv-abc123",
238
+ "message": "Bạn muốn thêm loại nào?",
239
+ "intent": "addToCart",
240
+ "action": "show_ui",
241
+ "componentKey": "cart",
242
+ "suggest": {
243
+ "options": [
244
+ { "key": "suon-non", "label": "Sườn non", "payload": { "sku": "SN-001" } },
245
+ { "key": "ga-dong-tao", "label": "Gà đông tảo", "payload": { "sku": "GA-001" } }
246
+ ]
247
+ },
248
+ "timestamp": 1742208605000
249
+ }
250
+ }
251
+ ```
252
+
253
+ **`action` values SDK xử lý:**
254
+ - `show_ui` — SDK gọi `renderRightPanel(componentKey)` và chuyển sang fullscreen
255
+ - Các value khác — SDK forward sang `onIntent` callback để app tự xử lý
256
+
257
+ ### SDK → BFF (Request)
258
+
259
+ ```json
260
+ // Nhắn tin thường
261
+ { "conversationId": "conv-abc123", "message": "tôi muốn mua gà", "timestamp": 1742208600000 }
262
+
263
+ // Click quick reply (có context từ option payload)
264
+ { "conversationId": "conv-abc123", "message": "Sườn non", "context": { "sku": "SN-001" }, "timestamp": 1742208600000 }
265
+
266
+ // Push context từ right panel (không có message)
267
+ { "conversationId": "conv-abc123", "context": { "type": "cart_updated", "items": [...] }, "timestamp": 1742208600000 }
268
+
269
+ // Với getAppContext (appContext được inject vào mọi request nếu có giá trị)
270
+ { "conversationId": "conv-abc123", "message": "tôi muốn mua gà", "appContext": { "terminalId": "T001", "terminalCode": "freshmart-hanoi" }, "timestamp": 1742208600000 }
271
+ ```
272
+
273
+ ## Release
274
+
275
+ Quy trình release lên NPM qua `release-it` + GitLab CI.
276
+
277
+ ### Setup Gitlab project
278
+
279
+ Vào **Settings > CI/CD > Variables**, thêm:
280
+
281
+ | Variable | Value | Protected | Masked |
282
+ | ----------- | ------------------ | --------- | ------ |
283
+ | `NPM_TOKEN` | token từ npmjs.org | x | x |
284
+
285
+ ### Quy trình release
286
+
287
+ ```bash
288
+ # 1. Merge tất cả feature branches vào main, đảm bảo pass CI
289
+
290
+ # 2. Run các command local
291
+ yarn install
292
+ yarn release
293
+ ```
294
+
295
+ `yarn release` sẽ:
296
+
297
+ 1. Phân tích commit history (Conventional Commits) để gợi ý version bump
298
+ 2. Cập nhật `version` trong `package.json`
299
+ 3. Tạo entry mới trong `CHANGELOG.md`
300
+ 4. Tạo commit `chore(release): vX.Y.Z`
301
+ 5. Tạo git tag `vX.Y.Z`
302
+ 6. Push commit + tag lên GitLab
303
+
304
+ GitLab CI detect tag `v*.*.*` → tự động chạy job `publish:npm`:
305
+
306
+ ```
307
+ yarn lint → yarn build → npm publish --access public
308
+ ```
309
+
310
+ ### Version bump theo Conventional Commits
311
+
312
+ | Commit prefix | Version bump |
313
+ | --------------------------- | ------------- |
314
+ | `fix:`, `perf:` | patch (0.0.x) |
315
+ | `feat:` | minor (0.x.0) |
316
+ | `BREAKING CHANGE` in footer | major (x.0.0) |
317
+
318
+ ### Pre-release (alpha / beta / rc)
319
+
320
+ ```bash
321
+ yarn release --preRelease=alpha # → v1.0.0-alpha.1, npm tag: alpha
322
+ yarn release --preRelease=beta # → v1.0.0-beta.1, npm tag: beta
323
+ yarn release --preRelease=rc # → v1.0.0-rc.1, npm tag: rc
324
+ ```
325
+
326
+ Consumer cài pre-release bằng cách chỉ định dist-tag:
327
+
328
+ ```bash
329
+ npm install teko-chat-sdk@beta
330
+ npm install teko-chat-sdk@alpha
331
+ ```
332
+
333
+ Package `latest` (stable) không bị ảnh hưởng bởi các lần publish pre-release.
334
+
335
+ ### Manual version override
336
+
337
+ ```bash
338
+ yarn release --release-version 1.0.0 # chỉ định version cụ thể
339
+ yarn release minor # ép bump minor
340
+ yarn release patch --dry-run # preview không commit thật
341
+ ```
342
+
343
+ ### Checklist trước khi release
344
+
345
+ - [ ] `main` branch CI pass
346
+ - [ ] `yarn lint` và `yarn build` pass local
347
+ - [ ] Working directory sạch (không có uncommitted changes)
348
+ - [ ] `NPM_TOKEN` CI variable đã được set trong GitLab project
349
+
350
+ ---
351
+
352
+ ## Development
353
+
354
+ ```bash
355
+ # Clone repo
356
+ git clone <repo-url>
357
+ cd teko-chat-sdk
358
+
359
+ # Cài dependencies
360
+ yarn install
361
+
362
+ # Chạy demo app (localhost:5173)
363
+ yarn dev
364
+
365
+ # Build SDK
366
+ yarn build
367
+
368
+ # Lint
369
+ yarn lint
370
+ ```
371
+
372
+ ### Storybook
373
+
374
+ ```bash
375
+ # Dev server — localhost:6006
376
+ yarn storybook
377
+
378
+ # Build static output
379
+ yarn build-storybook
380
+ ```
381
+
382
+ **Stories có sẵn:**
383
+
384
+ | Story | Mô tả |
385
+ | -------------------- | ------------------------------------------------------------------------------------------------- |
386
+ | `SDK/TekoChatWidget` | Full integration — BubbleState, WithCustomColor, WithOffsets, MiniPopup, MobileMode |
387
+ | `SDK/ChatBubble` | Default, CustomColor, GreenColor, WithOffset |
388
+ | `SDK/ChatMiniPopup` | Empty, WithMessages, WithLoading, CustomColor, CustomSize, MobileBottomSheet |
389
+ | `SDK/ChatFullscreen` | SplitLayout, WithoutRightPanel, CustomColor, WithHeaderOffset, MobileLayout, MobileLayoutChatView |
390
+ | `SDK/DataFlow` | DataFlowInspector — inline event log dùng `onDebugEvent` |
391
+ | `SDK/MessageBubble` | UserMessage, AIMessage, AIMessageWithOptions, CustomColor |
392
+
393
+ ### Cấu trúc
394
+
395
+ ```
396
+ teko-chat-sdk/
397
+ ├── src/
398
+ │ ├── locales/
399
+ │ │ └── index.ts # ChatLabels interface, BUILT_IN_LABELS (vi/en), resolveLabels()
400
+ │ ├── components/
401
+ │ │ ├── TekoChatWidget.tsx # Root component, state machine
402
+ │ │ ├── ChatBubble.tsx # State 1: floating button
403
+ │ │ ├── ChatMiniPopup.tsx # State 2: mini popup
404
+ │ │ ├── ChatFullscreen.tsx # State 3: fullscreen split layout
405
+ │ │ ├── MessageList.tsx
406
+ │ │ ├── MessageBubble.tsx # Message + quick reply options
407
+ │ │ └── MessageInput.tsx
408
+ │ ├── hooks/
409
+ │ │ └── useChatSession.ts # Session management, orchestrates transport
410
+ │ ├── transports/
411
+ │ │ ├── ITransport.ts # ITransport interface + TransportFrame type
412
+ │ │ ├── WebSocketTransport.ts # wss:// — persistent WS, auto-retry
413
+ │ │ ├── HttpStreamTransport.ts # https:// — fetch ndjson streaming
414
+ │ │ ├── MockTransport.ts # dev fallback — wraps mockHandler, simulates chunks
415
+ │ │ └── createTransport.ts # factory: auto-detect từ bffUrl scheme
416
+ │ ├── utils/
417
+ │ │ ├── mockHandler.ts # Mock BFF logic (dùng bởi MockTransport)
418
+ │ │ └── chatTheme.ts # ChatThemeContext, useChatTheme, hexToRgba
419
+ │ ├── stories/ # Storybook stories
420
+ │ │ ├── TekoChatWidget.stories.tsx
421
+ │ │ ├── ChatBubble.stories.tsx
422
+ │ │ ├── ChatMiniPopup.stories.tsx
423
+ │ │ ├── ChatFullscreen.stories.tsx
424
+ │ │ ├── DataFlow.stories.tsx
425
+ │ │ └── MessageBubble.stories.tsx
426
+ │ ├── types.ts # Full type definitions
427
+ │ └── index.ts # Public exports
428
+ ├── .storybook/ # Storybook config
429
+ │ ├── main.ts
430
+ │ └── preview.ts
431
+ └── demo/ # Demo app (FreshMart ECOM)
432
+ ```
433
+
434
+ ## Architecture
435
+
436
+ ```
437
+ ECOM App
438
+
439
+ └── <TekoChatWidget ref={chatRef} chatBffUrl="..." onIntent={...} renderRightPanel={...} />
440
+
441
+ │ createTransport(chatBffUrl) — auto-detect từ scheme:
442
+ │ wss:// → WebSocketTransport (persistent, streaming chunks)
443
+ │ https:// → HttpStreamTransport (fetch ndjson)
444
+ │ "" → MockTransport (dev, simulate streaming)
445
+
446
+ BFF → LLM Core → Business Services
447
+ (push TransportFrame chunks: {type:'chunk'} → {type:'done'})
448
+ ```
449
+
450
+ **UI States:**
451
+
452
+ ```
453
+ bubble ──click──► mini ──intent: show_ui──► fullscreen (split 35/65)
454
+ ▲ │ │
455
+ └──────close─────┘◄─────────close───────────────┘
456
+ ```
457
+
458
+ ## Scope POC
459
+
460
+ **In scope:** 3 UI states, pluggable transport (WS/HTTP streaming/mock), streaming responses, demo app, bidirectional communication, quick reply, mobile mode layout
461
+
462
+ **Out of scope:** BFF thật, authentication, message persistence