Widget builder
The embed build carries no React and no dependencies: under 10 KB gzip, its own shadow root, and the chat itself opens in an iframe on the address you give it. Pick the options below, copy the snippet and paste it before </body>.
Builder
Where the widget is shown
Button
Icon
Placement
Corner
Opening
0 keeps it closed
Animation
Second face of the button
Plays on the closed button; with an avatar it alternates the two faces
Actions
9 max
Code to paste
<script src="https://cdn.jsdelivr.net/npm/@sinups/ai-kit/dist/embed/widget.js" defer></script>
<script>
window.addEventListener("load", () => {
window.aiKitChat = AiKitChat({
"url": "https://chat.example.com/widget",
"position": "bottom-right",
"offset": 30,
"color": "rgb(17, 17, 17)",
"size": "medium",
"icon": "chat",
"title": "Assistant",
"iconAnimation": "coin",
"pulse": true,
"actions": [
{
"id": "chat",
"label": "Ответим сразу же",
"icon": "<svg fill=\"currentColor\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M1.53467 17.8613C0.561333 16.128 0 14.1307 0 12C0 5.372 5.372 0 12 0C18.628 0 24 5.372 24 12C24 18.628 18.628 24 12 24C9.86933 24 7.872 23.4387 6.13867 22.4653L0 24L1.53467 17.8613Z\" fill=\"currentColor\"/></svg>",
"opensChat": true
},
{
"id": "callback",
"label": "Перезвоним через 10 секунд",
"icon": "<svg fill=\"currentColor\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M10.3416 13.659C8.64498 11.9625 7.36597 10.0629 6.51619 8.1358C6.33638 7.72834 6.44224 7.25128 6.75691 6.93662L7.94457 5.75049C8.9176 4.77751 8.9176 3.40143 8.06783 2.5517L6.36538 0.84936C5.23283 -0.28312 3.39697 -0.28312 2.26442 0.84936L1.31894 1.79479C0.244399 2.86926 -0.20369 4.41935 0.0863353 5.95639C0.802697 9.74534 3.00399 13.8939 6.55535 17.445C10.1067 20.9962 14.2555 23.1974 18.0447 23.9137C19.5818 24.2037 21.132 23.7556 22.2066 22.6811L23.1506 21.7372C24.2831 20.6047 24.2831 18.7689 23.1506 17.6365L21.4496 15.9356C20.5998 15.0858 19.2222 15.0858 18.3739 15.9356L17.0644 17.2464C16.7497 17.5611 16.2726 17.6669 15.8652 17.4871C13.9379 16.6359 12.0383 15.3555 10.3416 13.659Z\" fill=\"currentColor\"/></svg>",
"href": "tel:+10000000"
}
]
});
});
</script>Preview
The panel opens empty here: the chat address is only used on your own page
Brand marks in the presets belong to their owners; the package itself ships none.
Install
Everything can be written straight into the tag: a data-* attribute per option, with data-actions and data-labels taking JSON. No JavaScript is needed.
<script src="https://cdn.jsdelivr.net/npm/@sinups/ai-kit/dist/embed/widget.js" defer
data-url="https://chat.example.com/widget"
data-title="Assistant"
data-color="#0a84ff"
data-size="medium"
data-position="bottom-right"
data-offset="30"
data-pulse="true"
data-open-after="15000"
data-devices="all"
data-actions='[
{"id":"chat","label":"Chat with us","opensChat":true},
{"id":"call","label":"Call me back","href":"tel:+10000000"},
{"id":"telegram","label":"Telegram","href":"https://t.me/example","color":"#2aabee"}
]'></script>The same thing as a call, when the page wants to keep the widget and drive it later.AiKitChat returns the widget; the auto-started one also lands on window.aiKitChat.
<script src="https://cdn.jsdelivr.net/npm/@sinups/ai-kit/dist/embed/widget.js" defer></script>
<script>
window.addEventListener("load", () => {
window.chat = AiKitChat({
url: "https://chat.example.com/widget",
title: "Assistant",
color: "#0a84ff",
location: ["bottom", "right"],
pulse: true,
openAfter: 15000,
labels: { open: "Открыть чат", actions: "Как с нами связаться" },
actions: [
{ id: "chat", label: "Чат с ассистентом", opensChat: true },
{ id: "call", label: "Перезвоним за 10 секунд", href: "tel:+10000000" },
],
});
});
</script>A remote JSON is for those who want to change the settings without touching the page — an admin panel, for example. It is optional: data-config-url (or data-app-id with data-config-endpoint) is fetched and merged, and what the tag sets stays above it.
<script src="https://cdn.jsdelivr.net/npm/@sinups/ai-kit/dist/embed/widget.js" defer
data-url="https://chat.example.com/widget"
data-config-url="https://cdn.example.com/widget.json"></script>In a bundler the same widget comes from @sinups/ai-kit/embed, with the full control surface in return.
import { chatWidget } from "@sinups/ai-kit/embed";
const widget = chatWidget({
url: "https://chat.example.com/widget",
position: "bottom-right",
color: "rgb(10, 132, 255)",
openAfter: 15000,
actions: [
{ id: "chat", label: "Chat with us", opensChat: true },
{ id: "call", label: "Call me back", href: "tel:+10000000" },
],
});
widget.notify({
title: "Need a hand?",
text: "We answer in a minute and keep the thread in your inbox",
avatar: "https://example.com/ada.png",
timeout: false,
});Options
urlconfigUrlpositionbottom-rightoffset30locationcolorsizemediumiconchatavatariconAnimationnonepulsefalseringColoropenAfter0openAfterOnMobilefalseremembertruedevicesallmobileHeight100actionsactionsMotionsequencenotificationstrueindicatortruenotificationTimeout12000notificationLimit3themeautoiconColorpanelWidth380panelHeight640actionSizeactionGapmobileBreakpoint520zIndex2147483000labelscssdefertrueNotifications
A bubble takes a string or an object with title, content (or text), avatar and timeout. Up to notificationLimit of them stay on screen, newest next to the button; each has its own timer and its own hide. A click on the bubble opens the chat, the cross closes that bubble, and an avatar that fails to load is dropped instead of showing a broken image.
// short form
widget.notify("Hi! Need a hand with the order?");
// full form: title, avatar, its own lifetime
widget.notify({
title: "Anna from support",
text: "Delivery across the EU takes 3 to 5 working days. Shall I cover returns?",
avatar: "https://example.com/anna.png",
timeout: 15000, // ms; false keeps it until it is dismissed
});
// the call returns the hide of that one bubble
const { hide } = widget.notify({ text: "Stays until you say so", timeout: false });
setTimeout(hide, 3000);Ways pages use it, all built from the same few calls:
const chat = window.aiKitChat;
// a greeting after the visitor has read for a while
setTimeout(() => chat.notify({ title: "Anna from support", text: "Anything I can help with?" }), 20000);
// a hint when the visitor is about to leave
document.addEventListener("mouseleave", (event) => {
if (event.clientY <= 0) {
chat.notify({ text: "Leaving? I can send the quote by email", id: "exit", timeout: false });
}
}, { once: true });
// after the visitor scrolled through the pricing
const pricing = document.querySelector("#pricing");
new IntersectionObserver(([entry], observer) => {
if (entry.isIntersecting) {
chat.notify({ text: "Compare the plans with me?", id: "pricing" });
observer.disconnect();
}
}).observe(pricing);
// own button on the page instead of the launcher
document.querySelector("#ask").addEventListener("click", () => chat.open());
// a question about the current page
document.querySelector("#ask-about-order").addEventListener("click", () => {
chat.navigate("https://chat.example.com/widget?order=A-1024");
});
// count of unanswered replies, kept by the page
chat.unread(3);
chat.on("open", () => chat.unread(0));
// what the visitor picked in the fan
chat.on("action", (id) => analytics.track("widget_action", { id }));
// quieter on a landing page, louder in the account
chat.setOptions({ pulse: false, openAfter: 0 });
chat.options.color = "#0a84ff";
// take it off a page where it does not belong
if (location.pathname.startsWith("/checkout")) {
chat.hide();
}Control from the page
open,close,toggle— the panel.notify— a bubble above the closed button with a title, an avatar and its own lifetime;timeout: falsekeeps it until it is dismissed, and the call returnshidefor that one bubble. The text takes**bold**,`code`and[links](https://example.com); everything else is escaped.clearNotifications— takes every bubble off the screen.unread— the count on the button, cleared when the chat opens.setOptions— new options at any time; the liveoptionsobject takes a single field, as inwidget.options.color = "#000".hideandshow— take the widget off the page and bring it back.navigate— open the panel on another address.on— events of the widget (open,close,action,message,ready);send— a message into the chat page.destroy— remove the widget with its listeners and timers.
Messages with the chat
The page in the panel talks to the widget through postMessage. A message is taken only when it comes from the frame of the widget itself and from the origin of url, so neither another tab nor another frame of the same site can close the widget or push a notification into it. Send with the origin of the host page, not "*".
// inside the chat page, in the iframe
const host = "https://shop.example.com"; // the page the widget runs on
parent.postMessage({ source: "ai-kit-chat", type: "unread", count: 3 }, host);
parent.postMessage({ source: "ai-kit-chat", type: "notify", title: "New reply", text: "Your ticket is answered" }, host);
parent.postMessage({ source: "ai-kit-chat", type: "close" }, host);
// on the page around it
widget.on("action", (id) => analytics.track("widget_action", { id }));
widget.send("locale", { locale: "ru" });