@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 +129 -43
- package/dist/index.d.ts +1 -0
- package/dist/index.js +246 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,95 +1,181 @@
|
|
|
1
1
|
# @pixygon/chatbot-react
|
|
2
2
|
|
|
3
|
-
React + MUI + RTK Query
|
|
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
|
-
|
|
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
|
-
|
|
60
|
+
const chatbot = createChatbotComponents({
|
|
23
61
|
apiSlice: baseApi,
|
|
24
|
-
pathPrefix: "/tenants",
|
|
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
|
-
|
|
72
|
+
export const KnowledgePage = chatbot.KnowledgePage;
|
|
73
|
+
export const ChatPage = chatbot.ChatPage;
|
|
74
|
+
export const ChatAnalyticsPage = chatbot.ChatAnalyticsPage;
|
|
36
75
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
97
|
+
## Drop into routes
|
|
45
98
|
|
|
46
99
|
```tsx
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
54
|
-
tenant + save handler since the Tenant model lives in the host):
|
|
111
|
+
## Drop into a settings page
|
|
55
112
|
|
|
56
113
|
```tsx
|
|
57
|
-
<
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
140
|
+
Then anyone can embed your chatbot on any HTML page:
|
|
80
141
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
|
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__ */
|
|
1300
|
+
return /* @__PURE__ */ jsx7(KnowledgePageBase, { tenantId });
|
|
1060
1301
|
};
|
|
1061
1302
|
const ChatPage = () => {
|
|
1062
1303
|
const tenantId = cfg.useTenantId();
|
|
1063
|
-
return /* @__PURE__ */
|
|
1304
|
+
return /* @__PURE__ */ jsx7(ChatPageBase, { tenantId });
|
|
1064
1305
|
};
|
|
1065
1306
|
const ChatAnalyticsPage = () => {
|
|
1066
1307
|
const tenantId = cfg.useTenantId();
|
|
1067
|
-
return /* @__PURE__ */
|
|
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