@pixygon/chatbot-react 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,95 +1,181 @@
1
1
  # @pixygon/chatbot-react
2
2
 
3
- React + MUI + RTK Query components for the Pixygon chatbot.
3
+ Ready-to-mount **React 18/19 + MUI 6/7 + RTK Query** pages for the Pixygon
4
+ chatbot stack. Pair with [`@pixygon/chatbot-server`](https://www.npmjs.com/package/@pixygon/chatbot-server)
5
+ on the API side.
6
+
7
+ ```
8
+ +----------------------+ +----------------------+
9
+ | Host React app |----->| createChatbotComponents()
10
+ | (your baseApi here) | +----------------------+
11
+ +----------------------+ |
12
+ v
13
+ KnowledgePage · ChatPage · ChatAnalyticsPage · EmbedChatPage · ChatbotSettings
14
+ |
15
+ v
16
+ HTTP -> chatbot-server
17
+ ```
18
+
19
+ You configure once at boot, then mount the components in your router or
20
+ settings tree. Hooks close over the active tenant id, branding, and your
21
+ existing RTK Query base.
22
+
23
+ ---
4
24
 
5
25
  ## Install
6
26
 
7
27
  ```bash
8
28
  npm install @pixygon/chatbot-react
9
- # Peer deps the host already has:
10
- # - react ≥18
11
- # - @mui/material, @mui/icons-material, @emotion/react, @emotion/styled ≥11
12
- # - @reduxjs/toolkit ≥2, react-redux ≥9
13
29
  ```
14
30
 
15
- ## Usage
31
+ Peer dependencies the host already has:
32
+
33
+ | Peer | Range |
34
+ |---|---|
35
+ | `react` / `react-dom` | ^18 or ^19 |
36
+ | `@mui/material` / `@mui/icons-material` | ^6 or ^7 |
37
+ | `@emotion/react` / `@emotion/styled` | ^11 |
38
+ | `@reduxjs/toolkit` | ^2 |
39
+ | `react-redux` | ^9 |
40
+
41
+ The host's `baseApi` should include these RTK Query tag types so cache
42
+ invalidation works:
16
43
 
17
44
  ```ts
45
+ tagTypes: [..., "Knowledge", "ChatConversation"]
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Configure once
51
+
52
+ ```tsx
53
+ // src/chatbot.tsx
18
54
  import { createChatbotComponents } from "@pixygon/chatbot-react";
55
+ import { useParams } from "react-router";
19
56
  import { baseApi } from "@/apis/baseApi";
20
57
  import { useAppSelector } from "@/store/store";
58
+ import { useGetTenantQuery, useUpdateTenantMutation } from "@/apis/tenantApi";
21
59
 
22
- export const chatbot = createChatbotComponents({
60
+ const chatbot = createChatbotComponents({
23
61
  apiSlice: baseApi,
24
- pathPrefix: "/tenants", // or "/companies"
62
+ pathPrefix: "/tenants", // or "/companies"
25
63
  useTenantId: () => useAppSelector((s) => s.global.activeTenantId),
26
- apiBase: import.meta.env.VITE_API_URL,
64
+ apiBase: import.meta.env.VITE_API_URL, // public surface origin
27
65
  brand: {
28
66
  name: "TotalHMS",
29
67
  tagline: "Powered by TotalHMS",
30
68
  primaryColor: "#8FB7C9",
31
69
  },
32
70
  });
33
- ```
34
71
 
35
- Then drop into your router:
72
+ export const KnowledgePage = chatbot.KnowledgePage;
73
+ export const ChatPage = chatbot.ChatPage;
74
+ export const ChatAnalyticsPage = chatbot.ChatAnalyticsPage;
36
75
 
37
- ```tsx
38
- <Route path="/knowledge" element={<chatbot.KnowledgePage />} />
39
- <Route path="/chat" element={<chatbot.ChatPage />} />
40
- <Route path="/chat-analytics" element={<chatbot.ChatAnalyticsPage />} />
41
- <Route path="/embed/chat/:tenantSlug" element={<EmbedRoute />} />
76
+ // Small wrapper so the route can hand the slug from useParams down to the page.
77
+ export function EmbedChatPageRoute() {
78
+ const { tenantSlug } = useParams();
79
+ return <chatbot.EmbedChatPage tenantSlug={tenantSlug} />;
80
+ }
81
+
82
+ // Settings card — host supplies tenant + save handler (Tenant model lives here).
83
+ export function ChatbotSettings({ tenantId }: { tenantId: string }) {
84
+ const { data: tenant } = useGetTenantQuery(tenantId);
85
+ const [updateTenant] = useUpdateTenantMutation();
86
+ return (
87
+ <chatbot.ChatbotSettings
88
+ tenant={tenant ?? null}
89
+ onSave={(patch) =>
90
+ updateTenant({ tenantId, patch }).unwrap().then(() => undefined)
91
+ }
92
+ />
93
+ );
94
+ }
42
95
  ```
43
96
 
44
- `EmbedChatPage` reads `tenantSlug` from props, so the host wires it through:
97
+ ## Drop into routes
45
98
 
46
99
  ```tsx
47
- function EmbedRoute() {
48
- const { tenantSlug } = useParams();
49
- return <chatbot.EmbedChatPage tenantSlug={tenantSlug} />;
50
- }
100
+ import { KnowledgePage, ChatPage, ChatAnalyticsPage, EmbedChatPageRoute } from "@/chatbot";
101
+
102
+ <Route path="/embed/chat/:tenantSlug" element={<EmbedChatPageRoute />} />
103
+
104
+ <Route element={<DashboardLayout />}>
105
+ <Route path="/knowledge" element={<KnowledgePage />} />
106
+ <Route path="/chat" element={<ChatPage />} />
107
+ <Route path="/chat-analytics" element={<ChatAnalyticsPage />} />
108
+ </Route>
51
109
  ```
52
110
 
53
- Settings card (drops into the host's settings page, host supplies the
54
- tenant + save handler since the Tenant model lives in the host):
111
+ ## Drop into a settings page
55
112
 
56
113
  ```tsx
57
- <chatbot.ChatbotSettings
58
- tenant={activeTenant}
59
- onSave={(patch) => updateTenant({ tenantId, patch }).unwrap()}
60
- />
114
+ <ChatbotSettings tenantId={activeTenantId} />
61
115
  ```
62
116
 
117
+ ---
118
+
63
119
  ## What you get
64
120
 
65
- - `KnowledgePage` list + paste-text upload + delete
66
- - `ChatPage` — operator chat UI with sessionId-keyed threads
67
- - `ChatAnalyticsPage` KPI tiles, knowledge gaps, top questions, keywords,
68
- semantic clusters, cost timeseries, source usage, drill-down dialog
69
- - `EmbedChatPage` iframe-friendly chat UI for the public anonymous surface
70
- - `ChatbotSettings` cost cap input + copy-pasteable embed snippet
121
+ | Component | Purpose |
122
+ |---|---|
123
+ | `KnowledgePage` | List, paste/upload, delete knowledge documents per tenant |
124
+ | `ChatPage` | Operator chat UI with session-keyed threads and citations |
125
+ | `ChatAnalyticsPage` | KPI tiles · knowledge gaps · top questions · keywords · semantic clusters · cost timeseries · source usage · question drill-down |
126
+ | `EmbedChatPage` | Iframe-friendly anonymous chat for the public surface |
127
+ | `ChatbotSettings` | Per-tenant monthly cost cap + copy-pasteable embed snippet |
128
+
129
+ ---
130
+
131
+ ## Embed widget
71
132
 
72
- The bootstrap snippet is served as a separate static file at
73
- `@pixygon/chatbot-react/chatbot.js`. Symlink or copy to your app's `public/`:
133
+ `chatbot.js` is shipped as a static file inside the package. Drop it in your
134
+ app's `public/` so it's served at `/chatbot.js`:
74
135
 
75
136
  ```bash
76
137
  cp node_modules/@pixygon/chatbot-react/public/chatbot.js public/chatbot.js
77
138
  ```
78
139
 
79
- ## RTK Query tag types
140
+ Then anyone can embed your chatbot on any HTML page:
80
141
 
81
- The host's `baseApi` should include these tags so invalidation works:
82
-
83
- ```ts
84
- tagTypes: [..., "Knowledge", "ChatConversation"]
142
+ ```html
143
+ <script src="https://app.example.com/chatbot.js"
144
+ data-tenant-slug="acme"
145
+ defer></script>
85
146
  ```
86
147
 
148
+ The widget renders a floating launcher, opens an iframe pointed at
149
+ `/embed/chat/<slug>` on your dashboard, and `postMessage`s a
150
+ `pixygon-chatbot:close` event when the user closes it. The `ChatbotSettings`
151
+ card auto-generates the snippet with the right URL.
152
+
153
+ ---
154
+
87
155
  ## Hooks-only API
88
156
 
89
- If you only want the RTK Query hooks without the pages, use `injectChatbotEndpoints`:
157
+ Skip the pages if you want to build your own UI:
90
158
 
91
159
  ```ts
92
160
  import { injectChatbotEndpoints } from "@pixygon/chatbot-react";
93
- const { useListDocumentsQuery, useSendMessageMutation, ... } =
94
- injectChatbotEndpoints(baseApi, { pathPrefix: "/tenants" });
161
+
162
+ const {
163
+ useListDocumentsQuery,
164
+ useCreateDocumentMutation,
165
+ useDeleteDocumentMutation,
166
+ useSendMessageMutation,
167
+ useGetConversationQuery,
168
+ useRateMessageMutation,
169
+ useChatOverviewQuery,
170
+ useKnowledgeGapsQuery,
171
+ useSemanticClustersQuery,
172
+ // ...
173
+ } = injectChatbotEndpoints(baseApi, { pathPrefix: "/tenants" });
95
174
  ```
175
+
176
+ ---
177
+
178
+ ## Companion package
179
+
180
+ `@pixygon/chatbot-server` is the Express + Mongoose backend these components
181
+ expect. See its README for the API wire-up.
package/dist/index.d.ts CHANGED
@@ -213,6 +213,7 @@ declare function createChatbotComponents(cfg: ChatbotComponentConfig): {
213
213
  }) => Promise<void>;
214
214
  embedScriptUrl?: string;
215
215
  }) => react.JSX.Element | null;
