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

@clikvn/agent-widget-lite

v0.6.2

Published

Lightweight chatbot widget

Readme

@clikvn/agent-widget-lite

Lightweight AI chatbot widget for React apps and plain HTML pages. Connects to the Clik AI API with SSE streaming, agent reasoning steps, contact capture form, and a fully themeable UI.

Installation

npm install @clikvn/agent-widget-lite

Peer dependencies (React apps only):

npm install react react-dom

Usage

1. React — Floating widget (button + chat panel)

import { ChatWidget } from '@clikvn/agent-widget-lite';
import type { WidgetConfig } from '@clikvn/agent-widget-lite';

const config: WidgetConfig = {
  apiHost: 'https://api.clik.vn/chatbot',
  agentId: 'your-agent-id',
  theme: {
    overview: {
      title: 'Anakie Assistant',
    },
    placeholder: 'Tell us about your project...',
  },
  floatingButton: {
    bottom: '24px',
    right: '24px',
    size: 44,
    panelWidth: 440,
    panelHeight: 610,
  },
};

export default function App() {
  return (
    <>
      {/* your app */}
      <ChatWidget config={config} />
    </>
  );
}

ChatWidget renders a fixed floating button (default: bottom-right 24px). Clicking it opens/closes the chat panel.


2. React — Embedded chat panel (no floating button)

Use ChatWindow directly to embed the panel inside your own layout:

import { ChatWindow } from '@clikvn/agent-widget-lite';
import type { WidgetConfig } from '@clikvn/agent-widget-lite';

const config: WidgetConfig = {
  apiHost: 'https://api.clik.vn/chatbot',
  agentId: 'your-agent-id',
};

export default function Sidebar() {
  return (
    <div style={{ width: 390, height: '100vh' }}>
      <ChatWindow config={config} embedded />
    </div>
  );
}

ChatWindow props:

| Prop | Type | Description | | ---------- | -------------- | -------------------------------------------------------------------------- | | config | WidgetConfig | Required. Full configuration object. | | embedded | boolean | Fills container, hides close button. Use when the panel is always visible. | | onClose | () => void | Called when the user clicks the close button (non-embedded). |


3. Plain HTML / vanilla JS — Script tag

Include the stylesheet and browser bundle, then call ChatbotLite.initWidget():

<!doctype html>
<html>
  <head>
    <link
      rel="stylesheet"
      href="https://unpkg.com/@clikvn/agent-widget-lite/dist/web.css"
    />
    <script
      type="module"
      src="https://unpkg.com/@clikvn/agent-widget-lite/dist/web.js"
    ></script>
  </head>
  <body>
    <script type="module">
      import ChatbotLite from 'https://unpkg.com/@clikvn/agent-widget-lite/dist/web.js';

      ChatbotLite.initWidget({
        apiHost: 'https://api.clik.vn/chatbot',
        agentId: 'your-agent-id',
        theme: {
          welcomeMessage: 'Hi! How can I help?',
          placeholder: 'Tell us about your project...',
          fontFamily: 'inherit', // Leave unset to inherit the page/WordPress theme font
          backgroundColor: '#ffffff',
          header: {
            backgroundColor: '#0D0D0D',
            borderBottom: 'none',
            textColor: '#ffffff',
            fontSize: '1rem',
            fontWeight: 700,
            lineHeight: '1.5rem',
          },
          input: {
            backgroundColor: '#ffffff',
            switchToTextarea: false,
          },
          apiMessage: {
            textColor: '#171717',
            fontSize: '1rem',
            lineHeight: '1.45',
            showBubble: true,
            backgroundColor: '#F4F3F1',
            borderRadius: '14px',
            padding: '1rem 1.125rem',
          },
          userMessage: {
            textColor: '#ffffff',
            fontSize: '1rem',
            lineHeight: '1.45',
            showBubble: true,
            backgroundColor: '#111111',
            borderRadius: '14px 4px 14px 14px',
            padding: '1rem 1.125rem',
          },
          overview: {
            title: 'Anakie Assistant',
            description: 'Premium joinery guidance for new builds and renovations.',
          },
        },
      });
    </script>
  </body>
