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.14

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',
  autoOpen: false,
  autoOpenMinWidth: 640,
  theme: {
    overview: {
      title: 'Anakie Assistant',
    },
    placeholder: 'Tell us about your project...',
    floatingButton: {
      // Omit these to use responsive defaults:
      // desktop: 440x610, right/bottom 24px
      // mobile <= 640px: calc(100vw - 24px)x620, right/bottom 12px
      size: 44,
      animation: 'pulse ping',
      animationStrength: 'normal',
      animationPingColor: 'light',
      animationStopAfterClose: true,
    },
  },
};

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. Set autoOpen: true to open the floating chat panel automatically when the widget first mounts. Use autoOpenMinWidth to restrict auto-open to screens at least that wide; it is only checked when autoOpen is true, and smaller screens stay closed. autoOpenMinWidth accepts a number such as 640 or a pixel string such as '640px'.


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',
        autoOpen: false,
        autoOpenMinWidth: 640,
        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',
    'autoOpen' => false,
    'autoOpenMinWidth' => 640,
    '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
  autoOpen?: boolean; // Open ChatWidget automatically on mount (default: false)
  autoOpenMinWidth?: number | string; // Only auto-open when viewport width is at least this many px
  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
  onProductClick?: (product: ProductType, context: ProductClickContext) => void; // Called when a product list row is clicked

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

  // Optional — UI appearance
  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.


theme.floatingButton

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

interface FloatingButtonConfig {
  // Trigger button
  position?: {
    desktop?: { top?: string; right?: string; bottom?: string; left?: string };
    mobile?: { top?: string; right?: string; bottom?: string; left?: string };
  }; // Default: desktop right/bottom 24px, mobile right/bottom 12px
  size?: number | { desktop?: number; mobile?: 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
  animation?: 'vibrate' | 'pulse' | 'ping' | 'vibrate ping' | 'pulse ping'; // Button animation (default: 'vibrate')
  animationStrength?: 'low' | 'normal' | 'strong'; // Button animation strength (default: 'normal')
  animationPingColor?: 'light' | 'dark'; // Ping ring color when animation includes ping (default: 'light')
  animationStopAfterMs?: number; // Stop animating after this many ms. Leave unset to keep animating until opened.
  animationStopAfterClose?: boolean; // Stop animating after the chat panel is closed (default: false)

  // Chat panel
  panelPosition?: {
    desktop?: { top?: string; right?: string; bottom?: string; left?: string };
    mobile?: { top?: string; right?: string; bottom?: string; left?: string };
  }; // Default: desktop right/bottom 24px, mobile right/bottom 12px
  panelSize?: {
    desktop?: { width?: string | number; height?: string | number };
    mobile?: { width?: string | number; height?: string | number };
  }; // Default: desktop 440x610px, mobile calc(100vw - 24px)x620px

  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;
    padding?: string; // Header padding CSS shorthand
    height?: string | number; // Header height
  };

  input?: {
    backgroundColor?: string; // Composer input background color
    padding?: string; // Footer/composer padding CSS shorthand
    height?: string | number; // Footer/composer height
    switchToTextarea?: boolean; // Switch from input to textarea on newline/overflow (default: false)
    showImageUploadButton?: boolean; // Show the image upload button
    submitButton?: {
      size?: string | number; // Submit button width/height
      borderRadius?: string; // Submit button corner radius
      icon?: ReactNode | string; // React element or image URL
    };
  };

  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)
  };

  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')
  };

  floatingButton?: FloatingButtonConfig;

  bot?: {
    name?: string; // Bot display name
    avatar?: string; // Bot avatar URL
  };

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

  scrollToTop?: {
    showButton?: boolean; // Show/hide the scroll-to-top button (default: true)
    size?: string | number; // Button size (default: 32)
    border?: string; // Button border shorthand (default: '1px solid #000000')
    borderRadius?: string; // Button radius (default: '100%')
    backgroundColor?: string; // Button background (default: '#000000')
    icon?: string; // Optional image URL. Uses the default ArrowUp icon when unset.
    iconColor?: string; // Default icon color (default: '#ffffff')
    endRefSize?: {
      width?: string | number; // Bottom spacer width (default: '1rem')
      height?: string | number; // Bottom spacer height (default: '1rem')
    };
  };

  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',
  autoOpen: false,
  autoOpenMinWidth: 640,
  showNewChatButton: true,
  onNewChat: (newChatId) => console.log('New chat started:', newChatId),
  onProductClick: (product, context) => console.log('Product clicked:', product, context),
  nocodb: {
    apiUrl: 'https://db.example.com',
    apiToken: 'your-noco-token',
    baseId: 'your-base-id',
    tableId: 'your-table-id',
    enabled: true,
  },
  theme: {
    fontFamily: 'inherit',
    backgroundColor: '#ffffff',
    welcomeMessage: 'Hi! How can I help?',
    placeholder: 'Tell us about your project...',
    floatingButton: {
      position: {
        desktop: { right: '24px', bottom: '24px' },
        mobile: { right: '12px', bottom: '12px' },
      },
      size: {
        desktop: 44,
        mobile: 44,
      },
      backgroundColor: '#0D0D0D',
      border: '1px solid #ffffff2b',
      borderRadius: '50%',
      animation: 'pulse ping',
      animationStrength: 'normal',
      animationPingColor: 'light',
      animationStopAfterMs: 10000,
      animationStopAfterClose: true,
      panelPosition: {
        desktop: { right: '24px', bottom: '24px' },
        mobile: { right: '12px', bottom: '12px' },
      },
      panelSize: {
        desktop: { width: 440, height: 610 },
        mobile: { width: 'calc(100vw - 24px)', height: 620 },
      },
      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',
    },
    header: {
      backgroundColor: '#0D0D0D',
      borderBottom: 'none',
      textColor: '#ffffff',
      fontSize: '1rem',
      fontWeight: 700,
      lineHeight: '1.5rem',
      padding: '1rem 1.125rem 1rem 1.25rem',
    },
    input: {
      backgroundColor: '#ffffff',
      padding: '1rem 1.5rem',
      switchToTextarea: false,
      showImageUploadButton: true,
      submitButton: {
        size: 40,
        borderRadius: '6px',
        icon: 'https://example.com/send.svg',
      },
    },
    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.',
    },
    scrollToTop: {
      showButton: true,
      size: 32,
      border: '1px solid #000000',
      borderRadius: '100%',
      backgroundColor: '#000000',
      icon: 'https://example.com/arrow-up.svg',
      iconColor: '#ffffff',
      endRefSize: {
        width: '1rem',
        height: '1rem',
      },
    },
    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.


Decoration style iframe

When an assistant message includes the render_decoration_style used tool, the widget renders an embedded iframe inside the chat, directly below that assistant message.

{
  "role": "apiMessage",
  "content": "Here is the render preview.",
  "usedTools": [
    {
      "tool": "render_decoration_style",
      "toolInput": {},
      "toolOutput": "https://example.com/render-preview"
    }
  ]
}

The iframe uses the URL returned in toolOutput. Plain URL strings are supported, and JSON string outputs with a top-level url field are also accepted. If the assistant message content is the same URL as the iframe source, the widget hides that message text and only shows the embedded preview. The embedded preview includes a fullscreen control that expands the iframe preview to the browser viewport.


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