npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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/styled

MUI 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.