</html>

To unmount:

window.ChatbotLite.destroy();

The widget mounts into a <clik-chatbot-lite> element appended to <body>. Calling initWidget again with a new config re-renders in place.


4. WordPress

Copy dist/web.js and dist/web.css into your theme or plugin, then enqueue the stylesheet and initialize the module in the footer:

add_action('wp_enqueue_scripts', function () {
  wp_enqueue_style(
    'clik-chatbot-lite',
    plugin_dir_url(__FILE__) . 'assets/web.css',
    array(),
    '0.3.0'
  );
});

add_action('wp_footer', function () {
  $script_url = plugin_dir_url(__FILE__) . 'assets/web.js';
  $config = array(
    'apiHost' => 'https://api.clik.vn/chatbot',
    'agentId' => 'your-agent-id',
    'theme' => array(
      'overview' => array(
        'title' => 'Anakie Assistant',
      ),
      'placeholder' => 'Tell us about your project...',
    ),
  );
  ?>
  <script type="module">
    import ChatbotLite from <?php echo wp_json_encode(esc_url($script_url)); ?>;

    ChatbotLite.initWidget(<?php echo wp_json_encode($config); ?>);
  </script>
  <?php
}, 99);

Do not put private values such as webhookApiKey or a NocoDB API token directly in the browser config. For those, route requests through a WordPress server-side endpoint.


Configuration (WidgetConfig)

interface WidgetConfig {
  // Required
  apiHost: string; // Base URL of the Clik chatbot API
  agentId: string; // Agent ID from the Clik dashboard

  // Optional — API
  webhookUrl?: string; // Use a custom webhook URL instead of the default prediction endpoint
  webhookApiKey?: string; // Bearer token for the webhook
  brandAlias?: string; // Brand identifier passed to the API

  // Optional — Features
  showNewChatButton?: boolean; // Show a "New chat" menu button in the header (default: false)
  onNewChat?: (newChatId: string) => void; // Called after the user starts a new chat

  // Optional — Contact form (NocoDB)
  nocodb?: NocoDBConfig;

  // Optional — UI
  bot?: {
    name?: string; // Bot display name
    avatar?: string; // URL to the bot avatar image
  };
  theme?: Theme;
}

NocoDBConfig

When provided, the contact capture form submits to a NocoDB table.

interface NocoDBConfig {
  apiUrl: string; // NocoDB instance base URL, e.g. https://db.example.com
  apiToken: string; // NocoDB API token (xc-token)
  baseId: string; // NocoDB base ID
  tableId: string; // NocoDB table ID
  enabled?: boolean; // Set to false to disable submission (default: true)
}

Expected table columns: Social name, Name, Phone number, Email, SessionID, Platform, Main pain points, Location.


floatingButton

Controls the floating trigger button and the chat panel that opens from it (used by ChatWidget only).

interface FloatingButtonConfig {
  // Trigger button
  bottom?: string; // Distance from bottom of viewport (default: '24px')
  right?: string; // Distance from right of viewport (default: '24px')
  size?: number; // Button size in px (default: 44)
  backgroundColor?: string; // Button background color (default: '#0D0D0D')
  border?: string; // Button border shorthand (default: '1px solid #ffffff2b')
  borderRadius?: string; // Button corner radius (default: '50%')
  icon?: ReactNode | string; // React element or image URL for the button icon

  // Chat panel
  panelWidth?: number; // Panel width in px (default: 440)
  panelHeight?: number; // Panel height in px (default: 610)
  panelBottom?: string; // Panel bottom offset (default: same as button bottom)
  panelRight?: string; // Panel right offset (default: same as button right)
  panelBorderRadius?: string; // Panel corner radius (default: '12px')
  panelBoxShadow?: string; // Panel drop shadow (default: '0 8px 32px rgba(0,0,0,0.18)'). Set to 'none' to disable.
  panelBorder?: string; // Panel border shorthand (default: '1px solid #ffffff38')
}

theme

interface Theme {
  fontFamily?: string; // Whole-widget font. Leave unset to inherit the embedding page font.
  backgroundColor?: string; // Messages panel background (default: '#ffffff')
  welcomeMessage?: string; // Fallback header title when overview.title is not set
  placeholder?: string; // Input placeholder text

