← All @molecule/* packages · App templates
@molecule/app-embeddable-chat-widgetFeature · embeddable-chat-widget · App (browser) · v1.0.1 · Apache-2.0
Embeddable AI chat widget — third-party-site drop-in launcher + expanded panel with streaming responses
npm install @molecule/app-embeddable-chat-widget@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
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.
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).EmbeddableChatWidgetConfig, EmbeddableChatWidgetTheme,
EmbeddableChatWidgetPosition, EmbeddableChatMessage,
EmbeddableChatStreamEvent.import { EmbeddableChatWidget } from '@molecule/app-embeddable-chat-widget'
;<EmbeddableChatWidget
config={{
apiBaseUrl: 'https://api.example.com',
brandName: 'Acme',
position: 'bottom-right',
theme: { primaryColor: '#7c3aed' },
}}
/>
feature
npm install @molecule/app-embeddable-chat-widget @molecule/app-i18n @molecule/app-react react react-dom
npm install -D @types/react @types/react-dom
EmbeddableChatLauncherPropsProps for {@link 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
}
EmbeddableChatMessageA 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
}
EmbeddableChatPanelPropsProps for {@link 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
}
EmbeddableChatWidgetConfigConfiguration 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
}
EmbeddableChatWidgetPropsProps for {@link EmbeddableChatWidget}.
interface EmbeddableChatWidgetProps {
/** Widget configuration. */
config: EmbeddableChatWidgetConfig
/** Optional initial expanded state (default: collapsed). */
defaultOpen?: boolean
}
EmbeddableChatWidgetThemeVisual / 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
}
MountEmbeddableChatWidgetOptionsOptions for {@link mountEmbeddableChatWidget}.
interface MountEmbeddableChatWidgetOptions {
/** Start with the panel expanded instead of the collapsed launcher. */
defaultOpen?: boolean
}
PanelMessagePropsProps for the panel's internal message bubble.
interface PanelMessageProps {
message: EmbeddableChatMessage
theme: EmbeddableChatWidgetConfig['theme']
}
EmbeddableChatStreamEventStreaming 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 }
EmbeddableChatWidgetPositionPosition of the floating launcher relative to the host viewport.
type EmbeddableChatWidgetPosition = 'bottom-right' | 'bottom-left'
WidgetTranslateTranslation function shape used throughout the widget.
type WidgetTranslate = (
key: string,
values?: InterpolationValues,
options?: { defaultValue?: string; count?: number },
) => string
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 (see {@link EmbeddableChatLauncherProps}).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 (see {@link EmbeddableChatPanelProps}).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 (see {@link EmbeddableChatWidgetProps}).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 (see {@link MountEmbeddableChatWidgetOptions}).Returns: The React Root; call .unmount() to tear the widget down.
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 — The ReadableStream returned by fetch().body.onEvent — Callback invoked once per parsed event.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, optional signal).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.
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@molecule/app-i18n@molecule/app-reactreactreact-domTrue 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:
data: {"type":"content","delta":"…"} lines, terminated by
data: {"type":"done"} or data: [DONE]; data: {"type":"error","message":"…"} surfaces an error.content or text field (OpenAI/Anthropic-shaped) —
the field is appended as a delta.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.
Translation strings are provided by @molecule/app-locales-embeddable-chat-widget.