@cxi-lmai/chatbot-blueprint
v0.1.0
Published
White-label React chat frontend: embeddable widget and standalone app.
Downloads
181
Readme
@cxi-lmai/chatbot-blueprint
A white-label React chat frontend: an embeddable widget, the bare conversation surface, or the complete standalone application — configured with props rather than forked.
Streaming answers over SSE, Markdown rendering, conversation history, light and dark themes, and every user-facing string overridable.
Pre-1.0. The API may change between minor versions until a consumer ships on it.
Install
npm install @cxi-lmai/chatbot-blueprint
npm install @mui/material @mui/icons-material @emotion/react @emotion/styledMUI and React are peer dependencies, so your application controls their
versions. React 19 and MUI 7 are required (see Requirements).
For the /app entry, also install react-router-dom, react-oidc-context and
oidc-client-ts — they are optional peers that only that entry needs.
Three entry points
| Import | What you get | Pulls in |
|---|---|---|
| @cxi-lmai/chatbot-blueprint | ChatWidget, ChatPanel, provider, hooks | React + MUI only |
| @cxi-lmai/chatbot-blueprint/app | ChatbotApp — routing, Keycloak, role gating, sidebar | router + OIDC |
| @cxi-lmai/chatbot-blueprint/theme | getAppTheme for theming | MUI |
The main entry deliberately contains no router and no OIDC code, so embedding the widget in an existing application costs nothing extra.
Quick start — drop-in widget
import { ChatWidget } from '@cxi-lmai/chatbot-blueprint';
export const App = () => (
<ChatWidget
config={{ apiBaseUrl: 'https://chat.example.com/api', moduleName: 'Support' }}
getToken={() => myAuth.getAccessToken()}
variant="bubble"
/>
);getToken is called before every request, not once, so a host application's
silent token renewal keeps a long conversation alive. Returning undefined is
supported: the widget renders unauthenticated rather than throwing.
variant is 'bubble' (floating launcher, the default), 'panel'
(always-open, fixed position) or 'inline' (fills its parent — you control
placement).
Bring your own layout
ChatPanel is the conversation with no chrome. It must sit inside a
ChatbotProvider, which is what ChatWidget renders for you:
import { ChatbotProvider, ChatPanel } from '@cxi-lmai/chatbot-blueprint';
<ChatbotProvider config={{ apiBaseUrl: '/api' }} getToken={getToken} onAuthError={signIn}>
<div style={{ height: 600 }}>
<ChatPanel />
</div>
</ChatbotProvider>Need to build your own UI? The hooks are exported: useChat (messages,
sendMessage, stopGeneration, history), useChatApi (the bound client) and
useChatbotConfig (resolved config and labels). createChatApi works outside
React entirely.
Configuration
interface ChatbotConfigInput {
apiBaseUrl: string; // absolute URL or same-origin path ('/api')
moduleName?: string; // display name, default 'AI Assistant'
devMode?: boolean; // reveals advanced affordances
labels?: Partial<ChatbotLabels>; // any user-facing string
}ChatbotProvider also takes getToken and onAuthError (called when the
backend rejects the token, so a host can trigger re-login).
Text and translations
Defaults are neutral English. Override one key or supply a whole pack:
import { ChatWidget, csLabels } from '@cxi-lmai/chatbot-blueprint';
<ChatWidget config={{ apiBaseUrl: '/api', labels: csLabels }} />
<ChatWidget config={{ apiBaseUrl: '/api', labels: { newChat: 'Start over' } }} />Overrides are merged over the defaults, so a partial pack is fine. Packs are
plain JSON-serialisable objects, so they can come from runtime config.
{module} and {role} placeholders are interpolated by formatLabel.
Built-in packs: defaultLabels (en), csLabels (cs), both in labelPacks.
Standalone application
import { ChatbotApp } from '@cxi-lmai/chatbot-blueprint/app';
createRoot(document.getElementById('root')!).render(<ChatbotApp />);With no props it reads window.ENV, so one build can be deployed to several
environments by swapping a config.js served next to index.html:
window.ENV = {
API_BASE_URL: 'https://chat.example.com/api',
MODULE_NAME: 'Support',
ENABLE_DEV_MODE: false,
LANGUAGE: 'cs', // selects a built-in label pack
KEYCLOAK_AUTHORITY: 'https://sso.example.com/realms/main',
KEYCLOAK_CLIENT_ID: 'chat-app',
KEYCLOAK_REQUIRED_ROLE: 'CHAT.USER', // omit to disable role gating
KEYCLOAK_REDIRECT_URI: 'https://chat.example.com/',
};Only KEYCLOAK_REDIRECT_URI is configured; the post-logout and silent-renew
URIs are derived from it, so there is nothing to keep in sync. Pass config
and oidc props instead if you prefer explicit wiring.
Theming
import { getAppTheme } from '@cxi-lmai/chatbot-blueprint/theme';
import { ThemeProvider } from '@mui/material';
<ThemeProvider theme={getAppTheme('dark')}>…</ThemeProvider>Components style themselves from theme variables, so a client palette flows through without touching component source.
Backend contract
| Method | Path | Purpose |
|---|---|---|
| POST | conversations | create a thread |
| GET | conversations/{id} | fetch a transcript |
| POST | chat/stream | send a message, receive SSE |
| GET | auth/status | verify the bearer token |
chat/stream emits data: frames carrying {"type":"answer_delta","delta":"…"}
(appended to the reply), {"type":"status","text":"…"} (progress) and
{"type":"error","message":"…","detail":"…"} (rejects the send and surfaces the
message in the transcript). Unknown event types are ignored, so the backend can
add more without breaking clients. Requests carry
Authorization: Bearer <token> when getToken returns one, and are abortable
via stopGeneration.
Requirements
- React 19 and MUI 7 as peers. React 18 and MUI 8/9 are not supported yet.
- A browser context. Tokens and chat history are never persisted — only the
theme choice and sidebar state reach
localStorage. - One widget per page: the store is a module-level singleton, so two mounted widgets share conversation state.
Licence
PolyForm Noncommercial License 1.0.0
— SPDX PolyForm-Noncommercial-1.0.0. See LICENSE.
Any noncommercial purpose is permitted, and the licence names educational institutions, public research organisations and government institutions as noncommercial regardless of how they are funded — so universities and public bodies may use, modify and redistribute it. Commercial use is not granted; contact the licensor for a separate licence.