  header?: {
    backgroundColor?: string; // Header background color
    borderBottom?: string; // CSS border shorthand, e.g. '1px solid #e4e4e7'
    textColor?: string; // Header title text color
    fontSize?: string | number;
    fontWeight?: string | number;
    lineHeight?: string | number;
  };

  input?: {
    backgroundColor?: string; // Composer input background color
    switchToTextarea?: boolean; // Switch from input to textarea on newline/overflow (default: false)
  };

  apiMessage?: {
    avatarSrc?: string; // Override bot avatar URL. Supplying a URL shows the avatar unless showAvatar is false.
    backgroundColor?: string; // Bubble background color (default when bubble: '#F4F3F1')
    textColor?: string; // Text color
    fontSize?: string | number;
    fontWeight?: string | number;
    lineHeight?: string | number;
    showAvatar?: boolean; // Show/hide the avatar ring (default: true when avatarSrc is set, otherwise false)
    showBubble?: boolean; // false renders as text lines; true renders as a bubble (default: true)
    borderRadius?: string; // Bubble radius (default: '14px' when bubble)
    padding?: string; // Bubble padding (default: '1rem 1.125rem' when bubble)
  };

  // Legacy alias for apiMessage. apiMessage takes priority when both are provided.
  botMessage?: {
    avatarSrc?: string;
    backgroundColor?: string;
    textColor?: string;
    fontSize?: string | number;
    fontWeight?: string | number;
    lineHeight?: string | number;
    showAvatar?: boolean;
    showBubble?: boolean;
    borderRadius?: string;
    padding?: string;
  };

  userMessage?: {
    backgroundColor?: string; // Bubble background color (default: '#111111')
    textColor?: string; // Text color
    fontSize?: string | number;
    fontWeight?: string | number;
    lineHeight?: string | number;
    showBubble?: boolean; // true renders as bubble; false renders as text lines (default: true)
    borderRadius?: string; // Bubble radius (default: '14px 4px 14px 14px')
    padding?: string; // Bubble padding (default: '1rem 1.125rem')
  };

  overview?: {
    title?: string; // Heading in the header and empty-state overview panel
    description?: string; // Subtext in the overview panel
  };

  userContactForm?: {
    title?: string; // Form heading (default: "Your contact")
    labelName?: string; // Name field label
    labelEmail?: string; // Email field label
    labelContact?: string; // Phone field label
    labelAddress?: string; // Address field label
    labelPurpose?: string; // Purpose field label
    messages?: {
      nameError?: string;
      invalidContact?: string;
      invalidEmail?: string;
      needFillContactOrEmail?: string;
      submitSuccess?: string;
      submitError?: string;
    };
  };

  buttons?: {
    textBtnSubmit?: string; // Submit button label (default: "Submit")
    textBtnCancel?: string; // Cancel button label (default: "Cancel")
  };
}

Full example config

