@xbrain-swe/nova-chat-widget 0.3.0-dev.17 → 0.3.0-dev.18
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 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +62 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -72,7 +72,7 @@ function useXbNovaInternal() {
|
|
|
72
72
|
return ctx;
|
|
73
73
|
}
|
|
74
74
|
var VISITOR_STORAGE_KEY = "xbnova_visitor_id";
|
|
75
|
-
var
|
|
75
|
+
var HOST_IDENTITY_WAIT_MS = 1e4;
|
|
76
76
|
function resolveVisitorId() {
|
|
77
77
|
if (typeof window === "undefined") return "";
|
|
78
78
|
try {
|
|
@@ -117,7 +117,7 @@ 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
|
|
120
|
+
const postEvent = (0, import_react.useCallback)((event, properties) => {
|
|
121
121
|
const { auth: a, visitorId: v, sessionId: sid } = latestRef.current;
|
|
122
122
|
if (!v) return;
|
|
123
123
|
const { conversation_id: conversationId, occurred_at: occurredAt, ...rest } = properties;
|
|
@@ -131,28 +131,45 @@ function InternalProvider({
|
|
|
131
131
|
visitor_id: v,
|
|
132
132
|
session_id: sid,
|
|
133
133
|
conversation_id: conversationId ?? null,
|
|
134
|
-
occurred_at: occurredAt
|
|
135
|
-
properties: { screen: a.screen, ...rest }
|
|
134
|
+
occurred_at: occurredAt,
|
|
135
|
+
properties: { screen: a.screen, ...a.eventProperties, ...rest }
|
|
136
136
|
})
|
|
137
137
|
}).catch(() => {
|
|
138
138
|
});
|
|
139
139
|
}, []);
|
|
140
|
-
const
|
|
141
|
-
const
|
|
140
|
+
const eventQueueRef = (0, import_react.useRef)([]);
|
|
141
|
+
const identitySettledRef = (0, import_react.useRef)(false);
|
|
142
|
+
const flushEvents = (0, import_react.useCallback)(() => {
|
|
143
|
+
identitySettledRef.current = true;
|
|
144
|
+
for (const [event, properties] of eventQueueRef.current.splice(0)) postEvent(event, properties);
|
|
145
|
+
}, [postEvent]);
|
|
146
|
+
const sendEvent = (0, import_react.useCallback)(
|
|
147
|
+
(event, properties = {}) => {
|
|
148
|
+
const stamped = { ...properties, occurred_at: properties.occurred_at ?? (/* @__PURE__ */ new Date()).toISOString() };
|
|
149
|
+
if (identitySettledRef.current) postEvent(event, stamped);
|
|
150
|
+
else eventQueueRef.current.push([event, stamped]);
|
|
151
|
+
},
|
|
152
|
+
[postEvent]
|
|
153
|
+
);
|
|
154
|
+
const hostIdentity = auth.userKey || sessionId;
|
|
142
155
|
(0, import_react.useEffect)(() => {
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
if (widgetViewSentRef.current) return;
|
|
146
|
-
widgetViewSentRef.current = true;
|
|
147
|
-
sendEvent("widget_view", { occurred_at: mountedAtRef.current });
|
|
148
|
-
};
|
|
149
|
-
if (sessionId) {
|
|
150
|
-
send();
|
|
156
|
+
if (hostIdentity) {
|
|
157
|
+
flushEvents();
|
|
151
158
|
return;
|
|
152
159
|
}
|
|
153
|
-
const timer = setTimeout(
|
|
154
|
-
|
|
155
|
-
|
|
160
|
+
const timer = setTimeout(flushEvents, HOST_IDENTITY_WAIT_MS);
|
|
161
|
+
window.addEventListener("pagehide", flushEvents);
|
|
162
|
+
return () => {
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
window.removeEventListener("pagehide", flushEvents);
|
|
165
|
+
};
|
|
166
|
+
}, [hostIdentity, flushEvents]);
|
|
167
|
+
const widgetViewSentRef = (0, import_react.useRef)(false);
|
|
168
|
+
(0, import_react.useEffect)(() => {
|
|
169
|
+
if (widgetViewSentRef.current) return;
|
|
170
|
+
widgetViewSentRef.current = true;
|
|
171
|
+
sendEvent("view_vehicle");
|
|
172
|
+
}, [sendEvent]);
|
|
156
173
|
const value = (0, import_react.useMemo)(
|
|
157
174
|
() => ({
|
|
158
175
|
auth,
|
|
@@ -748,7 +765,7 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
748
765
|
autoHideMs: messageNode.auto_hide_ms ?? 2e4,
|
|
749
766
|
quickReplies: messageNode.quick_replies ?? []
|
|
750
767
|
});
|
|
751
|
-
sendEvent("
|
|
768
|
+
sendEvent("nudge_AI_view", { nudge_node_id: messageNode.id, bot_id: botId });
|
|
752
769
|
};
|
|
753
770
|
const tryFireFrom = (node) => {
|
|
754
771
|
const nextEdge = (edgesFrom.get(node.id) ?? [])[0];
|
|
@@ -954,6 +971,7 @@ function XbNovaProvider({
|
|
|
954
971
|
userKey,
|
|
955
972
|
threadKey,
|
|
956
973
|
sessionKey,
|
|
974
|
+
eventProperties,
|
|
957
975
|
children
|
|
958
976
|
}) {
|
|
959
977
|
(0, import_react6.useEffect)(() => {
|
|
@@ -969,6 +987,7 @@ function XbNovaProvider({
|
|
|
969
987
|
(0, import_react6.useEffect)(() => {
|
|
970
988
|
if (screen) bumpVisitCount(screen);
|
|
971
989
|
}, [screen]);
|
|
990
|
+
const eventPropertiesJson = JSON.stringify(eventProperties ?? null);
|
|
972
991
|
const auth = (0, import_react6.useMemo)(
|
|
973
992
|
() => ({
|
|
974
993
|
apiKey,
|
|
@@ -978,9 +997,10 @@ function XbNovaProvider({
|
|
|
978
997
|
screen,
|
|
979
998
|
userKey: userKey || void 0,
|
|
980
999
|
threadKey: threadKey || void 0,
|
|
981
|
-
sessionKey: sessionKey || void 0
|
|
1000
|
+
sessionKey: sessionKey || void 0,
|
|
1001
|
+
eventProperties: JSON.parse(eventPropertiesJson) ?? void 0
|
|
982
1002
|
}),
|
|
983
|
-
[apiKey, botId, appId, apiBaseUrl, screen, userKey, threadKey, sessionKey]
|
|
1003
|
+
[apiKey, botId, appId, apiBaseUrl, screen, userKey, threadKey, sessionKey, eventPropertiesJson]
|
|
984
1004
|
);
|
|
985
1005
|
const resolvedTheme = (0, import_react6.useMemo)(
|
|
986
1006
|
() => ({ ...DEFAULT_THEME, ...theme }),
|
|
@@ -1224,6 +1244,10 @@ async function resumeToolResult(apiBaseUrl, apiKey, body, handlers) {
|
|
|
1224
1244
|
|
|
1225
1245
|
// src/use-chat.ts
|
|
1226
1246
|
var DEFAULT_CLIENT_ACTION_TIMEOUT_MS = 15e3;
|
|
1247
|
+
var CTA_CLICK_EVENTS = {
|
|
1248
|
+
suggest_consultation: { event: "apply_AI_click", ctaType: "consult" },
|
|
1249
|
+
suggest_finance_limit: { event: "finance_AI_click", ctaType: "finance" }
|
|
1250
|
+
};
|
|
1227
1251
|
function useXbNovaChat(options) {
|
|
1228
1252
|
const { auth, trackRegistry, contextRegistry, clientActionHandlers, visitorId, sessionId, sendEvent } = useXbNovaInternal();
|
|
1229
1253
|
const botId = options?.botId ?? auth.botId;
|
|
@@ -1328,13 +1352,14 @@ function useXbNovaChat(options) {
|
|
|
1328
1352
|
apply(prevRating);
|
|
1329
1353
|
}
|
|
1330
1354
|
}
|
|
1331
|
-
function postClientActionEvent(
|
|
1355
|
+
function postClientActionEvent(event, action, label, ctaType) {
|
|
1332
1356
|
const conversationId = conversationIdRef.current;
|
|
1333
1357
|
if (!conversationId) return;
|
|
1334
|
-
sendEvent(
|
|
1358
|
+
sendEvent(event, { conversation_id: conversationId, action, label, cta_type: ctaType });
|
|
1335
1359
|
}
|
|
1336
1360
|
function trackClientActionClick(action, label) {
|
|
1337
|
-
|
|
1361
|
+
const cta = CTA_CLICK_EVENTS[action];
|
|
1362
|
+
postClientActionEvent(cta?.event ?? "cta_click", action, label, cta?.ctaType);
|
|
1338
1363
|
}
|
|
1339
1364
|
async function runClientAction(spec, part, existingParts) {
|
|
1340
1365
|
if (spec.dismissSheet) options?.onDismissSheet?.();
|
|
@@ -1407,7 +1432,7 @@ function useXbNovaChat(options) {
|
|
|
1407
1432
|
void runClientAction(spec, part, last.parts);
|
|
1408
1433
|
}
|
|
1409
1434
|
}, [messages, clientActionHandlers, onboarding, apiBaseUrl, apiKey, botId]);
|
|
1410
|
-
async function send(text, nudgeOpening) {
|
|
1435
|
+
async function send(text, { nudgeOpening, inputType = "typed" } = {}) {
|
|
1411
1436
|
const trimmed = text.trim();
|
|
1412
1437
|
if (!trimmed || busy) return;
|
|
1413
1438
|
sendEvent("message_send", {
|
|
@@ -1415,6 +1440,13 @@ function useXbNovaChat(options) {
|
|
|
1415
1440
|
is_first: messages.length === 0,
|
|
1416
1441
|
length: trimmed.length
|
|
1417
1442
|
});
|
|
1443
|
+
let firstQuestionPending = messages.length === 0;
|
|
1444
|
+
const reportFirstQuestion = (conversationId) => {
|
|
1445
|
+
if (!firstQuestionPending) return;
|
|
1446
|
+
firstQuestionPending = false;
|
|
1447
|
+
sendEvent("question_AI_first", { conversation_id: conversationId, input_type: inputType });
|
|
1448
|
+
};
|
|
1449
|
+
if (conversationIdRef.current) reportFirstQuestion(conversationIdRef.current);
|
|
1418
1450
|
setError(null);
|
|
1419
1451
|
setBusy(true);
|
|
1420
1452
|
const resumedOpening = messages.length > 0 && nudgeOpening ? nudgeOpening : null;
|
|
@@ -1460,6 +1492,7 @@ function useXbNovaChat(options) {
|
|
|
1460
1492
|
onMessageId: setLastAssistantId,
|
|
1461
1493
|
onConversationId: (id) => {
|
|
1462
1494
|
conversationIdRef.current = id;
|
|
1495
|
+
reportFirstQuestion(id);
|
|
1463
1496
|
},
|
|
1464
1497
|
onError: setError
|
|
1465
1498
|
}
|
|
@@ -1841,11 +1874,12 @@ function XbNovaChat({
|
|
|
1841
1874
|
setActiveNudgeNodeId(consumed.messageNodeId);
|
|
1842
1875
|
setNudgeQuickReplies(consumed.quickReplies);
|
|
1843
1876
|
setNudgePrompt(consumed.text);
|
|
1844
|
-
sendEvent("
|
|
1877
|
+
sendEvent("nudge_AI_click", { nudge_node_id: consumed.messageNodeId, bot_id: consumed.botId });
|
|
1845
1878
|
} else {
|
|
1846
1879
|
setNudgeQuickReplies([]);
|
|
1847
1880
|
setNudgePrompt(null);
|
|
1848
1881
|
setActiveNudgeNodeId(void 0);
|
|
1882
|
+
sendEvent("chat_AI_open", { source: "direct" });
|
|
1849
1883
|
const focused = getFocusedTrackEntry(trackRegistry);
|
|
1850
1884
|
setOpeningLine(
|
|
1851
1885
|
focused ? `${focused.label ?? focused.region}\uC744(\uB97C) \uBCF4\uACE0 \uACC4\uC2DC\uB124\uC694. \uAD81\uAE08\uD558\uC2E0 \uC810\uC744 \uD3B8\uD558\uAC8C \uBB3C\uC5B4\uBCF4\uC138\uC694.` : null
|
|
@@ -1856,7 +1890,6 @@ function XbNovaChat({
|
|
|
1856
1890
|
markDisclosureSeen();
|
|
1857
1891
|
}
|
|
1858
1892
|
setOpen(true);
|
|
1859
|
-
sendEvent("chat_open", { from_nudge: Boolean(consumed) });
|
|
1860
1893
|
try {
|
|
1861
1894
|
onOpen?.({ fromNudge: Boolean(consumed), hintText: consumed?.text });
|
|
1862
1895
|
} catch (err) {
|
|
@@ -2076,7 +2109,7 @@ function XbNovaChat({
|
|
|
2076
2109
|
onClick: () => {
|
|
2077
2110
|
setNudgeQuickReplies([]);
|
|
2078
2111
|
setNudgePrompt(null);
|
|
2079
|
-
send(q, prompt);
|
|
2112
|
+
send(q, { nudgeOpening: prompt, inputType: "quick_reply" });
|
|
2080
2113
|
},
|
|
2081
2114
|
className: "xbnova-chip rounded-full border px-2.5 py-1 text-[11px] font-semibold transition",
|
|
2082
2115
|
children: q
|
|
@@ -2090,7 +2123,7 @@ function XbNovaChat({
|
|
|
2090
2123
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex flex-wrap gap-1.5 border-t border-neutral-200 px-3.5 py-2.5 dark:border-neutral-700", children: quickReplies.map((q) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2091
2124
|
"button",
|
|
2092
2125
|
{
|
|
2093
|
-
onClick: () => send(q),
|
|
2126
|
+
onClick: () => send(q, { inputType: "quick_reply" }),
|
|
2094
2127
|
className: "xbnova-chip rounded-full border px-2.5 py-1 text-[11px] font-semibold transition",
|
|
2095
2128
|
children: q
|
|
2096
2129
|
},
|
|
@@ -2104,7 +2137,7 @@ function XbNovaChat({
|
|
|
2104
2137
|
e.preventDefault();
|
|
2105
2138
|
setNudgeQuickReplies([]);
|
|
2106
2139
|
setNudgePrompt(null);
|
|
2107
|
-
send(input, messages.length > 0 ? nudgePrompt : null);
|
|
2140
|
+
send(input, { nudgeOpening: messages.length > 0 ? nudgePrompt : null });
|
|
2108
2141
|
setInput("");
|
|
2109
2142
|
},
|
|
2110
2143
|
className: "flex items-center gap-2 border-t border-neutral-200 px-3 py-2.5 dark:border-neutral-700",
|