@alxxsck/ai-assistant
v3.10.0
Published
Embeddable Lola AI assistant library
Readme
Lola AI Assistant
Embeddable browser library for the Lola assistant. The package owns its DOM host, Shadow DOM, chat UI, avatar, realtime transports, voice flow, and backend command runtime. A product frontend supplies only an authenticated interaction session and the few integrations that must cross into the host application.
Install and build
The package requires React 19 or newer as a peer dependency.
npm install
npm run build-libLibrary transport and asset origins are embedded at build time:
VITE_LOLA_API_URL=https://api.example.test/api/v1
VITE_LOLA_WS_URL=wss://ws.example.test/cable
VITE_AI_ASSISTANT_ASSETS_URL=https://assets.example.testAll three values are required absolute URLs. The library build fails when a value
is missing, malformed, or uses an incompatible protocol. VITE_* values are
public client configuration: never place service-account keys, access tokens, or
other credentials in them. CI values override the placeholders in .env.
Minimal integration
The product backend creates a Lola customer interaction session. The product frontend passes the short-lived result to the library:
import { ChatWidgetInstance } from "@alxxsck/ai-assistant";
const widget = new ChatWidgetInstance({
customerInteractionSession: {
accessToken: session.accessToken,
interactionSessionId: session.interactionSessionId,
expiresAt: session.expiresAt,
},
locale: productLocale,
onRenewSession: async () => {
const nextSession = await productApi.createLolaInteractionSession();
widget.setCustomerInteractionSession(nextSession);
},
});
await widget.track({
event: "Deposit Form Opened",
params: { currency: "EUR" },
});CustomerInteractionSession contains exactly:
type CustomerInteractionSession = {
readonly accessToken: string;
readonly interactionSessionId: string;
readonly expiresAt: string;
};Realtime settings, voice availability, provider voice selection, and project settings belong to Lola Backend. They are not widget constructor properties and must not be copied into the interaction session.
The instance creates a dedicated host below document.body and attaches an open
Shadow DOM to it. Multiple instances remain isolated. Call destroy() to unmount
the widget, disconnect its runtime, clear pending work, and remove the owned host.
Lifecycle
The supported instance methods are:
widget.setOpen(true);
widget.setLocale(productLocale);
widget.setCustomerInteractionSession(nextSession);
await widget.track({ event: "Game Opened", params: { gameId: "42" } });
widget.destroy();setOpen may be called before asynchronous widget initialization completes; the
latest requested state is applied when the UI is ready. Session replacement updates
authenticated HTTP, realtime, message, voice, and command dependencies without
recreating the public widget instance.
Compact scenario preview
Compact presentation is automatic and is not controlled by host configuration. When the chat is closed, a new eligible Scenario message, question, or CTA appears next to the launcher. When the chat is already open, or a Scenario explicitly opens it first, the same content is rendered only in the chat. Hiding the complete assistant chrome also hides and clears the compact surface.
Launcher docking and panel geometry use the widget's existing origin-scoped persistence. Host applications do not decide which presentation surface a Scenario uses.
Localization
Pass the product's current locale directly to the widget. English is used when
locale is omitted, empty, invalid, or unsupported:
const widget = new ChatWidgetInstance({
customerInteractionSession: session,
locale: productLocale, // for example "es-MX" or "pt-BR"
});When the product language changes, update the mounted widget in place:
widget.setLocale(nextProductLocale);Regional tags are normalized to a supported base language. The widget currently
ships ru, uz, en, it, es, de, fr, pl, pt, bn, sw, so,
and ar; Arabic automatically enables right-to-left layout. Updating the locale
preserves the open route, messages, draft, active conversation, voice state, and
CTA state, and does not reconnect transports.
The locale controls only text owned by the widget. Assistant and backend content is displayed as received; the assistant's response language follows the user's messages. See the localization guide for the complete integration contract.
The widget calls onRenewSession before expiry and after supported authentication
failures. Refresh through the product backend and replace the session in the
callback:
const widget = new ChatWidgetInstance({
customerInteractionSession: session,
onRenewSession: async () => {
const nextSession = await productApi.createLolaInteractionSession();
widget.setCustomerInteractionSession(nextSession);
},
});No event subscription or listener cleanup is required. Parallel renewal signals are debounced, and replacing the session cancels a pending duplicate callback.
Scenario interaction observations never cross an interactionSessionId
boundary. A token replacement for the same session retries the same observation
IDs. Canonical renewal creates a new interaction session, so any unsent queue
owned by the previous session is discarded and sequencing starts again. This is
an intentional principal-isolation rule: old observations are never reauthenticated
with a new session token.
Support identities and avatars
Support Presentations contract v1 uses responderPresentation schema v2. Message
bubbles use the immutable public author snapshot, while the header renders this
backend-owned projection. Its humanSupport and ai fields are independent:
only a current Assignment can name an operator, while routing and ambiguous
assignments stay generic Support. Operator photos are resolved through short-lived,
conversation-scoped grants; signed URLs stay in memory and are purged on session
replacement, authorization loss, and destroy().
Integrations do not pass names, avatar URLs, CMS user IDs, or support status into the constructor. These values come from Lola Backend. Older backends still render with safe fallbacks, but exact historical photos and responder presentation require CH-01 contract v1. See the support presentation guide for the wire contract, fallback rules, and failure behavior.
track sends product events directly to Lola Backend using the active interaction
session. The library owns the Lola URL, bearer authentication, event timestamp,
route context, and idempotency key. Tracking failures are reported through the
standard widget logger and do not reject into the product flow. A session replaced
with setCustomerInteractionSession is used by all later tracking calls.
The constructor and replacement method reject empty tokens, empty interaction session identifiers, and invalid expiry timestamps.
Launcher control
The built-in launcher is visible by default. Products that render their own button can hide it while keeping the same programmatic open path:
const widget = new ChatWidgetInstance({
customerInteractionSession: session,
hideOpenButton: true,
});
productButton.addEventListener("click", () => widget.setOpen(true));Closing a hidden-launcher widget leaves it non-interactive until the product calls
setOpen(true) again. The runtime and active interaction session remain mounted.
Attachments
Attachment support is enabled by default and uses Lola Backend's
quarantine-and-scan pipeline. The picker and drag-and-drop target appear only when
the current server-authoritative responder presentation is CURRENT and human
support is ASSIGNED; they stay unavailable before a current human-support
assignment exists, including NONE and ROUTING. If that assignment is released
after a file was selected, the draft remains removable but is not sent while the
assignment is absent. The SDK sends only attachments that reach READY; uploaded
bytes never become durable message URLs. Set attachments.enabled to false only
when embedding against a legacy backend.
const widget = new ChatWidgetInstance({
customerInteractionSession: session,
// Optional: attachments: { enabled: false },
});The default adapter hashes the file, creates an upload intent, streams the bytes to the presigned storage URL with progress, completes the upload, and waits for the malware scan. Download and image/PDF preview grants are fetched only when the user asks for them. Signed URLs are never retained in chat history.
For controlled demos or tests, a custom transport can replace that adapter. READY
attachment ids are sent by default; sendAttachmentIds: false is a temporary
legacy-backend escape hatch.
attachments: {
enabled: true,
sendAttachmentIds: true,
transport: {
upload: async ({ file, conversationId, onProgress, onStage, signal }) => {
// Create an upload session, PUT the file, then complete it.
// Return durable metadata: { id, fileName, mediaType, size, kind }.
},
grant: async ({ attachmentId, conversationId }) => ({ url, expiresAt }),
revoke: async ({ attachmentId, conversationId }) => {},
},
}Host handlers
handlers is optional and is the only command execution surface. Use it when a
backend command must navigate the product, open a product modal, or customize an
element highlight. uiActionPolicies separately controls trusted presentation
behavior:
const widget = new ChatWidgetInstance({
customerInteractionSession: session,
uiActionPolicies: {
account_page: { mobileDisposition: "collapseOnSuccess" },
featured_game_page: { mobileDisposition: "immersive" },
},
handlers: {
openPage: ({ route }) => productRouter.push(route),
openModal: ({ modalName }) => productModals.open(modalName),
highlight: ({ element, active, reportActivation }) => {
element?.classList.toggle("lola-highlight", active);
productHighlights.setAction(
element,
active
? () => {
reportActivation();
productActions.openDeposit();
}
: null,
);
},
},
});uiActionPolicies is trusted host configuration keyed by canonical action
code. keep leaves the assistant surface open; collapseOnSuccess and
immersive minimize it to the compact assistant after the handler resolves,
on both desktop and mobile. mobileDisposition is retained as a compatibility
field name; a value supplied in command payload is ignored. Unregistered page,
modal, and element actions default to collapseOnSuccess.
Navigation and modal handlers should resolve only after the destination is
ready to own focus, or reject on failure. A successful handoff never returns
focus to Lola's launcher; it preserves host focus while manual collapse still
returns focus to the assistant. immersive currently shares the safe
surface-minimization semantics of collapseOnSuccess; hiding all assistant
chrome is reserved until the host exposes an explicit destination-exit
lifecycle.
reportActivation() is the authoritative signal that the product accepted the
user activation. Call it synchronously from the product-owned action handler,
before navigation can unmount the widget. It records the activation, not the
result of the product action. The attributed target remains observable until its
highlight is removed or replaced, including after the Scenario closes the chat.
The SDK observes target visibility, but never treats arbitrary host DOM clicks as
activation. Existing highlight handlers may ignore this callback.
Page and modal targets arrive in canonical Lola command envelopes. The built-in
element fallback resolves stable [data-lola="..."] attributes and reports only
visibility because it does not own the product action. CTA display, chat
open/close, speech, voice conversations, and avatar animation stay internal.
Command lifecycle events are available as command_received,
command_succeeded, command_failed, command_expired, and
command_unsupported.
Diagnostics
Set debug: true to enable verbose lifecycle, realtime, speech, and voice logs
prefixed with [Lola-Widget]. Initialization and runtime errors remain visible
without this flag. The option does not restore the removed debug GUI or avatar
debug controls.
Breaking migration
This release intentionally removes the former dev/demo and host-configuration surface. Remove these constructor properties from integrations:
mountElementId,agentId,customerId,configNameapiUrl,wsUrlAIAvatar,background,footer,voiceEnabled,voiceConfigmanualOpen,avatarDebug,uiTargets,commandHandlerscompactScenarioPreview
Replace manualOpen with hideOpenButton: true only when the standard launcher
must be hidden. Remove calls to playAnimation, executeReaction,
sendServiceMessage, registerUiTarget, and
registerCommandHandler. Backend commands retain the supported assistant
behaviour internally. Remove compactScenarioPreview without replacement: the
SDK automatically renders eligible Scenario content as a compact preview only
while the chat is closed, and renders it in the full chat while the chat is open.
The repository ships only the library build. It no longer contains an app-mode HTML entry, local session creation, remote JSON widget configs, a debug GUI, preview/deploy demo scripts, or a Vite dev-server configuration.
Verification
npm run typecheck
npm test
npm run lint
npm run format:check
npm run build-lib