@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-litePeer dependencies (React apps only):
npm install react react-domUsage
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:
- On submit the widget sends a JSON event message to the chat:
{ "type": "application_event", "event": "contact_form_submitted", "tool": "save_contact_form", "name": "...", ... } - If
nocodb.enabledistrue, the widget also submits the contact data to the configured NocoDB table. - The agent is expected to respond with a
save_contact_formtool call whose output contains{ "status": "succeeded" | "failed" }. - If
statusis"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. - If
statusis not"succeeded", the contact state reverts tocancelled.
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 devRunning the example app
cd example && npm install && npm run devThe 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.jsLicense
ISC © Clik JSC