216
+ ChatLauncher: () => react.JSX.Element;
216
217
  KnowledgePageBase: (props: {
217
218
  tenantId?: string | null;
218
219
  }) => react.JSX.Element;
package/dist/index.js CHANGED
@@ -1044,8 +1044,248 @@ function createChatbotSettings(cfg) {
1044
1044
  };
1045
1045
  }
1046
1046
 
1047
+ // src/pages/ChatLauncher.tsx
1048
+ import { useEffect as useEffect4, useRef as useRef3, useState as useState6 } from "react";
1049
+ import {
1050
+ Box as Box6,
1051
+ Typography as Typography6,
1052
+ IconButton as IconButton5,
1053
+ Fab,
1054
+ TextField as TextField5,
1055
+ Stack as Stack6,
1056
+ Tooltip as Tooltip5,
1057
+ Chip as Chip6,
1058
+ Slide,
1059
+ Paper,
1060
+ Divider as Divider3,
1061
+ Alert as Alert5
1062
+ } from "@mui/material";
1063
+ import {
1064
+ ChatBubble,
1065
+ Close as Close3,
1066
+ Send as Send3,
1067
+ RestartAlt as RestartAlt2,
1068
+ ThumbUp as ThumbUp4,
1069
+ ThumbDown as ThumbDown4
1070
+ } from "@mui/icons-material";
1071
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1072
+ function createChatLauncher(cfg, hooks) {
1073
+ return function ChatLauncher() {
1074
+ const tenantId = cfg.useTenantId();
1075
+ const [open, setOpen] = useState6(false);
1076
+ const [sessionId, setSessionId] = useState6(null);
1077
+ const [draft, setDraft] = useState6("");
1078
+ const [optimistic, setOptimistic] = useState6([]);
1079
+ const [error, setError] = useState6(null);
1080
+ const scrollRef = useRef3(null);
1081
+ const [sendMessage, { isLoading: sending }] = hooks.useSendMessageMutation();
1082
+ const [rateMessage] = hooks.useRateMessageMutation();
1083
+ const { data: conversation } = hooks.useGetConversationQuery(
1084
+ { tenantId, sessionId },
1085
+ { skip: !tenantId || !sessionId || !open }
1086
+ );
1087
+ const display = [
1088
+ ...conversation?.messages || [],
1089
+ ...optimistic.filter((o) => !(conversation?.messages || []).some(
1090
+ (m) => m.content === o.content && m.role === o.role
1091
+ ))
1092
+ ];
1093
+ useEffect4(() => {
1094
+ if (!open) return;
1095
+ scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: "smooth" });
1096
+ }, [display.length, open]);
1097
+ const handleSend = async () => {
1098
+ if (!draft.trim() || sending || !tenantId) return;
1099
+ setError(null);
1100
+ const localUser = { _localId: `local-${Date.now()}`, role: "user", content: draft.trim() };
1101
+ const localPending = { _localId: `pending-${Date.now()}`, role: "assistant", content: "Thinking\u2026", pending: true };
1102
+ setOptimistic((prev) => [...prev, localUser, localPending]);
1103
+ setDraft("");
1104
+ try {
1105
+ const res = await sendMessage({
1106
+ tenantId,
1107
+ sessionId: sessionId || void 0,
1108
+ message: localUser.content
1109
+ }).unwrap();
1110
+ if (!sessionId) setSessionId(res.sessionId);
1111
+ setOptimistic((prev) => prev.filter((p) => p._localId !== localPending._localId));
1112
+ } catch (e) {
1113
+ setError(e?.data?.error || "Send failed");
1114
+ setOptimistic((prev) => prev.filter((p) => !p.pending));
1115
+ }
1116
+ };
1117
+ const handleRate = async (messageId, rating) => {
1118
+ if (!conversation || !tenantId) return;
1119
+ await rateMessage({ tenantId, conversationId: conversation._id, messageId, rating });
1120
+ };
1121
+ const handleNewChat = () => {
1122
+ setSessionId(null);
1123
+ setOptimistic([]);
1124
+ setError(null);
1125
+ };
1126
+ const primary = cfg.brand.primaryColor;
1127
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1128
+ /* @__PURE__ */ jsx6(
1129
+ Fab,
1130
+ {
1131
+ color: "primary",
1132
+ "aria-label": open ? "Close assistant" : "Open assistant",
1133
+ onClick: () => setOpen((o) => !o),
1134
+ sx: {
1135
+ position: "fixed",
1136
+ right: 24,
1137
+ bottom: 24,
1138
+ zIndex: 1300,
1139
+ bgcolor: primary,
1140
+ "&:hover": { bgcolor: primary, filter: "brightness(0.92)" }
1141
+ },
1142
+ children: open ? /* @__PURE__ */ jsx6(Close3, {}) : /* @__PURE__ */ jsx6(ChatBubble, {})
1143
+ }
1144
+ ),
1145
+ /* @__PURE__ */ jsx6(Slide, { direction: "up", in: open, mountOnEnter: true, unmountOnExit: true, children: /* @__PURE__ */ jsxs6(
1146
+ Paper,
1147
+ {
1148
+ elevation: 8,
1149
+ sx: {
1150
+ position: "fixed",
1151
+ right: 24,
1152
+ bottom: 96,
1153
+ zIndex: 1299,
1154
+ width: { xs: "calc(100vw - 48px)", sm: 380 },
1155
+ height: { xs: "calc(100vh - 144px)", sm: 580 },
1156
+ maxHeight: "80vh",
1157
+ display: "flex",
1158
+ flexDirection: "column",
1159
+ borderRadius: 2,
1160
+ overflow: "hidden"
1161
+ },
1162
+ children: [
1163
+ /* @__PURE__ */ jsxs6(Box6, { sx: {
1164
+ p: 1.5,
1165
+ bgcolor: primary,
1166
+ color: "white",
1167
+ display: "flex",
1168
+ alignItems: "center",
1169
+ gap: 1
1170
+ }, children: [
1171
+ /* @__PURE__ */ jsxs6(Box6, { sx: { flex: 1 }, children: [
1172
+ /* @__PURE__ */ jsxs6(Typography6, { variant: "subtitle2", fontWeight: 700, children: [
1173
+ cfg.brand.name,
1174
+ " Assistant"
1175
+ ] }),
1176
+ cfg.brand.tagline && /* @__PURE__ */ jsx6(Typography6, { variant: "caption", sx: { opacity: 0.85, display: "block" }, children: cfg.brand.tagline })
1177
+ ] }),
1178
+ /* @__PURE__ */ jsx6(Tooltip5, { title: "New chat", children: /* @__PURE__ */ jsx6(IconButton5, { size: "small", onClick: handleNewChat, sx: { color: "white" }, children: /* @__PURE__ */ jsx6(RestartAlt2, { fontSize: "small" }) }) }),
1179
+ /* @__PURE__ */ jsx6(Tooltip5, { title: "Close", children: /* @__PURE__ */ jsx6(IconButton5, { size: "small", onClick: () => setOpen(false), sx: { color: "white" }, children: /* @__PURE__ */ jsx6(Close3, { fontSize: "small" }) }) })
1180
+ ] }),
1181
+ /* @__PURE__ */ jsx6(
1182
+ Box6,
1183
+ {
1184
+ ref: scrollRef,
1185
+ sx: { flex: 1, overflowY: "auto", p: 1.5, bgcolor: "background.default" },
1186
+ children: !tenantId ? /* @__PURE__ */ jsx6(Alert5, { severity: "info", children: "Select a tenant to start chatting." }) : display.length === 0 ? /* @__PURE__ */ jsxs6(Stack6, { alignItems: "center", sx: { py: 6 }, spacing: 1.5, children: [
1187
+ /* @__PURE__ */ jsx6(ChatBubble, { sx: { fontSize: 40, color: "text.secondary" } }),
1188
+ /* @__PURE__ */ jsx6(Typography6, { variant: "body2", color: "text.secondary", align: "center", children: "Ask anything \u2014 answers come from your knowledge base only." })
1189
+ ] }) : /* @__PURE__ */ jsx6(Stack6, { spacing: 1.5, children: display.map((turn, i) => /* @__PURE__ */ jsx6(
1190
+ CompactTurn,
1191
+ {
1192
+ turn,
1193
+ onRate: turn._id ? (r) => handleRate(turn._id, r) : void 0
1194
+ },
1195
+ turn._id || turn._localId || String(i)
1196
+ )) })
1197
+ }
1198
+ ),
1199
+ /* @__PURE__ */ jsx6(Divider3, {}),
1200
+ /* @__PURE__ */ jsxs6(Box6, { sx: { p: 1, bgcolor: "background.paper" }, children: [
1201
+ error && /* @__PURE__ */ jsx6(Alert5, { severity: "error", sx: { mb: 1 }, children: error }),
1202
+ /* @__PURE__ */ jsxs6(Stack6, { direction: "row", spacing: 0.5, alignItems: "flex-end", children: [
1203
+ /* @__PURE__ */ jsx6(
1204
+ TextField5,
1205
+ {
1206
+ fullWidth: true,
1207
+ size: "small",
1208
+ multiline: true,
1209
+ maxRows: 4,
1210
+ placeholder: "Ask the assistant\u2026",
1211
+ value: draft,
1212
+ onChange: (e) => setDraft(e.target.value),
1213
+ onKeyDown: (e) => {
1214
+ if (e.key === "Enter" && !e.shiftKey) {
1215
+ e.preventDefault();
1216
+ handleSend();
1217
+ }
1218
+ },
1219
+ disabled: sending || !tenantId
1220
+ }
1221
+ ),
1222
+ /* @__PURE__ */ jsx6(
1223
+ IconButton5,
1224
+ {
1225
+ color: "primary",
1226
+ onClick: handleSend,
1227
+ disabled: !draft.trim() || sending || !tenantId,
1228
+ children: /* @__PURE__ */ jsx6(Send3, {})
1229
+ }
1230
+ )
1231
+ ] })
1232
+ ] })
1233
+ ]
1234
+ }
1235
+ ) })
1236
+ ] });
1237
+ };
1238
+ }
1239
+ function CompactTurn({ turn, onRate }) {
1240
+ const isUser = turn.role === "user";
1241
+ return /* @__PURE__ */ jsx6(Box6, { sx: { display: "flex", justifyContent: isUser ? "flex-end" : "flex-start" }, children: /* @__PURE__ */ jsxs6(Box6, { sx: { maxWidth: "85%" }, children: [
1242
+ /* @__PURE__ */ jsx6(
1243
+ Box6,
1244
+ {
1245
+ sx: {
1246
+ bgcolor: isUser ? "primary.main" : "background.paper",
1247
+ color: isUser ? "primary.contrastText" : "text.primary",
1248
+ px: 1.5,
1249
+ py: 1,
1250
+ borderRadius: 1.5,
1251
+ border: "1px solid",
1252
+ borderColor: isUser ? "primary.main" : "divider",
1253
+ whiteSpace: "pre-wrap",
1254
+ wordBreak: "break-word",
1255
+ opacity: turn.pending ? 0.6 : 1,
1256
+ fontSize: 14
1257
+ },
1258
+ children: /* @__PURE__ */ jsx6(Typography6, { variant: "body2", children: turn.content })
1259
+ }
1260
+ ),
1261
+ !isUser && turn.citations?.length > 0 && /* @__PURE__ */ jsx6(Stack6, { direction: "row", spacing: 0.5, sx: { mt: 0.5, flexWrap: "wrap", gap: 0.5 }, children: turn.citations.map((c, i) => /* @__PURE__ */ jsx6(Tooltip5, { title: c.snippet, children: /* @__PURE__ */ jsx6(Chip6, { label: `[${i + 1}]`, size: "small", variant: "outlined" }) }, c.chunkId || i)) }),
1262
+ !isUser && onRate && /* @__PURE__ */ jsxs6(Stack6, { direction: "row", spacing: 0.5, sx: { mt: 0.25 }, children: [
1263
+ /* @__PURE__ */ jsx6(
1264
+ IconButton5,
1265
+ {
1266
+ size: "small",
1267
+ onClick: () => onRate(1),
1268
+ color: turn.rating === 1 ? "primary" : "default",
1269
+ sx: { p: 0.25 },
1270
+ children: /* @__PURE__ */ jsx6(ThumbUp4, { sx: { fontSize: 14 } })
1271
+ }
1272
+ ),
1273
+ /* @__PURE__ */ jsx6(
1274
+ IconButton5,
1275
+ {
1276
+ size: "small",
1277
+ onClick: () => onRate(-1),
1278
+ color: turn.rating === -1 ? "error" : "default",
1279
+ sx: { p: 0.25 },
1280
+ children: /* @__PURE__ */ jsx6(ThumbDown4, { sx: { fontSize: 14 } })
1281
+ }
1282
+ )
1283
+ ] })
1284
+ ] }) });
1285
+ }
1286
+
1047
1287
  // src/index.tsx
