@molecule/app-embeddable-chat-widget
Feature · embeddable-chat-widget · App (browser) · v1.0.2 · Apache-2.0
Embeddable AI chat widget — third-party-site drop-in launcher + expanded panel with streaming responses
npm install @molecule/app-embeddable-chat-widgetHow it works
@molecule/app-embeddable-chat-widget is a ready-made embeddable-chat-widget feature for the app (browser) side. It composes the core interfaces it needs, so it works with whichever providers your app has bonded.
import { EmbeddableChatWidget } from '@molecule/app-embeddable-chat-widget'
;<EmbeddableChatWidget
config={{
apiBaseUrl: 'https://api.example.com',
brandName: 'Acme',
position: 'bottom-right',
theme: { primaryColor: '#7c3aed' },
}}
/>Works with: @molecule/app-i18n, @molecule/app-react
Reference
Embeddable AI chat widget — floating launcher + expanding chat panel for embedding a brand-configured assistant into a page.
Exports:
<EmbeddableChatWidget>— root component (floating launcher + expanded panel).<EmbeddableChatLauncher>— standalone floating bubble (used internally).<EmbeddableChatPanel>— standalone expanded panel (used internally).mountEmbeddableChatWidget()— imperative one-call mount into a host element.sendChatRequest()— fetch + SSE helper, exported for advanced integrations.readChatStream()— low-level SSE / chunked-text reader.useSafeTranslation()— provider-optional translation hook (used internally).- Types:
EmbeddableChatWidgetConfig,EmbeddableChatWidgetTheme,EmbeddableChatWidgetPosition,EmbeddableChatMessage,EmbeddableChatStreamEvent.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { EmbeddableChatWidget } from '@molecule/app-embeddable-chat-widget'
;<EmbeddableChatWidget
config={{
apiBaseUrl: 'https://api.example.com',
brandName: 'Acme',
position: 'bottom-right',
theme: { primaryColor: '#7c3aed' },
}}
/>
Type
feature
Installation
npm install @molecule/app-embeddable-chat-widget @molecule/app-i18n @molecule/app-react react react-dom
npm install -D @types/react @types/react-dom
API
Interfaces
EmbeddableChatLauncherProps
Props for EmbeddableChatLauncher.
interface EmbeddableChatLauncherProps {
/** Whether the launcher should render. Hidden while the panel is expanded. */
visible: boolean
/** Click handler — flips the widget into expanded state. */
onOpen: () => void
/** Floating position (`bottom-right` | `bottom-left`). */
position: EmbeddableChatWidgetPosition
/** Optional theme. */
theme?: EmbeddableChatWidgetTheme
/** Extra classes. */
className?: string
}
EmbeddableChatPanelProps
Props for EmbeddableChatPanel.
interface EmbeddableChatPanelProps {
/** Whether the panel is expanded (visible) or collapsed (hidden). */
visible: boolean
/** Close handler — collapses back to launcher. */
onClose: () => void
/** Floating corner. */
position: EmbeddableChatWidgetPosition
/** Resolved widget config. */
config: EmbeddableChatWidgetConfig
}
PanelMessageProps
Props for the panel's internal message bubble.
interface PanelMessageProps {
message: EmbeddableChatMessage
theme: EmbeddableChatWidgetConfig['theme']
}
EmbeddableChatWidgetProps
Props for EmbeddableChatWidget.
interface EmbeddableChatWidgetProps {
/** Widget configuration. */
config: EmbeddableChatWidgetConfig
/** Optional initial expanded state (default: collapsed). */
defaultOpen?: boolean
}
MountEmbeddableChatWidgetOptions
Options for mountEmbeddableChatWidget.
interface MountEmbeddableChatWidgetOptions {
/** Start with the panel expanded instead of the collapsed launcher. */
defaultOpen?: boolean
}
EmbeddableChatWidgetTheme
Visual / branding configuration for the widget shell.
interface EmbeddableChatWidgetTheme {
/** Primary accent colour (header, send button, launcher background). */
primaryColor?: string
/** Foreground colour to use against `primaryColor`. */
primaryForegroundColor?: string
}
EmbeddableChatWidgetConfig
Configuration object passed into <EmbeddableChatWidget>. The object is
intentionally flat so a host site can populate it from a single
data-* attribute on the embed div.
interface EmbeddableChatWidgetConfig {
/** Base URL of the chat backend (no trailing slash). `/chat` is appended. */
apiBaseUrl: string
/** Brand name shown in the header. Required. */
brandName: string
/** Optional brand logo (URL) shown in the header next to the brand name. */
brandLogo?: string
/** Floating-launcher position. Defaults to `bottom-right`. */
position?: EmbeddableChatWidgetPosition
/** Visual theme overrides. */
theme?: EmbeddableChatWidgetTheme
/** Optional fetch override (test injection / custom auth). Defaults to `globalThis.fetch`. */
fetchImpl?: typeof fetch
}
EmbeddableChatMessage
A single message stored in the widget's local state.
interface EmbeddableChatMessage {
/** Stable id (uuid-ish) used as the React key. */
id: string
/** Author role — drives alignment + accent. */
role: 'user' | 'assistant'
/** Plain text body. Streaming assistant messages append to this. */
body: string
/** Unix-ms timestamp the message started. */
timestamp: number
}
Types
EmbeddableChatWidgetPosition
Position of the floating launcher relative to the host viewport.
type EmbeddableChatWidgetPosition = 'bottom-right' | 'bottom-left'
EmbeddableChatStreamEvent
Streaming chat event the widget understands. The widget speaks SSE
(data: {json}\n\n) but is tolerant of plain chunked text — any
non-JSON payload is appended to the in-flight assistant message verbatim.
type EmbeddableChatStreamEvent =
{ type: 'content'; delta: string } | { type: 'done' } | { type: 'error'; message: string }
WidgetTranslate
Translation function shape used throughout the widget.
type WidgetTranslate = (
key: string,
values?: InterpolationValues,
options?: { defaultValue?: string; count?: number },
) => string
Functions
EmbeddableChatLauncher(props)
Floating circular launcher rendered in the corner of the host page. Click expands the chat panel.
Fully self-contained: positioning, sizing, color, and flex centering are
all inline styles, so the launcher renders correctly even when the host
page has NOT loaded the molecule stylesheet / wired a ClassMap bond. Text
is resolved through a provider-optional translation hook that falls back
to English defaults when no I18nProvider is present.
function EmbeddableChatLauncher({
visible,
onOpen,
position,
theme,
className,
}: EmbeddableChatLauncherProps): JSX.Element | null
props— Component props (seeEmbeddableChatLauncherProps).
EmbeddableChatPanel(props)
Expanded chat panel — header + scrollable message list + composer.
State is local — each open conversation lives in messages. The widget
is intentionally storage-agnostic; integrations that want persistence
should wrap this with their own resume logic.
function EmbeddableChatPanel({
visible,
onClose,
position,
config,
}: EmbeddableChatPanelProps): JSX.Element | null
props— Component props (seeEmbeddableChatPanelProps).
EmbeddableChatWidget(props)
Drop-in floating chat widget for third-party sites. Renders a collapsed launcher in the configured corner; clicking expands a 360x540px panel with a message list, typing indicator, and input.
The widget is intentionally self-contained — all geometry, colors,
and shadows are inlined so the host site does not need to ship the
molecule stylesheet. Pair with @molecule/app-locales-embeddable-chat-widget
for translations.
function EmbeddableChatWidget({
config,
defaultOpen = false,
}: EmbeddableChatWidgetProps): JSX.Element
props— Component props (seeEmbeddableChatWidgetProps).
mountEmbeddableChatWidget(container, config, options)
Mounts the embeddable chat widget into a host container. Works on a bare
page — no I18nProvider, setClassMap(), or molecule stylesheet required.
function mountEmbeddableChatWidget(
container: string | HTMLElement,
config: EmbeddableChatWidgetConfig,
options?: MountEmbeddableChatWidgetOptions,
): Root
container— The target element, or a CSS selector resolving to one.config— Widget configuration (apiBaseUrl,brandName, …).options— Optional mount options (seeMountEmbeddableChatWidgetOptions).
Returns: The React Root; call .unmount() to tear the widget down.
sendChatRequest(args)
Sends a chat message and streams the response back. Throws on transport
errors or non-OK HTTP statuses; resolves cleanly on done / stream end.
function sendChatRequest({ message, config, onDelta, signal }: SendChatRequestArgs): Promise<void>
args— Send args (message,config,onDelta, optionalsignal).
readChatStream(body, onEvent)
Reads a fetch Response body and yields normalized stream events. Tolerates
both SSE-formatted chunks (data: {json}\n\n) and plain text chunks (any
non-data line is forwarded as a content delta).
Stops cleanly on done events, AbortError, or stream end.
function readChatStream(
body: ReadableStream<Uint8Array<ArrayBufferLike>>,
onEvent: (event: EmbeddableChatStreamEvent) => void,
): Promise<void>
body— TheReadableStreamreturned byfetch().body.onEvent— Callback invoked once per parsed event.
useSafeTranslation()
Returns a translation function that works with OR without a molecule
I18nProvider in context. See the module docs for the two paths.
function useSafeTranslation(): { t: WidgetTranslate }
Returns: An object with a provider-optional t() function.
Injection Notes
Requirements
Peer dependencies:
@molecule/app-i18n^1.0.1@molecule/app-react^1.0.1react^18.0.0 || ^19.0.0react-dom^18.0.0 || ^19.0.0
Runtime Dependencies
@molecule/app-i18n@molecule/app-reactreactreact-dom
True drop-in — no provider wiring required. The widget renders with
sensible English defaults and fully inlined styling when no molecule
I18nProvider or ClassMap bond is present, so it does NOT throw on a bare
third-party page. When it IS mounted inside a molecule app it
automatically picks up the host I18nProvider (via context) for
translations + locale changes; no ClassMap is needed either way because
all geometry/colour is inline. Mount it with the bundled helper — no
I18nProvider, no setClassMap():
import { mountEmbeddableChatWidget } from '@molecule/app-embeddable-chat-widget'
mountEmbeddableChatWidget('#molecule-chat-widget', {
apiBaseUrl: 'https://api.example.com',
brandName: 'Acme',
})
(Raw JSX in a <script> tag still won't parse — build your embed with a
bundler, or ship a pre-built bundle that calls mountEmbeddableChatWidget.)
Backend wire contract. Every send POSTs ${apiBaseUrl}/chat with
request header Accept: text/event-stream, Content-Type: application/json, and JSON body { "message": "<latest user text>" } —
ONLY the newest user turn. No conversation id, history, or auth is sent;
the transcript lives only in widget state, so a stateless backend answers
each turn without context. Correlate sessions server-side (cookies) or
inject headers/ids with config.fetchImpl. The response should stream the
assistant reply back; the reader accepts any of:
- SSE:
data: {"type":"content","delta":"…"}lines, terminated bydata: {"type":"done"}ordata: [DONE];data: {"type":"error","message":"…"}surfaces an error. - JSON lines with a
contentortextfield (OpenAI/Anthropic-shaped) — the field is appended as a delta. - Plain chunked text — appended verbatim as deltas.
A non-2xx status (or a
data:error event) is surfaced to the user as an error message in the panel.
Styling. Panel/launcher geometry and colours are 100% inline
(independent of host CSS) and light-themed (white panel); config.theme
customises only the primary accent + its foreground. There is no ClassMap
dependency, so the widget looks identical on and off a molecule host.
Translations
Translation strings are provided by @molecule/app-locales-embeddable-chat-widget.