@embeddable.com/ai-chat
v0.1.6
Published
A framework-agnostic Web Component that renders a streaming AI chat widget and connects to an Embeddable orchestrator backend over a STOMP WebSocket.
Downloads
552
Maintainers
Keywords
Readme
@embeddable.com/ai-chat
A framework-agnostic Web Component that renders a streaming AI chat widget and connects to an Embeddable orchestrator backend over a STOMP WebSocket.
Built with Stencil — works in plain HTML, React, Vue, Angular, or any other framework.
Quick start
1. Configure your LLM
Before the ai-chat can respond, your workspace needs an LLM configured on the orchestrator backend. Send a POST to /api/v1/llm-config, authenticated with your Embeddable API key (Authorization: Bearer <api-key>). This is a one-time, per-workspace setup — the config is stored encrypted and reused for every chat session.
curl -X POST https://api.[us|eu].embeddable.com/api/v1/llm-config \
-H "Authorization: Bearer YOUR_EMBEDDABLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "anthropic",
"model": "claude-sonnet-4-5",
"apiKey": "sk-ant-...",
"temperature": 0.7,
"maxTokens": 2048,
"systemPrompt": "You are a helpful analytics assistant."
}'LLM config fields
| Field | Type | Required | Description |
| -------------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| provider | string | Yes | LLM provider ID. Must be one supported by the backend: anthropic, openai, google, openrouter, mistralai, bedrock, or ollama. An unsupported value returns 400. |
| model | string | Yes | Model ID for the chosen provider (e.g. claude-sonnet-4-5, claude-haiku-4-5). Must be a model the provider supports; an unsupported value returns 400 with the accepted set. |
| apiKey | string | Yes | Provider API key used to authenticate LLM calls. Stored encrypted and never returned in full — GET/POST responses mask it. |
| temperature | number | No | Sampling temperature. Higher values produce more varied output. Provider default is used when omitted. |
| maxTokens | integer | No | Maximum number of tokens to generate per response. Provider default is used when omitted. |
| systemPrompt | string | No | System prompt prepended to every conversation to steer tone and behavior. |
Retrieve the current config (with the API key masked) with GET /api/v1/llm-config. If none is set, it returns 404.
2. Mint a security token
Each user needs a security token, minted server-side with your Embeddable API key and passed to the embeddable-tokens prop. See Security tokens for the request and its options (including making the canvas read-only).
3. Load the ai-chat
Add this script to load the component latest version from unpkg (no build step required):
<script type="module" src="https://unpkg.com/@embeddable.com/[email protected]/dist/ai-chat/ai-chat.esm.js"></script>4. Add the ai-chat to your page
Choose between modal (floating panel) or inline (fills its container):
- Modal mode - the chat opens in a floating panel when the trigger is clicked
<em-ai-chat
mode="modal"
placement="bottom-right"
orchestrator-url="wss://api.[us|eu].embeddable.com/ws"
embeddable-tokens='["your-security-token"]'
>
<button slot="trigger">Open chat</button>
</em-ai-chat>- Inline mode - the chat fills its container
<div style="width: 420px; height: 600px;">
<em-ai-chat mode="inline" orchestrator-url="wss://api.[us|eu].embeddable.com/ws" embeddable-tokens='["your-security-token"]'></em-ai-chat>
</div>See demo.html for a full working example.
Security tokens
Every request the ai-chat makes is authenticated with a security token. Mint one per user with POST /api/v1/security-token (authenticated with your Embeddable API key), then pass it to the embeddable-tokens prop as a JSON-encoded array — the first token is used as the Bearer credential.
curl -X POST https://api.[us|eu].embeddable.com/api/v1/security-token \
-H "Authorization: Bearer YOUR_EMBEDDABLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"embeddableId": "your-embeddable-id",
"user": "user-123",
"securityContext": {},
"expiryInSeconds": 3600
}'Disallowing custom canvas mutation
The AI can mutate the dashboard's custom canvas (for example, adding a chart it just generated). To make the canvas read-only for a token, add "customCanvasReadOnly": true to the request above. When set, the orchestrator denies canvas writes for that token and the ai-chat's tools are marked read-only — the assistant can still answer questions and preview charts, but cannot add to or otherwise modify the canvas. The flag defaults to false (mutations allowed) when omitted. Because it lives on the token, different users can be granted different levels of access with the same embedded widget.
Ai-chat props
| Attribute | Type | Default | Description |
| ------------------------- | -------------------------------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| orchestrator-url | string | — | WebSocket URL of the Embeddable orchestrator backend (ws:// or wss://) |
| embeddable-tokens | string | — | JSON-encoded array of security tokens. The first token is used as the Bearer credential. |
| client-context | string | — | JSON-encoded dashboard client context (the same object passed to embeddables). Its timezone resolves dates consistently with the dashboard. Forwarded to the orchestrator and to injected components. |
| mode | "modal" \| "inline" | "inline" | modal opens a floating draggable panel; inline fills its container |
| placement | "bottom-right" \| "bottom-left" \| "top-right" \| "top-left" | "top-right" | Where the modal panel appears relative to the trigger element |
| chart-preview-supported | "true" \| "false" | "true" | Tells the orchestrator whether this client can render Embeddable chart previews. Sent as the Embeddable-Chart-Preview-Supported header on the STOMP CONNECT frame. |
| labels | Partial<ChatLabels> | — | Overrides the widget's UI text (welcome message, input placeholder, feedback dialog copy). This is an object property, not a string attribute — set it in JS via the DOM property (el.labels = {…}), not as an HTML attribute. See Labels below. |
Labels
All user-facing text is customized through the labels property (type ChatLabels). Provide any subset of the keys below; omitted keys fall back to the defaults.
| Key | Default | Description |
| ----------------------------- | ------------------------------ | ------------------------------------------------------------------------ |
| welcomeMessage | (built-in greeting) | Message shown in the empty state before the first user message |
| inputPlaceholder | "Ask anything..." | Placeholder text inside the message input |
| feedbackTitlePositive | "Tell us what you liked" | Title of the feedback comment dialog after a thumbs-up rating |
| feedbackTitleNegative | "How can we improve?" | Title of the feedback comment dialog after a thumbs-down rating |
| feedbackPlaceholderPositive | "Share more" | Placeholder text inside the feedback comment input after a thumbs-up |
| feedbackPlaceholderNegative | "Share more" | Placeholder text inside the feedback comment input after a thumbs-down |
| feedbackDismiss | "Dismiss" | Label for the dismiss button in the feedback dialog |
| feedbackSubmit | "Submit feedback" | Label for the submit button in the feedback dialog |
| feedbackThanksForFeedback | "Thanks for your feedback" | Confirmation message shown after a written comment is submitted |
| feedbackThanksForRating | "Thanks for your rating" | Confirmation message shown after only a thumbs up/down rating is given |
const chat = document.querySelector('em-ai-chat');
chat.labels = {
welcomeMessage: 'Hi! Ask me anything about your dashboard.',
inputPlaceholder: 'Type a question…',
};Slots
| Name | Description |
| --------- | ----------------------------------------------------------------------- |
| trigger | (modal mode only) The element that toggles the chat panel open/closed |
Theming
The component's visual properties are defined as --em-ai-chat-* CSS custom properties. By default, these resolve to tokens from the remarkable-ui design system — the --em-sem-* (semantic) and --em-core-* (primitive) token layers. You can see the full mapping in src/em-ai-chat/em-ai-chat.tokens.css.
If an <em-mbeddable> is already present in your app, the tokens --em-sem-* (semantic) and --em-core-* (primitive) will be automatically injected into the :root, and the style will be applied automatically.
If no <em-mbeddable> is present, or if you need specific styles for the chat, set the --em-ai-chat-* variables directly on :root (or on the element itself) to match your own design:
:root {
/* Shell */
--em-ai-chat-shell-background: #ffffff;
--em-ai-chat-shell-border-color: #e4e4ea;
/* Input composer */
--em-ai-chat-composer-input-border-color: #e4e4ea;
--em-ai-chat-composer-input-border-color--focus: #2d52cc;
/* Primary icon button (send / cancel) */
--em-ai-chat-icon-button-primary-background: #2d52cc;
--em-ai-chat-icon-button-primary-color: #ffffff;
/* Panel size (modal mode) */
--em-ai-chat-panel-width: 23.75rem;
--em-ai-chat-panel-height: 32.5rem;
}The full list of available variables is in src/em-ai-chat/em-ai-chat.tokens.css.
Framework integration
React / Vue / Angular (ESM import)
import { defineCustomElements } from '@embeddable.com/ai-chat/loader';
defineCustomElements();Then use <em-ai-chat> like any other HTML element:
return (
<em-ai-chat embeddable-tokens={JSON.stringify([token])} orchestrator-url="wss://..." mode="modal">
<button slot="trigger">Chat</button>
</em-ai-chat>
);Vite users — add this to vite.config.ts to prevent Vite from pre-bundling the loader (which breaks Stencil's lazy chunk resolution):
export default defineConfig({
optimizeDeps: {
exclude: ['@embeddable.com/ai-chat'],
},
});Local development
npm install
npm start # dev server with hot reload at http://localhost:3333Edit src/index.html to change props or tokens while developing locally. The orchestrator backend is expected at ws://localhost:8080/ws by default.
npm run build # production build → dist/Architecture
em-ai-chat is the root component. It owns the STOMP WebSocket connection, holds all message state, and coordinates the child components below.
em-ai-chat
│ Root. Manages the STOMP WebSocket lifecycle, accumulates streaming
│ events into a display list, and switches between modal and inline modes.
│
├── em-ai-chat-modal-shell (modal mode only)
│ Floating panel with trigger-based open/close, drag-to-reposition,
│ and mouse/keyboard resize. Persists panel size and position across toggles.
│
├── em-ai-chat-messages
│ Scrolling feed of user messages, assistant responses, tool calls, errors,
│ and injected components. Groups tool calls under their parent message
│ and auto-scrolls to the latest entry.
│
│ ├── em-ai-chat-message-feedback
│ │ Thumbs up/down rating attached to each assistant message.
│ │
│ └── em-ai-chat-component
│ Sandboxed host for Embeddable component bundles injected by the AI.
│ Injects theme tokens, scales height via ResizeObserver, and proxies
│ data-fetch requests to the orchestrator.
│
└── em-ai-chat-input
Textarea composer. Enter sends, Shift+Enter adds a newline. Auto-focuses
on visibility and toggles the action button between send and cancel
while a response is streaming.Incoming server events are processed by reduceEvent() in src/utils/messages.utils.ts. It maintains a Map<id, message> for O(1) streaming updates and returns a new array reference on each change to trigger Stencil re-renders.