const config: WidgetConfig = {
  apiHost: 'https://api.clik.vn/chatbot',
  agentId: 'your-agent-id',
  showNewChatButton: true,
  onNewChat: (newChatId) => console.log('New chat started:', newChatId),
  nocodb: {
    apiUrl: 'https://db.example.com',
    apiToken: 'your-noco-token',
    baseId: 'your-base-id',
    tableId: 'your-table-id',
    enabled: true,
  },
  floatingButton: {
    bottom: '24px',
    right: '24px',
    size: 44,
    backgroundColor: '#0D0D0D',
    border: '1px solid #ffffff2b',
    borderRadius: '50%',
    panelWidth: 440,
    panelHeight: 610,
    panelBorderRadius: '12px',
    panelBoxShadow: '0 8px 32px rgba(0,0,0,0.18)',
    panelBorder: '1px solid #ffffff38',
  },
  bot: {
    name: 'Aria',
    avatar: 'https://example.com/bot-avatar.png',
  },
  theme: {
    fontFamily: 'inherit',
    backgroundColor: '#ffffff',
    welcomeMessage: 'Hi! How can I help?',
    placeholder: 'Tell us about your project...',
    header: {
      backgroundColor: '#0D0D0D',
      borderBottom: 'none',
      textColor: '#ffffff',
      fontSize: '1rem',
      fontWeight: 700,
      lineHeight: '1.5rem',
    },
    input: {
      backgroundColor: '#ffffff',
      switchToTextarea: false,
    },
    apiMessage: {
      avatarSrc: 'https://example.com/bot-avatar.png',
      backgroundColor: '#F4F3F1',
      textColor: '#171717',
      fontSize: '1rem',
      lineHeight: '1.45',
      showAvatar: true,
      showBubble: true,
      borderRadius: '14px',
      padding: '1rem 1.125rem',
    },
    userMessage: {
      backgroundColor: '#111111',
      textColor: '#FFFFFF',
      fontSize: '1rem',
      lineHeight: '1.45',
      showBubble: true,
      borderRadius: '14px 4px 14px 14px',
      padding: '1rem 1.125rem',
    },
    overview: {
      title: 'Anakie Assistant',
      description: 'Premium joinery guidance for new builds and renovations.',
    },
    userContactForm: {
      title: 'Leave your contact',
      labelName: 'Name',
      labelEmail: 'Email',
      labelContact: 'Phone',
      labelAddress: 'Address',
      labelPurpose: 'Purpose',
      messages: {
        submitSuccess: 'Thanks! We will be in touch.',
        submitError: 'Something went wrong. Please try again.',
      },
    },
    buttons: {
      textBtnSubmit: 'Submit',
      textBtnCancel: 'Cancel',
    },
  },
};

Contact capture form

When the agent invokes the display_contact_form tool, a contact form appears below the assistant message. It collects name (required), email, phone, address, and purpose.

Submit flow:

  1. On submit the widget sends a JSON event message to the chat:
    { "type": "application_event", "event": "contact_form_submitted", "tool": "save_contact_form", "name": "...", ... }
  2. If nocodb.enabled is true, the widget also submits the contact data to the configured NocoDB table.
  3. The agent is expected to respond with a save_contact_form tool call whose output contains { "status": "succeeded" | "failed" }.
  4. If status is "succeeded", the widget marks the contact as submitted and replaces the hidden event message with a success confirmation panel. This state persists across page refreshes.
  5. If status is not "succeeded", the contact state reverts to cancelled.

State machine: unknown → displayed → submitted | cancelled (submitted → cancelled on failed save). Persisted in localStorage keyed by agentId + chatId. Starting a new chat clears the previous session's contact state.

The current contact status and purpose are injected into every chat request as contact_status and contact_purpose fields, so the agent can adjust its behaviour accordingly.


Chat history & session persistence

The widget automatically persists the chatId in localStorage (key: clik_chat_${agentId}) and fetches the conversation history on mount via:

GET {apiHost}/ext/v1/chats/chatmessage/{chatId}

Refreshing the page continues the previous conversation. Starting a new chat (via showNewChatButton) generates a fresh chatId.


Agent reasoning

The widget automatically displays agent thinking steps (tool calls, sub-agent flows) as a collapsible "Thought for N seconds" panel above each assistant message.


Local development

# Install dependencies
npm install

# Build (both NPM package and web bundle)
npm run build

# Watch mode
npm run dev

Running the example app

cd example && npm install && npm run dev

The example app starts a Vite dev server with a live config editor on the left and the chat panel on the right. The config form covers common chat fields, NocoDB credentials, feature toggles, and floating widget appearance; the default config also demonstrates the available theme styling options. Toggle "Floating widget" to switch between embedded and floating modes.


Exports

// NPM package entry (dist/index.js) — use in React apps
import { ChatWidget, ChatWindow } from '@clikvn/agent-widget-lite';
import type {
  WidgetConfig,
  Message,
  UsedTool,
  StreamEvent,
} from '@clikvn/agent-widget-lite';

// Web bundle (dist/web.js) — use via script tag
window.ChatbotLite.initWidget(config);
window.ChatbotLite.destroy();

// Web stylesheet (dist/web.css) - load next to dist/web.js

License

ISC © Clik JSC