@xbrain-swe/nova-chat-widget 0.2.4-dev.15 → 0.3.0-dev.17
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 +62 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +62 -42
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -117,23 +117,34 @@ function InternalProvider({
|
|
|
117
117
|
const sessionId = auth.sessionKey || null;
|
|
118
118
|
const latestRef = (0, import_react.useRef)({ auth, visitorId, sessionId });
|
|
119
119
|
latestRef.current = { auth, visitorId, sessionId };
|
|
120
|
-
const sendEvent = (0, import_react.useCallback)((
|
|
120
|
+
const sendEvent = (0, import_react.useCallback)((event, properties = {}) => {
|
|
121
121
|
const { auth: a, visitorId: v, sessionId: sid } = latestRef.current;
|
|
122
|
-
|
|
122
|
+
if (!v) return;
|
|
123
|
+
const { conversation_id: conversationId, occurred_at: occurredAt, ...rest } = properties;
|
|
124
|
+
fetch(`${a.apiBaseUrl}/v1/events`, {
|
|
123
125
|
method: "POST",
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
keepalive: true,
|
|
127
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${a.apiKey}` },
|
|
128
|
+
body: JSON.stringify({
|
|
129
|
+
app_id: a.appId,
|
|
130
|
+
event,
|
|
131
|
+
visitor_id: v,
|
|
132
|
+
session_id: sid,
|
|
133
|
+
conversation_id: conversationId ?? null,
|
|
134
|
+
occurred_at: occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
135
|
+
properties: { screen: a.screen, ...rest }
|
|
136
|
+
})
|
|
126
137
|
}).catch(() => {
|
|
127
138
|
});
|
|
128
139
|
}, []);
|
|
129
140
|
const widgetViewSentRef = (0, import_react.useRef)(false);
|
|
141
|
+
const mountedAtRef = (0, import_react.useRef)((/* @__PURE__ */ new Date()).toISOString());
|
|
130
142
|
(0, import_react.useEffect)(() => {
|
|
131
143
|
if (widgetViewSentRef.current) return;
|
|
132
144
|
const send = () => {
|
|
133
145
|
if (widgetViewSentRef.current) return;
|
|
134
146
|
widgetViewSentRef.current = true;
|
|
135
|
-
|
|
136
|
-
sendEvent("widget_view", screen ? { payload: { screen } } : {});
|
|
147
|
+
sendEvent("widget_view", { occurred_at: mountedAtRef.current });
|
|
137
148
|
};
|
|
138
149
|
if (sessionId) {
|
|
139
150
|
send();
|
|
@@ -1224,7 +1235,6 @@ function useXbNovaChat(options) {
|
|
|
1224
1235
|
const [resumed, setResumed] = (0, import_react8.useState)(false);
|
|
1225
1236
|
const [sentOpening, setSentOpening] = (0, import_react8.useState)(null);
|
|
1226
1237
|
const conversationIdRef = (0, import_react8.useRef)(null);
|
|
1227
|
-
const pendingOpeningRef = (0, import_react8.useRef)(null);
|
|
1228
1238
|
const messagesRef = (0, import_react8.useRef)(messages);
|
|
1229
1239
|
messagesRef.current = messages;
|
|
1230
1240
|
const [busy, setBusy] = (0, import_react8.useState)(false);
|
|
@@ -1247,7 +1257,6 @@ function useXbNovaChat(options) {
|
|
|
1247
1257
|
setFeedbackState({});
|
|
1248
1258
|
dispatchedClientActionsRef.current = /* @__PURE__ */ new Set();
|
|
1249
1259
|
conversationIdRef.current = null;
|
|
1250
|
-
pendingOpeningRef.current = null;
|
|
1251
1260
|
fetch(`${apiBaseUrl}/v1/conversations`, {
|
|
1252
1261
|
method: "POST",
|
|
1253
1262
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
@@ -1280,16 +1289,6 @@ function useXbNovaChat(options) {
|
|
|
1280
1289
|
cancelled = true;
|
|
1281
1290
|
};
|
|
1282
1291
|
}, [apiBaseUrl, apiKey, botId, userKey, threadKey]);
|
|
1283
|
-
const openingMessage = options?.openingMessage;
|
|
1284
|
-
(0, import_react8.useEffect)(() => {
|
|
1285
|
-
if (!onboarding || !nudgeNodeId || !openingMessage) return;
|
|
1286
|
-
if (messagesRef.current.length === 0) return;
|
|
1287
|
-
setMessages((prev) => [
|
|
1288
|
-
...prev,
|
|
1289
|
-
{ role: "assistant", parts: [{ kind: "text", id: `nudge:${nudgeNodeId}:${prev.length}`, text: openingMessage }] }
|
|
1290
|
-
]);
|
|
1291
|
-
pendingOpeningRef.current = openingMessage;
|
|
1292
|
-
}, [onboarding, nudgeNodeId, openingMessage]);
|
|
1293
1292
|
const appendToLastAssistant = (existingParts, newParts) => setMessages((prev) => {
|
|
1294
1293
|
const next = [...prev];
|
|
1295
1294
|
next[next.length - 1] = { ...next[next.length - 1], role: "assistant", parts: [...existingParts, ...newParts] };
|
|
@@ -1322,6 +1321,7 @@ function useXbNovaChat(options) {
|
|
|
1322
1321
|
}
|
|
1323
1322
|
);
|
|
1324
1323
|
if (!res.ok) throw new Error(`${res.status}`);
|
|
1324
|
+
sendEvent("feedback_submit", { conversation_id: conversationId, message_id: messageId, rating, reason });
|
|
1325
1325
|
options?.onFeedback?.({ conversationId, messageId, rating, reason });
|
|
1326
1326
|
} catch (e) {
|
|
1327
1327
|
console.warn("[xbnova] \uD53C\uB4DC\uBC31 \uC800\uC7A5 \uC2E4\uD328", e);
|
|
@@ -1331,7 +1331,7 @@ function useXbNovaChat(options) {
|
|
|
1331
1331
|
function postClientActionEvent(kind, action, label) {
|
|
1332
1332
|
const conversationId = conversationIdRef.current;
|
|
1333
1333
|
if (!conversationId) return;
|
|
1334
|
-
sendEvent(kind, { conversation_id: conversationId,
|
|
1334
|
+
sendEvent(kind, { conversation_id: conversationId, action, label });
|
|
1335
1335
|
}
|
|
1336
1336
|
function trackClientActionClick(action, label) {
|
|
1337
1337
|
postClientActionEvent("cta_click", action, label);
|
|
@@ -1407,13 +1407,20 @@ function useXbNovaChat(options) {
|
|
|
1407
1407
|
void runClientAction(spec, part, last.parts);
|
|
1408
1408
|
}
|
|
1409
1409
|
}, [messages, clientActionHandlers, onboarding, apiBaseUrl, apiKey, botId]);
|
|
1410
|
-
async function send(text) {
|
|
1410
|
+
async function send(text, nudgeOpening) {
|
|
1411
1411
|
const trimmed = text.trim();
|
|
1412
1412
|
if (!trimmed || busy) return;
|
|
1413
|
+
sendEvent("message_send", {
|
|
1414
|
+
conversation_id: conversationIdRef.current,
|
|
1415
|
+
is_first: messages.length === 0,
|
|
1416
|
+
length: trimmed.length
|
|
1417
|
+
});
|
|
1413
1418
|
setError(null);
|
|
1414
1419
|
setBusy(true);
|
|
1420
|
+
const resumedOpening = messages.length > 0 && nudgeOpening ? nudgeOpening : null;
|
|
1415
1421
|
setMessages((prev) => [
|
|
1416
1422
|
...prev,
|
|
1423
|
+
...resumedOpening ? [{ role: "assistant", parts: [{ kind: "text", id: `nudge:${prev.length}`, text: resumedOpening }] }] : [],
|
|
1417
1424
|
{ role: "user", parts: [{ kind: "text", id: "u", text: trimmed }] },
|
|
1418
1425
|
{ role: "assistant", parts: [] }
|
|
1419
1426
|
]);
|
|
@@ -1426,14 +1433,13 @@ function useXbNovaChat(options) {
|
|
|
1426
1433
|
const notes = collectContextEntries(contextRegistry);
|
|
1427
1434
|
const screen = focused ? { region: focused.region, data: resolveFactValue(focused.data) } : {};
|
|
1428
1435
|
const context = focused || notes.length ? { ...screen, ...notes.length ? { notes } : {} } : void 0;
|
|
1429
|
-
let
|
|
1436
|
+
let openingMessage;
|
|
1430
1437
|
if (messages.length === 0) {
|
|
1431
|
-
|
|
1432
|
-
setSentOpening(
|
|
1438
|
+
openingMessage = (options?.openingMessage ?? onboarding?.greeting) || null;
|
|
1439
|
+
setSentOpening(openingMessage);
|
|
1433
1440
|
} else {
|
|
1434
|
-
|
|
1441
|
+
openingMessage = resumedOpening;
|
|
1435
1442
|
}
|
|
1436
|
-
pendingOpeningRef.current = null;
|
|
1437
1443
|
try {
|
|
1438
1444
|
await streamChat(
|
|
1439
1445
|
apiBaseUrl,
|
|
@@ -1444,7 +1450,7 @@ function useXbNovaChat(options) {
|
|
|
1444
1450
|
conversation_id: conversationIdRef.current,
|
|
1445
1451
|
context,
|
|
1446
1452
|
nudge_node_id: nudgeNodeId ?? null,
|
|
1447
|
-
opening_message:
|
|
1453
|
+
opening_message: openingMessage,
|
|
1448
1454
|
visitor_id: userKey || null,
|
|
1449
1455
|
thread_key: threadKey ?? null,
|
|
1450
1456
|
session_id: sessionId
|
|
@@ -1464,7 +1470,7 @@ function useXbNovaChat(options) {
|
|
|
1464
1470
|
setBusy(false);
|
|
1465
1471
|
}
|
|
1466
1472
|
}
|
|
1467
|
-
const opening = resumed ? null : sentOpening
|
|
1473
|
+
const opening = resumed ? null : messages.length > 0 ? sentOpening : options?.openingMessage ?? onboarding?.greeting ?? null;
|
|
1468
1474
|
return {
|
|
1469
1475
|
onboarding,
|
|
1470
1476
|
opening,
|
|
@@ -1764,6 +1770,7 @@ function XbNovaChat({
|
|
|
1764
1770
|
const [activeBotId, setActiveBotId] = (0, import_react9.useState)(void 0);
|
|
1765
1771
|
const [activeNudgeNodeId, setActiveNudgeNodeId] = (0, import_react9.useState)(void 0);
|
|
1766
1772
|
const [nudgeQuickReplies, setNudgeQuickReplies] = (0, import_react9.useState)([]);
|
|
1773
|
+
const [nudgePrompt, setNudgePrompt] = (0, import_react9.useState)(null);
|
|
1767
1774
|
const [openingLine, setOpeningLine] = (0, import_react9.useState)(null);
|
|
1768
1775
|
const [showDisclosure, setShowDisclosure] = (0, import_react9.useState)(false);
|
|
1769
1776
|
const [input, setInput] = (0, import_react9.useState)("");
|
|
@@ -1833,9 +1840,11 @@ function XbNovaChat({
|
|
|
1833
1840
|
setOpeningLine(consumed.text);
|
|
1834
1841
|
setActiveNudgeNodeId(consumed.messageNodeId);
|
|
1835
1842
|
setNudgeQuickReplies(consumed.quickReplies);
|
|
1843
|
+
setNudgePrompt(consumed.text);
|
|
1836
1844
|
sendEvent("nudge_open", { nudge_node_id: consumed.messageNodeId, bot_id: consumed.botId });
|
|
1837
1845
|
} else {
|
|
1838
1846
|
setNudgeQuickReplies([]);
|
|
1847
|
+
setNudgePrompt(null);
|
|
1839
1848
|
setActiveNudgeNodeId(void 0);
|
|
1840
1849
|
const focused = getFocusedTrackEntry(trackRegistry);
|
|
1841
1850
|
setOpeningLine(
|
|
@@ -1847,12 +1856,17 @@ function XbNovaChat({
|
|
|
1847
1856
|
markDisclosureSeen();
|
|
1848
1857
|
}
|
|
1849
1858
|
setOpen(true);
|
|
1859
|
+
sendEvent("chat_open", { from_nudge: Boolean(consumed) });
|
|
1850
1860
|
try {
|
|
1851
1861
|
onOpen?.({ fromNudge: Boolean(consumed), hintText: consumed?.text });
|
|
1852
1862
|
} catch (err) {
|
|
1853
1863
|
console.warn("[XbNova] onOpen \uCF5C\uBC31 \uC624\uB958", err);
|
|
1854
1864
|
}
|
|
1855
1865
|
}
|
|
1866
|
+
function closeSheet() {
|
|
1867
|
+
setOpen(false);
|
|
1868
|
+
sendEvent("chat_close");
|
|
1869
|
+
}
|
|
1856
1870
|
if (!open) {
|
|
1857
1871
|
if (barHidden) return null;
|
|
1858
1872
|
const showHint = Boolean(hint && hintsEnabled);
|
|
@@ -1957,7 +1971,7 @@ function XbNovaChat({
|
|
|
1957
1971
|
transition: "opacity 260ms ease-out",
|
|
1958
1972
|
...backdropStyle
|
|
1959
1973
|
},
|
|
1960
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "h-full w-full bg-black/45", onClick:
|
|
1974
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "h-full w-full bg-black/45", onClick: closeSheet })
|
|
1961
1975
|
}
|
|
1962
1976
|
) : null,
|
|
1963
1977
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
@@ -1993,7 +2007,7 @@ function XbNovaChat({
|
|
|
1993
2007
|
"button",
|
|
1994
2008
|
{
|
|
1995
2009
|
type: "button",
|
|
1996
|
-
onClick:
|
|
2010
|
+
onClick: closeSheet,
|
|
1997
2011
|
"aria-label": "\uCC44\uD305 \uB2EB\uAE30",
|
|
1998
2012
|
className: "shrink-0 text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-200",
|
|
1999
2013
|
children: "\xD7"
|
|
@@ -2052,19 +2066,24 @@ function XbNovaChat({
|
|
|
2052
2066
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: bottomRef })
|
|
2053
2067
|
] }),
|
|
2054
2068
|
(() => {
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
{
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2069
|
+
const prompt = messages.length > 0 ? nudgePrompt : null;
|
|
2070
|
+
if (prompt || nudgeQuickReplies.length > 0) {
|
|
2071
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "space-y-2 border-t border-neutral-200 px-3.5 py-2.5 dark:border-neutral-700", children: [
|
|
2072
|
+
prompt ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-sm font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AssistantMarkdown, { text: prompt }) }) : null,
|
|
2073
|
+
nudgeQuickReplies.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex flex-wrap gap-1.5", children: nudgeQuickReplies.map((q) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2074
|
+
"button",
|
|
2075
|
+
{
|
|
2076
|
+
onClick: () => {
|
|
2077
|
+
setNudgeQuickReplies([]);
|
|
2078
|
+
setNudgePrompt(null);
|
|
2079
|
+
send(q, prompt);
|
|
2080
|
+
},
|
|
2081
|
+
className: "xbnova-chip rounded-full border px-2.5 py-1 text-[11px] font-semibold transition",
|
|
2082
|
+
children: q
|
|
2062
2083
|
},
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
q
|
|
2067
|
-
)) });
|
|
2084
|
+
q
|
|
2085
|
+
)) }) : null
|
|
2086
|
+
] });
|
|
2068
2087
|
}
|
|
2069
2088
|
const quickReplies = onboarding?.quick_replies ?? [];
|
|
2070
2089
|
if (messages.length !== 0 || quickReplies.length === 0) return null;
|
|
@@ -2084,7 +2103,8 @@ function XbNovaChat({
|
|
|
2084
2103
|
onSubmit: (e) => {
|
|
2085
2104
|
e.preventDefault();
|
|
2086
2105
|
setNudgeQuickReplies([]);
|
|
2087
|
-
|
|
2106
|
+
setNudgePrompt(null);
|
|
2107
|
+
send(input, messages.length > 0 ? nudgePrompt : null);
|
|
2088
2108
|
setInput("");
|
|
2089
2109
|
},
|
|
2090
2110
|
className: "flex items-center gap-2 border-t border-neutral-200 px-3 py-2.5 dark:border-neutral-700",
|