npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

Live Demo · NPM · GitLab

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 Services

Cài đặt

yarn add teko-assistant-ui
# peer deps
yarn add react react-dom

Usage

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-idx-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\ndefault, 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.