1048
- import { jsx as jsx6 } from "react/jsx-runtime";
1288
+ import { jsx as jsx7 } from "react/jsx-runtime";
1049
1289
  function createChatbotComponents(cfg) {
1050
1290
  const resolved = resolveConfig(cfg);
1051
1291
  const hooks = injectChatbotEndpoints(cfg.apiSlice, { pathPrefix: cfg.pathPrefix });
@@ -1054,17 +1294,18 @@ function createChatbotComponents(cfg) {
1054
1294
  const ChatAnalyticsPageBase = createChatAnalyticsPage(resolved, hooks);
1055
1295
  const EmbedChatPageBase = createEmbedChatPage(resolved);
1056
1296
  const ChatbotSettings = createChatbotSettings(resolved);
1297
+ const ChatLauncher = createChatLauncher(resolved, hooks);
1057
1298
  const KnowledgePage = () => {
1058
1299
  const tenantId = cfg.useTenantId();
1059
- return /* @__PURE__ */ jsx6(KnowledgePageBase, { tenantId });
1300
+ return /* @__PURE__ */ jsx7(KnowledgePageBase, { tenantId });
1060
1301
  };
1061
1302
  const ChatPage = () => {
1062
1303
  const tenantId = cfg.useTenantId();
1063
- return /* @__PURE__ */ jsx6(ChatPageBase, { tenantId });
1304
+ return /* @__PURE__ */ jsx7(ChatPageBase, { tenantId });
1064
1305
  };
1065
1306
  const ChatAnalyticsPage = () => {
1066
1307
  const tenantId = cfg.useTenantId();
1067
- return /* @__PURE__ */ jsx6(ChatAnalyticsPageBase, { tenantId });
1308
+ return /* @__PURE__ */ jsx7(ChatAnalyticsPageBase, { tenantId });
1068
1309
  };
1069
1310
  return {
1070
1311
  hooks,
@@ -1073,6 +1314,7 @@ function createChatbotComponents(cfg) {
1073
1314
  ChatAnalyticsPage,
1074
1315
  EmbedChatPage: EmbedChatPageBase,
1075
1316
  ChatbotSettings,
1317
+ ChatLauncher,
1076
1318
  // Base variants if the host wants to pass tenantId explicitly.
1077
1319
  KnowledgePageBase,
1078
1320
  ChatPageBase,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixygon/chatbot-react",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "React + MUI pages for the Pixygon chatbot: knowledge base admin, chat, analytics, embeddable widget.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",