@xbrain-swe/nova-chat-widget 0.1.7 → 0.1.9
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/dist/index.cjs +142 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -37
- package/dist/index.d.ts +55 -37
- package/dist/index.js +142 -23
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1057,6 +1057,9 @@ async function consumeSse(response, handlers) {
|
|
|
1057
1057
|
continue;
|
|
1058
1058
|
}
|
|
1059
1059
|
switch (event.type) {
|
|
1060
|
+
case "start":
|
|
1061
|
+
if (typeof event.messageId === "string" && event.messageId) handlers.onMessageId?.(event.messageId);
|
|
1062
|
+
break;
|
|
1060
1063
|
case "text-start": {
|
|
1061
1064
|
const id = String(event.id);
|
|
1062
1065
|
textIndex.set(id, parts.length);
|
|
@@ -1133,7 +1136,7 @@ async function resumeToolResult(apiBaseUrl, apiKey, body, handlers) {
|
|
|
1133
1136
|
// src/use-chat.ts
|
|
1134
1137
|
var DEFAULT_CLIENT_ACTION_TIMEOUT_MS = 15e3;
|
|
1135
1138
|
function useXbNovaChat(options) {
|
|
1136
|
-
const { auth, trackRegistry, clientActionHandlers } = useXbNovaInternal();
|
|
1139
|
+
const { auth, trackRegistry, clientActionHandlers, visitorId } = useXbNovaInternal();
|
|
1137
1140
|
const botId = options?.botId ?? auth.botId;
|
|
1138
1141
|
const nudgeNodeId = options?.nudgeNodeId;
|
|
1139
1142
|
const { apiKey, apiBaseUrl } = auth;
|
|
@@ -1142,6 +1145,9 @@ function useXbNovaChat(options) {
|
|
|
1142
1145
|
const [busy, setBusy] = (0, import_react8.useState)(false);
|
|
1143
1146
|
const [error, setError] = (0, import_react8.useState)(null);
|
|
1144
1147
|
const [clientResults, setClientResults] = (0, import_react8.useState)({});
|
|
1148
|
+
const [feedback, setFeedbackState] = (0, import_react8.useState)({});
|
|
1149
|
+
const feedbackRef = (0, import_react8.useRef)(feedback);
|
|
1150
|
+
feedbackRef.current = feedback;
|
|
1145
1151
|
const dispatchedClientActionsRef = (0, import_react8.useRef)(/* @__PURE__ */ new Set());
|
|
1146
1152
|
const nudgeNodeIdRef = (0, import_react8.useRef)(nudgeNodeId);
|
|
1147
1153
|
nudgeNodeIdRef.current = nudgeNodeId;
|
|
@@ -1150,6 +1156,7 @@ function useXbNovaChat(options) {
|
|
|
1150
1156
|
setMessages([]);
|
|
1151
1157
|
setError(null);
|
|
1152
1158
|
setClientResults({});
|
|
1159
|
+
setFeedbackState({});
|
|
1153
1160
|
dispatchedClientActionsRef.current = /* @__PURE__ */ new Set();
|
|
1154
1161
|
fetch(`${apiBaseUrl}/v1/conversations`, {
|
|
1155
1162
|
method: "POST",
|
|
@@ -1159,9 +1166,42 @@ function useXbNovaChat(options) {
|
|
|
1159
1166
|
}, [apiBaseUrl, apiKey, botId]);
|
|
1160
1167
|
const appendToLastAssistant = (existingParts, newParts) => setMessages((prev) => {
|
|
1161
1168
|
const next = [...prev];
|
|
1162
|
-
next[next.length - 1] = { role: "assistant", parts: [...existingParts, ...newParts] };
|
|
1169
|
+
next[next.length - 1] = { ...next[next.length - 1], role: "assistant", parts: [...existingParts, ...newParts] };
|
|
1170
|
+
return next;
|
|
1171
|
+
});
|
|
1172
|
+
const setLastAssistantId = (id) => setMessages((prev) => {
|
|
1173
|
+
if (prev.length === 0) return prev;
|
|
1174
|
+
const next = [...prev];
|
|
1175
|
+
next[next.length - 1] = { ...next[next.length - 1], id };
|
|
1163
1176
|
return next;
|
|
1164
1177
|
});
|
|
1178
|
+
async function setFeedback(messageId, rating) {
|
|
1179
|
+
const conversationId = onboarding?.conversation_id;
|
|
1180
|
+
if (!conversationId || !messageId) return;
|
|
1181
|
+
const prevRating = feedbackRef.current[messageId] ?? null;
|
|
1182
|
+
const apply = (r) => setFeedbackState((prev) => {
|
|
1183
|
+
const next = { ...prev };
|
|
1184
|
+
if (r === null) delete next[messageId];
|
|
1185
|
+
else next[messageId] = r;
|
|
1186
|
+
return next;
|
|
1187
|
+
});
|
|
1188
|
+
apply(rating);
|
|
1189
|
+
try {
|
|
1190
|
+
const res = await fetch(
|
|
1191
|
+
`${apiBaseUrl}/v1/conversations/${encodeURIComponent(conversationId)}/messages/${encodeURIComponent(messageId)}/feedback`,
|
|
1192
|
+
{
|
|
1193
|
+
method: "PUT",
|
|
1194
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
1195
|
+
body: JSON.stringify({ rating, visitor_id: visitorId })
|
|
1196
|
+
}
|
|
1197
|
+
);
|
|
1198
|
+
if (!res.ok) throw new Error(`${res.status}`);
|
|
1199
|
+
options?.onFeedback?.({ conversationId, messageId, rating });
|
|
1200
|
+
} catch (e) {
|
|
1201
|
+
console.warn("[xbnova] \uD53C\uB4DC\uBC31 \uC800\uC7A5 \uC2E4\uD328", e);
|
|
1202
|
+
apply(prevRating);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1165
1205
|
async function runClientAction(spec, part, existingParts) {
|
|
1166
1206
|
if (spec.dismissSheet) options?.onDismissSheet?.();
|
|
1167
1207
|
let result;
|
|
@@ -1207,7 +1247,11 @@ function useXbNovaChat(options) {
|
|
|
1207
1247
|
tool_call_id: part.toolCallId,
|
|
1208
1248
|
result_text: resultText
|
|
1209
1249
|
},
|
|
1210
|
-
{
|
|
1250
|
+
{
|
|
1251
|
+
onPart: (newParts) => appendToLastAssistant(existingParts, newParts),
|
|
1252
|
+
onMessageId: setLastAssistantId,
|
|
1253
|
+
onError: setError
|
|
1254
|
+
}
|
|
1211
1255
|
);
|
|
1212
1256
|
} catch (e) {
|
|
1213
1257
|
setError(e instanceof Error ? e.message : String(e));
|
|
@@ -1240,7 +1284,7 @@ function useXbNovaChat(options) {
|
|
|
1240
1284
|
]);
|
|
1241
1285
|
const replaceLast = (parts) => setMessages((prev) => {
|
|
1242
1286
|
const next = [...prev];
|
|
1243
|
-
next[next.length - 1] = { role: "assistant", parts };
|
|
1287
|
+
next[next.length - 1] = { ...next[next.length - 1], role: "assistant", parts };
|
|
1244
1288
|
return next;
|
|
1245
1289
|
});
|
|
1246
1290
|
const focused = getFocusedTrackEntry(trackRegistry);
|
|
@@ -1256,7 +1300,7 @@ function useXbNovaChat(options) {
|
|
|
1256
1300
|
context,
|
|
1257
1301
|
nudge_node_id: nudgeNodeId ?? null
|
|
1258
1302
|
},
|
|
1259
|
-
{ onPart: replaceLast, onError: setError }
|
|
1303
|
+
{ onPart: replaceLast, onMessageId: setLastAssistantId, onError: setError }
|
|
1260
1304
|
);
|
|
1261
1305
|
} catch (e) {
|
|
1262
1306
|
setError(e instanceof Error ? e.message : String(e));
|
|
@@ -1264,7 +1308,7 @@ function useXbNovaChat(options) {
|
|
|
1264
1308
|
setBusy(false);
|
|
1265
1309
|
}
|
|
1266
1310
|
}
|
|
1267
|
-
return { onboarding, messages, busy, error, send, clientResults };
|
|
1311
|
+
return { onboarding, messages, busy, error, send, clientResults, feedback, setFeedback };
|
|
1268
1312
|
}
|
|
1269
1313
|
|
|
1270
1314
|
// src/views.tsx
|
|
@@ -1524,7 +1568,9 @@ function XbNovaChat({
|
|
|
1524
1568
|
defaultOpen = false,
|
|
1525
1569
|
idleText = "AI\uC5D0\uAC8C \uC774 \uCC28 \uBB3C\uC5B4\uBCF4\uAE30",
|
|
1526
1570
|
avatarSize = 28,
|
|
1527
|
-
onVerdictAction
|
|
1571
|
+
onVerdictAction,
|
|
1572
|
+
feedback: feedbackEnabled = true,
|
|
1573
|
+
onFeedback
|
|
1528
1574
|
}) {
|
|
1529
1575
|
const { auth, trackRegistry, factsRegistry, visitorId, clientActionHandlers, theme } = useXbNovaInternal();
|
|
1530
1576
|
const themeVars = {
|
|
@@ -1547,11 +1593,34 @@ function XbNovaChat({
|
|
|
1547
1593
|
const [input, setInput] = (0, import_react9.useState)("");
|
|
1548
1594
|
const bottomRef = (0, import_react9.useRef)(null);
|
|
1549
1595
|
const typedHintText = useTypewriter(hint?.text ?? "", hint?.triggerNodeId ?? "");
|
|
1550
|
-
const { onboarding, messages, busy, error, send, clientResults } = useXbNovaChat({
|
|
1596
|
+
const { onboarding, messages, busy, error, send, clientResults, feedback, setFeedback } = useXbNovaChat({
|
|
1597
|
+
onFeedback,
|
|
1551
1598
|
botId: activeBotId ?? botId,
|
|
1552
1599
|
nudgeNodeId: activeNudgeNodeId,
|
|
1553
1600
|
onDismissSheet: () => setOpen(false)
|
|
1554
1601
|
});
|
|
1602
|
+
const effectiveBotId = activeBotId ?? botId;
|
|
1603
|
+
const [cachedBranding, setCachedBranding] = (0, import_react9.useState)(null);
|
|
1604
|
+
(0, import_react9.useEffect)(() => {
|
|
1605
|
+
try {
|
|
1606
|
+
const raw = window.localStorage.getItem(`xbnova_branding:${effectiveBotId}`);
|
|
1607
|
+
setCachedBranding(raw ? JSON.parse(raw) : null);
|
|
1608
|
+
} catch {
|
|
1609
|
+
setCachedBranding(null);
|
|
1610
|
+
}
|
|
1611
|
+
}, [effectiveBotId]);
|
|
1612
|
+
(0, import_react9.useEffect)(() => {
|
|
1613
|
+
if (!onboarding?.branding) return;
|
|
1614
|
+
try {
|
|
1615
|
+
window.localStorage.setItem(
|
|
1616
|
+
`xbnova_branding:${effectiveBotId}`,
|
|
1617
|
+
JSON.stringify({ logo_url: onboarding.branding.logo_url, avatar_text: onboarding.branding.avatar_text })
|
|
1618
|
+
);
|
|
1619
|
+
} catch {
|
|
1620
|
+
}
|
|
1621
|
+
}, [onboarding?.branding, effectiveBotId]);
|
|
1622
|
+
const branding = onboarding?.branding ?? cachedBranding ?? void 0;
|
|
1623
|
+
const brandingReady = Boolean(onboarding) || cachedBranding !== null || Boolean(error);
|
|
1555
1624
|
(0, import_react9.useEffect)(() => {
|
|
1556
1625
|
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
1557
1626
|
}, [messages]);
|
|
@@ -1679,7 +1748,8 @@ function XbNovaChat({
|
|
|
1679
1748
|
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1680
1749
|
Avatar,
|
|
1681
1750
|
{
|
|
1682
|
-
branding
|
|
1751
|
+
branding,
|
|
1752
|
+
ready: brandingReady,
|
|
1683
1753
|
size: avatarSize,
|
|
1684
1754
|
className: "shadow-md",
|
|
1685
1755
|
gradient: avatarGradientStyle
|
|
@@ -1728,7 +1798,8 @@ function XbNovaChat({
|
|
|
1728
1798
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1729
1799
|
Avatar,
|
|
1730
1800
|
{
|
|
1731
|
-
branding
|
|
1801
|
+
branding,
|
|
1802
|
+
ready: brandingReady,
|
|
1732
1803
|
size: headerAvatarSize,
|
|
1733
1804
|
className: "shrink-0",
|
|
1734
1805
|
gradient: avatarGradientStyle
|
|
@@ -1775,7 +1846,14 @@ function XbNovaChat({
|
|
|
1775
1846
|
}
|
|
1776
1847
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ToolView, { part, viewRenderers, onVerdictAction }, j);
|
|
1777
1848
|
}),
|
|
1778
|
-
!hasVisiblePart(message.parts, clientActionHandlers, clientResults) && busy ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Bubble, { role: "assistant", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-neutral-400", children: "\uC0DD\uAC01\uD558\uB294 \uC911\u2026" }) }) : null
|
|
1849
|
+
!hasVisiblePart(message.parts, clientActionHandlers, clientResults) && busy ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Bubble, { role: "assistant", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-neutral-400", children: "\uC0DD\uAC01\uD558\uB294 \uC911\u2026" }) }) : null,
|
|
1850
|
+
feedbackEnabled && !busy && message.id && message.parts.some((p) => p.kind === "text" && p.text.trim()) ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1851
|
+
FeedbackButtons,
|
|
1852
|
+
{
|
|
1853
|
+
rating: feedback[message.id] ?? null,
|
|
1854
|
+
onChange: (r) => void setFeedback(message.id, r)
|
|
1855
|
+
}
|
|
1856
|
+
) : null
|
|
1779
1857
|
] }) }, i)),
|
|
1780
1858
|
error ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rounded-lg bg-red-50 px-3 py-2 text-xs text-red-700 dark:bg-red-950 dark:text-red-300", children: error }) : null,
|
|
1781
1859
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: bottomRef })
|
|
@@ -1848,6 +1926,7 @@ function XbNovaChat({
|
|
|
1848
1926
|
}
|
|
1849
1927
|
function Avatar({
|
|
1850
1928
|
branding,
|
|
1929
|
+
ready,
|
|
1851
1930
|
size,
|
|
1852
1931
|
className = "",
|
|
1853
1932
|
gradient
|
|
@@ -1855,27 +1934,67 @@ function Avatar({
|
|
|
1855
1934
|
const box = { width: size, height: size };
|
|
1856
1935
|
const logoUrl = branding?.logo_url?.trim() ?? "";
|
|
1857
1936
|
const [failedUrl, setFailedUrl] = (0, import_react9.useState)("");
|
|
1937
|
+
const [loadedUrl, setLoadedUrl] = (0, import_react9.useState)("");
|
|
1938
|
+
const base = `${className} relative flex flex-none items-center justify-center overflow-hidden rounded-full font-bold text-white`;
|
|
1939
|
+
if (!ready) {
|
|
1940
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: base, style: { ...gradient, ...box }, "aria-hidden": true });
|
|
1941
|
+
}
|
|
1858
1942
|
if (logoUrl && failedUrl !== logoUrl) {
|
|
1859
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1943
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: base, style: { ...gradient, ...box }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1860
1944
|
"img",
|
|
1861
1945
|
{
|
|
1862
1946
|
src: logoUrl,
|
|
1863
1947
|
alt: "",
|
|
1948
|
+
onLoad: () => setLoadedUrl(logoUrl),
|
|
1864
1949
|
onError: () => setFailedUrl(logoUrl),
|
|
1865
|
-
|
|
1866
|
-
|
|
1950
|
+
className: "absolute inset-0 h-full w-full rounded-full bg-white object-cover transition-opacity duration-200",
|
|
1951
|
+
style: { opacity: loadedUrl === logoUrl ? 1 : 0 }
|
|
1867
1952
|
}
|
|
1868
|
-
);
|
|
1953
|
+
) });
|
|
1869
1954
|
}
|
|
1870
1955
|
const text = branding?.avatar_text?.trim() || "AI";
|
|
1871
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1956
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: base, style: { ...gradient, ...box, fontSize: Math.max(10, Math.round(size * 0.4)) }, children: text });
|
|
1957
|
+
}
|
|
1958
|
+
var THUMB_UP_PATH = "M7 10v12M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z";
|
|
1959
|
+
var THUMB_DOWN_PATH = "M17 14V2M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z";
|
|
1960
|
+
function FeedbackButtons({
|
|
1961
|
+
rating,
|
|
1962
|
+
onChange
|
|
1963
|
+
}) {
|
|
1964
|
+
const item = (kind, label, path) => {
|
|
1965
|
+
const selected = rating === kind;
|
|
1966
|
+
const color = selected ? kind === "up" ? "var(--xbnova-accent-1, #4f46e5)" : "#dc2626" : void 0;
|
|
1967
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1968
|
+
"button",
|
|
1969
|
+
{
|
|
1970
|
+
type: "button",
|
|
1971
|
+
"aria-label": label,
|
|
1972
|
+
"aria-pressed": selected,
|
|
1973
|
+
onClick: () => onChange(selected ? null : kind),
|
|
1974
|
+
className: "flex h-6 w-6 items-center justify-center rounded-md text-neutral-400 transition-colors hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300",
|
|
1975
|
+
style: color ? { color } : void 0,
|
|
1976
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1977
|
+
"svg",
|
|
1978
|
+
{
|
|
1979
|
+
width: "16",
|
|
1980
|
+
height: "16",
|
|
1981
|
+
viewBox: "0 0 24 24",
|
|
1982
|
+
fill: selected ? "currentColor" : "none",
|
|
1983
|
+
stroke: "currentColor",
|
|
1984
|
+
strokeWidth: "2",
|
|
1985
|
+
strokeLinecap: "round",
|
|
1986
|
+
strokeLinejoin: "round",
|
|
1987
|
+
"aria-hidden": "true",
|
|
1988
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: path })
|
|
1989
|
+
}
|
|
1990
|
+
)
|
|
1991
|
+
}
|
|
1992
|
+
);
|
|
1993
|
+
};
|
|
1994
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-0.5 pl-1", children: [
|
|
1995
|
+
item("up", "\uB3C4\uC6C0\uC774 \uB410\uC5B4\uC694", THUMB_UP_PATH),
|
|
1996
|
+
item("down", "\uB3C4\uC6C0\uC774 \uC548 \uB410\uC5B4\uC694", THUMB_DOWN_PATH)
|
|
1997
|
+
] });
|
|
1879
1998
|
}
|
|
1880
1999
|
function Bubble({ role, children }) {
|
|
1881
2000
|
const mine = role === "user";
|