teko-assistant-ui
v1.0.4
Published
teko-assistant-ui - AI assistant UI SDK for Teko apps
Readme
teko-assistant-ui
React component library giúp các ECOM app của Teko tích hợp AI chatbot. Host app tự quyết định container, position, và visibility — SDK fill 100% parent.
How it works
Host App
└── <AssistantPanel chatBffUrl="..." transportMode="sse" />
│
│ SDK chọn transport theo transportMode:
│ 'sse' → HTTP streaming (text/event-stream)
│ 'stream' → HTTP streaming (application/x-ndjson)
│ 'websocket' → WebSocket
│ 'rest' → HTTP POST thông thường
▼
BFF → LLM → Business ServicesCài đặt
yarn add teko-assistant-ui
# peer deps
yarn add react react-domUsage
import { AssistantPanel, type AssistantPanelRef } from 'teko-assistant-ui';
import { useRef, useState } from 'react';
type Product = { sku: string; name: string; canonical?: string };
export default function App() {
const chatRef = useRef<AssistantPanelRef>(null);
const [chatOpen, setChatOpen] = useState(false);
return (
<>
{/* Host app tự làm trigger button */}
<button onClick={() => setChatOpen(true)}>Chat với AI</button>
{/* Host app định nghĩa container — SDK fill 100% */}
{chatOpen && (
<div style={{
position: 'fixed', right: 0, top: 0,
width: 380, height: '100vh',
zIndex: 1000, boxShadow: '-4px 0 12px rgba(0,0,0,0.15)',
}}>
<AssistantPanel
ref={chatRef}
appId="your-app-id"
chatBffUrl="https://your-bff.example.com/chat"
transportMode="sse"
getRequestHeaders={async () => ({
'x-authorization': await getAccessToken(),
'x-terminal-id': 'T001',
'x-cart-token': getCartToken(),
})}
intents={{
INTENT_PRODUCT_SEARCH: {
onResponse: (args) => {
const products = args.products as Product[] | undefined;
if (!products?.length) return;
return products.slice(0, 8).map((p) => ({
key: p.sku,
label: p.name,
payload: { sku: p.sku, canonical: p.canonical },
}));
},
onOptionClick: (option, { sendContext, sendMessage }) => {
sendContext(option.payload ?? {});
sendMessage(option.label);
},
},
}}
onToolCall={(name, args) => {
if (name === 'INTENT_VIEW_CART') openCartDrawer(args.cart_token);
}}
/>
</div>
)}
</>
);
}Bidirectional Communication
Tool components có thể push context ngược về AI sau khi user thực hiện action — không cần user gõ lại thông tin.
import { useAssistantContext } from 'teko-assistant-ui';
function ProductDetailCard({ args }: { args: Record<string, unknown> }) {
const { sendContext } = useAssistantContext();
const handleAddToCart = () => {
addToCart(args.sku as string);
sendContext({ type: 'cart_updated', items: cartItems });
};
return <button onClick={handleAddToCart}>Thêm vào giỏ</button>;
}
<AssistantPanel
tools={{ INTENT_PRODUCT_DETAIL: ProductDetailCard }}
// ...
/>useAssistantContext() trả về { sendContext, sendMessage }.
API
<AssistantPanel> Props
Bắt buộc:
| Prop | Type | Mô tả |
| --------------- | -------------------------------------------- | --------------------- |
| appId | string | App identifier |
| chatBffUrl | string | BFF endpoint URL |
| transportMode | 'sse' \| 'stream' \| 'websocket' \| 'rest' | Giao thức kết nối BFF |
Intent & tools:
| Prop | Type | Mô tả |
| ------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| intents | Record<string, IntentConfig> | Handler cho từng tool call — SDK gọi khi BFF trả tool_calls |
| tools | Record<string, ToolUIComponent> | Render React component inline khi message có toolCall |
| onToolCall | (name: string, args: Record<string, unknown>) => void | Nhận mọi tool call — dùng để mở drawer, navigate, v.v. |
| getRequestHeaders | () => Record<string, string> \| Promise<...> | Headers đính kèm mỗi request. SDK tự thêm x-session-id và x-user-id — host app cung cấp auth/context |
IntentConfig
interface IntentConfig {
onResponse: (
args: Record<string, unknown>,
ctx: { sendContext: (data: Record<string, unknown>) => void; sendMessage: (text: string) => void },
) => SuggestOption[] | void | Promise<SuggestOption[] | void>;
onOptionClick?: (
option: SuggestOption,
ctx: { sendContext: (data: Record<string, unknown>) => void; sendMessage: (text: string) => void },
) => void;
}History:
| Prop | Type | Default | Mô tả |
| ----------------- | --------- | ------- | ------------------------------------------------------------------------------------------------- |
| persistHistory | boolean | false | Lưu lịch sử chat vào localStorage. Reload trang vẫn giữ lại messages. Scroll lên để load thêm cũ. |
| historyPageSize | number | 10 | Số messages load mỗi trang (khi mount và khi scroll lên). |
UI:
| Prop | Type | Default | Mô tả |
| ---------------- | ------------------------- | ------------------------------ | ------------------------------------------------------ |
| primaryColor | string | '#1a73e8' | Primary color (hex) |
| locale | 'vi' \| 'en' | 'vi' | Ngôn ngữ hiển thị |
| loadingPhrases | string[] | ['Đang soạn câu trả lời...'] | Text hiển thị sau 2s khi đang chờ. Truyền [] để tắt. |
| labels | Partial<ChatLabels> | — | Override bất kỳ UI label nào |
| onDebugEvent | (e: DebugEvent) => void | — | Dev only — nhận debug events từ SDK |
Unread badge:
| Prop | Type | Mô tả |
| --------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| isOpen | boolean | Trạng thái hiển thị của panel. Khi true và tab đang active, tin nhắn mới không được đếm là unread và count tự reset về 0. |
| onUnreadCountChange | (count: number) => void | Fires khi số unread messages thay đổi. Count tự reset khi isOpen=true + tab active, hoặc khi user quay lại tab với panel đang open. |
const [chatOpen, setChatOpen] = useState(false);
const [unreadCount, setUnreadCount] = useState(0);
<AssistantPanel
isOpen={chatOpen}
onUnreadCountChange={setUnreadCount}
...
/>
{/* Trigger button — hiển thị badge khi panel đóng */}
{!chatOpen && (
<button onClick={() => setChatOpen(true)}>
Chat với AI
{unreadCount > 0 && (
<span>{unreadCount > 9 ? '9+' : unreadCount}</span>
)}
</button>
)}AssistantPanelRef Methods
| Method | Signature | Mô tả |
| ------------- | ----------------------------------------- | ---------------------------------------------------------------- |
| sendMessage | (text: string) => void | Gửi message programmatically |
| sendContext | (data: Record<string, unknown>) => void | Lưu context — đính vào system message của request tiếp theo |
| markAllRead | () => void | Đánh dấu tất cả messages đã đọc — gọi khi host app mở chat panel |
BFF Protocol
BFF cần trả OpenAI Chat Completions streaming format — từng chunk là một delta JSON object, tương thích với OpenAI API (choices[].delta.content). SDK parse và hiển thị text real-time khi bytes đến.
| transportMode | Giao thức | Format |
| --------------- | --------- | ------------------------------------------------------------------------------------------------- |
| 'sse' | HTTP | SSE framing chuẩn: data: {...}\n\n, kết thúc bằng data: [DONE]\n\n — default, recommended |
| 'stream' | HTTP | NDJSON: mỗi dòng là một delta JSON, kết thúc bằng [DONE] |
| 'websocket' | WebSocket | Mỗi WS message là một delta JSON object; không support custom headers |
| 'rest' | HTTP | Full response JSON một lần — không có streaming |
Contributing
See DEVELOPMENT.md for dev setup, Playground, project structure, and release process.
