@vani-ai/chat-ui
v0.1.7
Published
Unified SDK for Vani AI Chat UI (core, react, and vanilla) with subpath exports.
Readme
@vani-ai/chat-ui
Embeddable chat UI for Vani AI. Drop it into React, plain HTML, or any framework — one config object, fully customizable.
Packages
| Package | Description |
|---------|-------------|
| @vani-ai/chat-ui | Unified package (core + react + vanilla via subpath exports) |
| @vani-ai/chat-ui-core | Headless client, types, and utilities |
| @vani-ai/chat-ui-react | React component |
| @vani-ai/chat-ui-vanilla | Vanilla JS implementation |
Installation
Unified package (recommended):
npm install @vani-ai/chat-ui
# or
yarn add @vani-ai/chat-uiIndividual packages:
npm install @vani-ai/chat-ui-react # React
npm install @vani-ai/chat-ui-vanilla # Vanilla JS
npm install @vani-ai/chat-ui-core # Headless / core onlyQuick Start
React
import { VaniChat } from '@vani-ai/chat-ui/react';
import '@vani-ai/chat-ui/react/styles.css';
function App() {
return (
<VaniChat
agentId="your-agent-id"
publicApiKey="your-public-key"
baseUrl="http://localhost:4000/api/v1/"
mode="floating"
position="bottom-right"
/>
);
}Vanilla JavaScript
<script type="module">
import { VaniAIChat } from '@vani-ai/chat-ui/vanilla';
VaniAIChat.init({
agentId: 'your-agent-id',
publicApiKey: 'your-public-key',
baseUrl: 'http://localhost:4000/api/v1/',
mode: 'floating',
position: 'bottom-right',
});
</script>To mount inside a specific element instead of the page body:
VaniAIChat.init({
agentId: 'your-agent-id',
publicApiKey: 'your-public-key',
baseUrl: 'http://localhost:4000/api/v1/',
mode: 'embedded',
container: '#chat-container', // CSS selector or HTMLElement
});To tear down:
VaniAIChat.destroy();Core Client (Headless)
Use this when you want to drive your own UI:
import { VaniChatClient } from '@vani-ai/chat-ui';
const client = new VaniChatClient({
agentId: 'your-agent-id',
publicApiKey: 'your-public-key',
baseUrl: 'http://localhost:4000/api/v1/',
});
await client.ready();
// Streaming (SSE)
await client.streamMessage({
message: 'Hello!',
onDelta: (delta) => process.stdout.write(delta),
onDone: (response) => console.log('Done:', response),
onError: (err) => console.error(err),
});
// Non-streaming
const response = await client.sendMessage('Hello!');Getting Your Credentials
| Value | Where to find it |
|-------|-----------------|
| agentId | Vani AI Admin Portal → your agent → Settings |
| publicApiKey | Vani AI Admin Portal → API Keys → Public Key |
| baseUrl | Your backend API root, e.g. http://localhost:4000/api/v1/ |https://dev.vani-ai.com/api/v1/
Configuration Reference
All options are optional. The three identity fields (agentId, publicApiKey, baseUrl) are required for actual API calls.
interface VaniChatConfig {
// ── Identity ─────────────────────────────────────────────────────────────
agentId?: string;
publicApiKey?: string;
baseUrl?: string; // e.g. "https://api.your-domain.com"
// ── Layout ───────────────────────────────────────────────────────────────
mode?: 'floating' | 'embedded' | 'inline' | 'popup'; // default: "floating"
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'; // default: "bottom-right"
direction?: 'ltr' | 'rtl';
locale?: string;
// ── Theme ────────────────────────────────────────────────────────────────
theme?: {
mode?: 'light' | 'dark' | 'system'; // default: "system"
colors?: {
primary?: string;
secondary?: string;
background?: string;
surface?: string;
text?: string;
mutedText?: string;
border?: string;
userBubble?: string;
userBubbleText?: string;
assistantBubble?: string;
assistantBubbleText?: string;
inputBackground?: string;
inputBorder?: string;
headerBackground?: string;
headerText?: string;
};
fonts?: {
body?: string;
mono?: string;
};
fontFamily?: string;
fontSize?: string;
radius?: string; // e.g. "12px"
spacing?: string;
shadows?: {
widget?: string;
launcher?: string;
};
};
// ── Launcher (floating button) ───────────────────────────────────────────
launcher?: {
iconUrl?: string; // Image shown on the FAB button
label?: string;
size?: number; // Diameter in px (default 60)
backgroundColor?: string;
pulse?: boolean; // Animated ring
badge?: number | string;
draggable?: boolean; // Allow dragging the button
rememberPosition?: boolean; // Persist dragged position in localStorage
enabled?: boolean;
};
// ── Chat window ──────────────────────────────────────────────────────────
widget?: {
width?: string | number; // Initial width (px or CSS string)
height?: string | number; // Initial height
minWidth?: string | number; // Default 280
maxWidth?: string | number;
minHeight?: string | number; // Default 400
maxHeight?: string | number;
borderRadius?: string;
shadow?: string;
backdropBlur?: boolean;
glass?: boolean;
resizable?: boolean; // Enable drag-to-resize corner handle
fullscreenBreakpoint?: number; // px — below this, use fullscreen sheet
};
// ── Header ───────────────────────────────────────────────────────────────
header?: {
title?: string;
subtitle?: string;
logoUrl?: string; // Brand logo shown on the left
avatarUrl?: string; // Overrides logoUrl when provided
showAvatar?: boolean;
showStatus?: boolean;
showClose?: boolean;
showMinimize?: boolean;
showExpand?: boolean;
};
// ── Behavior ─────────────────────────────────────────────────────────────
behavior?: {
openOnLoad?: boolean;
autoFocus?: boolean;
rememberState?: boolean; // Persist open/closed in localStorage
preserveConversation?: boolean;
destroyOnClose?: boolean;
closeOnEscape?: boolean;
outsideClickClose?: boolean;
};
// ── Localization ─────────────────────────────────────────────────────────
messages?: {
title?: string;
subtitle?: string;
welcomeMessage?: string;
placeholder?: string;
launcherLabel?: string;
sendLabel?: string;
closeLabel?: string;
minimizeLabel?: string;
expandLabel?: string;
retryLabel?: string;
typingLabel?: string;
copyLabel?: string;
offlineMessage?: string;
connectionError?: string;
};
// ── Suggestions ──────────────────────────────────────────────────────────
suggestions?: string[]; // Starter prompts shown before first message
// ── Misc ─────────────────────────────────────────────────────────────────
storageKey?: string; // localStorage key prefix (default "vc")
enable?: boolean; // Disable the widget entirely (default true)
}Events
All events are optional callbacks on the config object:
{
onReady?: () => void;
onOpen?: () => void;
onClose?: () => void;
onExpand?: () => void;
onMinimize?: () => void;
onMessage?: (message: VaniChatMessage) => void;
onTyping?: (text: string) => void;
onError?: (error: Error) => void;
onDrag?: (position: { x: number; y: number }) => void;
onConversationStarted?: (conversationId: string) => void;
onConversationEnded?: (conversationId: string) => void;
onReconnect?: () => void;
onSessionExpired?: () => void;
onThemeChanged?: (mode: 'light' | 'dark') => void;
}Common Recipes
Custom branding
<VaniChat
agentId="your-agent-id"
publicApiKey="your-public-key"
baseUrl="https://api.your-domain.com"
launcher={{ iconUrl: '/icons/chat.png' }}
header={{
logoUrl: '/brand/logo.png',
title: 'Support',
subtitle: 'Replies in seconds',
}}
theme={{
mode: 'light',
colors: { primary: '#6366f1' },
radius: '16px',
}}
/>Dark mode
<VaniChat
agentId="your-agent-id"
publicApiKey="your-public-key"
baseUrl="https://api.your-domain.com"
theme={{ mode: 'dark' }}
/>Embedded (inline) mode
<VaniChat
agentId="your-agent-id"
publicApiKey="your-public-key"
baseUrl="https://api.your-domain.com"
mode="embedded"
/>Resizable widget + draggable launcher
<VaniChat
agentId="your-agent-id"
publicApiKey="your-public-key"
baseUrl="https://api.your-domain.com"
launcher={{ draggable: true, rememberPosition: true }}
widget={{ resizable: true, minWidth: 320, minHeight: 450 }}
/>Starter suggestions
<VaniChat
agentId="your-agent-id"
publicApiKey="your-public-key"
baseUrl="https://api.your-domain.com"
suggestions={['How do I reset my password?', 'Show me pricing', 'Talk to a human']}
/>Auto-open + remember state
<VaniChat
agentId="your-agent-id"
publicApiKey="your-public-key"
baseUrl="https://api.your-domain.com"
behavior={{ openOnLoad: true, rememberState: true }}
/>White-Label / Powered-by Footer
The "Powered by" footer is controlled server-side via your Vani AI Admin Portal. No SDK config is needed. The SDK fetches branding from GET /api/v1/sdk/bootstrap on mount, caches it for 60 minutes (memory + localStorage), and shows or hides the footer automatically.
To force a cache refresh from your app code:
import { BootstrapService } from '@vani-ai/chat-ui';
BootstrapService.clearCache('your-agent-id', 'your-public-key');Project Structure
This is a Yarn workspaces monorepo:
packages/
core/ — @vani-ai/chat-ui-core (types, client, theme, bootstrap)
react/ — @vani-ai/chat-ui-react (VaniChat component + styles.css)
vanilla/ — @vani-ai/chat-ui-vanilla (VaniAIChat, injects CSS inline)Development
yarn install
yarn build # build all packages + unified dist
yarn build:packages # compile TypeScript only
yarn build:sdk # assemble unified dist from compiled packages
yarn clean # remove dist
node test-sdk.mjs # smoke-test core + vanilla importsLicense
MIT — see LICENSE.
