@xbrain-swe/nova-chat-widget 0.1.10 → 0.2.4-dev.15
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 +260 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -5
- package/dist/index.d.ts +57 -5
- package/dist/index.js +307 -104
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/provider.tsx
|
|
4
|
-
import { useEffect as
|
|
4
|
+
import { useEffect as useEffect6, useMemo as useMemo2 } from "react";
|
|
5
5
|
|
|
6
6
|
// src/client-actions.tsx
|
|
7
|
-
import { useEffect, useRef as useRef2 } from "react";
|
|
7
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
8
8
|
|
|
9
9
|
// src/internal-context.tsx
|
|
10
|
-
import { createContext, useContext, useMemo, useRef } from "react";
|
|
10
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef } from "react";
|
|
11
11
|
import { jsx } from "react/jsx-runtime";
|
|
12
12
|
var DEFAULT_THEME = {
|
|
13
13
|
accentFrom: "#4f46e5",
|
|
@@ -25,6 +25,7 @@ function useXbNovaInternal() {
|
|
|
25
25
|
return ctx;
|
|
26
26
|
}
|
|
27
27
|
var VISITOR_STORAGE_KEY = "xbnova_visitor_id";
|
|
28
|
+
var WIDGET_VIEW_WAIT_MS = 1e4;
|
|
28
29
|
function resolveVisitorId() {
|
|
29
30
|
if (typeof window === "undefined") return "";
|
|
30
31
|
try {
|
|
@@ -40,6 +41,17 @@ function resolveVisitorId() {
|
|
|
40
41
|
function resolveFactValue(value) {
|
|
41
42
|
return typeof value === "function" ? value() : value;
|
|
42
43
|
}
|
|
44
|
+
function collectContextEntries(registry) {
|
|
45
|
+
const out = [];
|
|
46
|
+
for (const source of registry.values()) {
|
|
47
|
+
const entries = typeof source === "function" ? source() : source;
|
|
48
|
+
if (!Array.isArray(entries)) continue;
|
|
49
|
+
for (const e of entries) {
|
|
50
|
+
if (e && typeof e.title === "string" && typeof e.description === "string") out.push(e);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
43
55
|
function InternalProvider({
|
|
44
56
|
auth,
|
|
45
57
|
clientActionHandlers,
|
|
@@ -48,21 +60,54 @@ function InternalProvider({
|
|
|
48
60
|
}) {
|
|
49
61
|
const trackRegistry = useRef(/* @__PURE__ */ new Map()).current;
|
|
50
62
|
const factsRegistry = useRef(/* @__PURE__ */ new Map()).current;
|
|
63
|
+
const contextRegistry = useRef(/* @__PURE__ */ new Map()).current;
|
|
51
64
|
const emptyHandlers = useRef(/* @__PURE__ */ new Map()).current;
|
|
52
65
|
const visitorIdRef = useRef(null);
|
|
53
66
|
if (visitorIdRef.current === null) {
|
|
54
67
|
visitorIdRef.current = resolveVisitorId();
|
|
55
68
|
}
|
|
69
|
+
const visitorId = auth.userKey || visitorIdRef.current;
|
|
70
|
+
const sessionId = auth.sessionKey || null;
|
|
71
|
+
const latestRef = useRef({ auth, visitorId, sessionId });
|
|
72
|
+
latestRef.current = { auth, visitorId, sessionId };
|
|
73
|
+
const sendEvent = useCallback((kind, fields = {}) => {
|
|
74
|
+
const { auth: a, visitorId: v, sessionId: sid } = latestRef.current;
|
|
75
|
+
fetch(`${a.apiBaseUrl}/v1/nudge-events`, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: { "Content-Type": "application/json" },
|
|
78
|
+
body: JSON.stringify({ app_id: a.appId, kind, visitor_id: v || null, session_id: sid, ...fields })
|
|
79
|
+
}).catch(() => {
|
|
80
|
+
});
|
|
81
|
+
}, []);
|
|
82
|
+
const widgetViewSentRef = useRef(false);
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (widgetViewSentRef.current) return;
|
|
85
|
+
const send = () => {
|
|
86
|
+
if (widgetViewSentRef.current) return;
|
|
87
|
+
widgetViewSentRef.current = true;
|
|
88
|
+
const screen = latestRef.current.auth.screen;
|
|
89
|
+
sendEvent("widget_view", screen ? { payload: { screen } } : {});
|
|
90
|
+
};
|
|
91
|
+
if (sessionId) {
|
|
92
|
+
send();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const timer = setTimeout(send, WIDGET_VIEW_WAIT_MS);
|
|
96
|
+
return () => clearTimeout(timer);
|
|
97
|
+
}, [sessionId, sendEvent]);
|
|
56
98
|
const value = useMemo(
|
|
57
99
|
() => ({
|
|
58
100
|
auth,
|
|
59
101
|
trackRegistry,
|
|
60
102
|
factsRegistry,
|
|
103
|
+
contextRegistry,
|
|
61
104
|
clientActionHandlers: clientActionHandlers ?? emptyHandlers,
|
|
62
|
-
visitorId
|
|
105
|
+
visitorId,
|
|
106
|
+
sessionId,
|
|
107
|
+
sendEvent,
|
|
63
108
|
theme
|
|
64
109
|
}),
|
|
65
|
-
[auth, trackRegistry, factsRegistry, clientActionHandlers, emptyHandlers, theme]
|
|
110
|
+
[auth, trackRegistry, factsRegistry, contextRegistry, clientActionHandlers, emptyHandlers, visitorId, sessionId, sendEvent, theme]
|
|
66
111
|
);
|
|
67
112
|
return /* @__PURE__ */ jsx(InternalContext.Provider, { value, children });
|
|
68
113
|
}
|
|
@@ -83,7 +128,7 @@ function XbNovaClientActionSync({ actions }) {
|
|
|
83
128
|
const { auth } = useXbNovaInternal();
|
|
84
129
|
const { apiKey, apiBaseUrl, appId } = auth;
|
|
85
130
|
const lastSyncedRef = useRef2(null);
|
|
86
|
-
|
|
131
|
+
useEffect2(() => {
|
|
87
132
|
if (!actions || Object.keys(actions).length === 0) return;
|
|
88
133
|
const serialized = JSON.stringify(toSyncPayload(actions));
|
|
89
134
|
if (serialized === lastSyncedRef.current) return;
|
|
@@ -137,7 +182,7 @@ function peekVisitCount(screen) {
|
|
|
137
182
|
}
|
|
138
183
|
|
|
139
184
|
// src/nudge-context.tsx
|
|
140
|
-
import { createContext as createContext2, useCallback, useContext as useContext2, useEffect as
|
|
185
|
+
import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect as useEffect3, useRef as useRef3, useState } from "react";
|
|
141
186
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
142
187
|
var NudgeContext = createContext2(null);
|
|
143
188
|
var HINTS_DISABLED_KEY = "xbnova_hints_disabled";
|
|
@@ -181,43 +226,43 @@ function NudgeStateProvider({ children }) {
|
|
|
181
226
|
const [hintsEnabled, setHintsEnabled] = useState(() => !readFlag(HINTS_DISABLED_KEY));
|
|
182
227
|
const [barHidden, setBarHidden] = useState(() => readFlag(BAR_HIDDEN_KEY));
|
|
183
228
|
const streakRef = useRef3(readStreak());
|
|
184
|
-
const disableHints =
|
|
229
|
+
const disableHints = useCallback2(() => {
|
|
185
230
|
setHintsEnabled(false);
|
|
186
231
|
writeFlag(HINTS_DISABLED_KEY, true);
|
|
187
232
|
}, []);
|
|
188
|
-
const enableHints =
|
|
233
|
+
const enableHints = useCallback2(() => {
|
|
189
234
|
setHintsEnabled(true);
|
|
190
235
|
writeFlag(HINTS_DISABLED_KEY, false);
|
|
191
236
|
streakRef.current = 0;
|
|
192
237
|
writeStreak(0);
|
|
193
238
|
}, []);
|
|
194
|
-
const hideBar =
|
|
239
|
+
const hideBar = useCallback2(() => {
|
|
195
240
|
setBarHidden(true);
|
|
196
241
|
writeFlag(BAR_HIDDEN_KEY, true);
|
|
197
242
|
}, []);
|
|
198
|
-
const showBar =
|
|
243
|
+
const showBar = useCallback2(() => {
|
|
199
244
|
setBarHidden(false);
|
|
200
245
|
writeFlag(BAR_HIDDEN_KEY, false);
|
|
201
246
|
}, []);
|
|
202
|
-
const offerHint =
|
|
247
|
+
const offerHint = useCallback2((next) => {
|
|
203
248
|
setHint((current) => current ? current : next);
|
|
204
249
|
}, []);
|
|
205
|
-
|
|
250
|
+
useEffect3(() => {
|
|
206
251
|
if (hint) setLastHint(hint);
|
|
207
252
|
}, [hint]);
|
|
208
|
-
const dismissHint =
|
|
253
|
+
const dismissHint = useCallback2(() => {
|
|
209
254
|
const limit = hint?.autoDisableAfterDismissals ?? DEFAULT_DISMISS_STREAK_LIMIT;
|
|
210
255
|
setHint(null);
|
|
211
256
|
streakRef.current += 1;
|
|
212
257
|
writeStreak(streakRef.current);
|
|
213
258
|
if (streakRef.current >= limit) disableHints();
|
|
214
259
|
}, [hint, disableHints]);
|
|
215
|
-
const openHint =
|
|
260
|
+
const openHint = useCallback2(() => {
|
|
216
261
|
setHint(null);
|
|
217
262
|
streakRef.current = 0;
|
|
218
263
|
writeStreak(0);
|
|
219
264
|
}, []);
|
|
220
|
-
const autoHideHint =
|
|
265
|
+
const autoHideHint = useCallback2(() => {
|
|
221
266
|
setHint(null);
|
|
222
267
|
}, []);
|
|
223
268
|
return /* @__PURE__ */ jsx2(
|
|
@@ -254,10 +299,10 @@ function useXbNovaSettings() {
|
|
|
254
299
|
}
|
|
255
300
|
|
|
256
301
|
// src/nudge-loop.tsx
|
|
257
|
-
import { useCallback as
|
|
302
|
+
import { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef5 } from "react";
|
|
258
303
|
|
|
259
304
|
// src/track.tsx
|
|
260
|
-
import { useCallback as
|
|
305
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4 } from "react";
|
|
261
306
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
262
307
|
var VISIBILITY_THRESHOLD = 0;
|
|
263
308
|
function finalizeIfVisible(registry, region) {
|
|
@@ -274,7 +319,7 @@ function useXbNovaTrack(region, data, label) {
|
|
|
274
319
|
dataRef.current = data;
|
|
275
320
|
const labelRef = useRef4(label);
|
|
276
321
|
labelRef.current = label;
|
|
277
|
-
|
|
322
|
+
useEffect4(() => {
|
|
278
323
|
const entry = trackRegistry.get(region);
|
|
279
324
|
if (entry) {
|
|
280
325
|
entry.data = data;
|
|
@@ -283,7 +328,7 @@ function useXbNovaTrack(region, data, label) {
|
|
|
283
328
|
trackRegistry.set(region, { region, data, label, enteredAt: null, totalMs: 0, node: null });
|
|
284
329
|
}
|
|
285
330
|
}, [region, data, label, trackRegistry]);
|
|
286
|
-
|
|
331
|
+
useEffect4(() => {
|
|
287
332
|
return () => {
|
|
288
333
|
observerRef.current?.disconnect();
|
|
289
334
|
finalizeIfVisible(trackRegistry, region);
|
|
@@ -291,7 +336,7 @@ function useXbNovaTrack(region, data, label) {
|
|
|
291
336
|
if (entry) entry.node = null;
|
|
292
337
|
};
|
|
293
338
|
}, [region, trackRegistry]);
|
|
294
|
-
return
|
|
339
|
+
return useCallback3(
|
|
295
340
|
(node) => {
|
|
296
341
|
observerRef.current?.disconnect();
|
|
297
342
|
observerRef.current = null;
|
|
@@ -365,7 +410,7 @@ function getFocusedTrackEntry(trackRegistry) {
|
|
|
365
410
|
}
|
|
366
411
|
|
|
367
412
|
// src/nudge-loop.tsx
|
|
368
|
-
var EVAL_INTERVAL_MS =
|
|
413
|
+
var EVAL_INTERVAL_MS = 250;
|
|
369
414
|
var XBNOVA_EVENT_NAME = "xbnova:event";
|
|
370
415
|
var SESSION_COUNT_KEY_PREFIX = "xbnova_hint_session_count:";
|
|
371
416
|
var SCROLL_SETTLE_MS = 500;
|
|
@@ -558,7 +603,7 @@ function clearDebugOverlay(refs) {
|
|
|
558
603
|
refs.bandLinesRef.current.clear();
|
|
559
604
|
}
|
|
560
605
|
function XbNovaNudgeLoop({ appId }) {
|
|
561
|
-
const { auth, trackRegistry, factsRegistry,
|
|
606
|
+
const { auth, trackRegistry, factsRegistry, sendEvent } = useXbNovaInternal();
|
|
562
607
|
const { apiKey, apiBaseUrl, screen } = auth;
|
|
563
608
|
const { hint, offerHint, autoHideHint, hintsEnabled } = useNudgeState();
|
|
564
609
|
const graphRef = useRef5(null);
|
|
@@ -573,7 +618,7 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
573
618
|
hintRef.current = hint;
|
|
574
619
|
const hintsEnabledRef = useRef5(hintsEnabled);
|
|
575
620
|
hintsEnabledRef.current = hintsEnabled;
|
|
576
|
-
|
|
621
|
+
useEffect5(() => {
|
|
577
622
|
let cancelled = false;
|
|
578
623
|
fetch(`${apiBaseUrl}/v1/apps/${appId}/nudge-graph`, {
|
|
579
624
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
@@ -585,14 +630,16 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
585
630
|
cancelled = true;
|
|
586
631
|
};
|
|
587
632
|
}, [apiBaseUrl, apiKey, appId]);
|
|
588
|
-
const evaluate =
|
|
633
|
+
const evaluate = useCallback4(
|
|
589
634
|
(firedEventName) => {
|
|
590
635
|
const graph = graphRef.current;
|
|
591
636
|
const debugEnabled = isDebugEnabled();
|
|
592
637
|
const debugRefs = { panelRef: debugPanelRef, boxesRef: debugBoxesRef, bandLinesRef: debugBandLinesRef };
|
|
593
638
|
if (!debugEnabled && debugPanelRef.current) clearDebugOverlay(debugRefs);
|
|
639
|
+
const tickDebug = debugEnabled ? (msg) => console.debug(msg) : () => {
|
|
640
|
+
};
|
|
594
641
|
if (!graph) {
|
|
595
|
-
|
|
642
|
+
tickDebug("[XbNova] nudge \uD3C9\uAC00 \uAC74\uB108\uB700: \uADF8\uB798\uD504\uB97C \uC544\uC9C1 \uBABB \uBD88\uB7EC\uC634");
|
|
596
643
|
return;
|
|
597
644
|
}
|
|
598
645
|
const canOffer = !hintRef.current && hintsEnabledRef.current;
|
|
@@ -643,18 +690,7 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
643
690
|
autoHideMs: messageNode.auto_hide_ms ?? 2e4,
|
|
644
691
|
quickReplies: messageNode.quick_replies ?? []
|
|
645
692
|
});
|
|
646
|
-
|
|
647
|
-
method: "POST",
|
|
648
|
-
headers: { "Content-Type": "application/json" },
|
|
649
|
-
body: JSON.stringify({
|
|
650
|
-
app_id: appId,
|
|
651
|
-
nudge_node_id: messageNode.id,
|
|
652
|
-
kind: "nudge_impression",
|
|
653
|
-
bot_id: botId,
|
|
654
|
-
visitor_id: visitorId
|
|
655
|
-
})
|
|
656
|
-
}).catch(() => {
|
|
657
|
-
});
|
|
693
|
+
sendEvent("nudge_impression", { nudge_node_id: messageNode.id, bot_id: botId });
|
|
658
694
|
};
|
|
659
695
|
const tryFireFrom = (node) => {
|
|
660
696
|
const nextEdge = (edgesFrom.get(node.id) ?? [])[0];
|
|
@@ -682,7 +718,7 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
682
718
|
const maxPerSession = node.max_per_session === 0 ? Infinity : node.max_per_session ?? 3;
|
|
683
719
|
const sessionCount = peekSessionCount(node.id);
|
|
684
720
|
if (sessionCount >= maxPerSession) {
|
|
685
|
-
|
|
721
|
+
tickDebug(`[XbNova] \uD2B8\uB9AC\uAC70 ${node.id}(${node.trigger_type}) \uAC74\uB108\uB700: \uC138\uC158\uB2F9 \uC0C1\uD55C(${maxPerSession}) \uB3C4\uB2EC`);
|
|
686
722
|
if (debugEnabled && node.trigger_type === "dwell") {
|
|
687
723
|
debugLines.push(`- [${node.id}] \uC138\uC158\uB2F9 \uC0C1\uD55C(${maxPerSession}) \uB3C4\uB2EC \u2014 \uB354 \uC548 \uC7BC`);
|
|
688
724
|
}
|
|
@@ -717,7 +753,7 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
717
753
|
overlapPx = status.overlapPx;
|
|
718
754
|
statusDetail = `rect=[top=${status.rectTop.toFixed(0)}, bottom=${status.rectBottom.toFixed(0)}] band=[top=${status.bandTop.toFixed(0)}, bottom=${status.bandBottom.toFixed(0)}] overlapPx=${overlapPx.toFixed(0)} inBand=${inBand}`;
|
|
719
755
|
}
|
|
720
|
-
|
|
756
|
+
tickDebug(
|
|
721
757
|
`[XbNova] band dwell \uD2B8\uB9AC\uAC70=${node.id} region="${region}" band%=${fromPercent}~${toPercent} | ${statusDetail}`
|
|
722
758
|
);
|
|
723
759
|
dwellRecords.push({ node, state, requiredMs, region, found, screenMatches, inBand, overlapPx });
|
|
@@ -726,7 +762,7 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
726
762
|
const fires = triggerFires(node, { screen, firedEventName });
|
|
727
763
|
if (!fires) continue;
|
|
728
764
|
if (!canOffer) {
|
|
729
|
-
|
|
765
|
+
tickDebug(`[XbNova] \uD2B8\uB9AC\uAC70 ${node.id} \uC870\uAC74\uC740 \uCDA9\uC871\uD588\uC9C0\uB9CC \uC0C8 \uD78C\uD2B8 \uD45C\uC2DC\uB294 \uC7A0\uAE50 \uB9C9\uD798`);
|
|
730
766
|
continue;
|
|
731
767
|
}
|
|
732
768
|
if (tryFireFrom(node)) {
|
|
@@ -826,9 +862,9 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
826
862
|
ensureDebugPanel(debugPanelRef, debugCollapsedRef).body.textContent = debugLines.join("\n");
|
|
827
863
|
}
|
|
828
864
|
},
|
|
829
|
-
[apiBaseUrl, appId, autoHideHint, factsRegistry, offerHint, screen, trackRegistry,
|
|
865
|
+
[apiBaseUrl, appId, autoHideHint, factsRegistry, offerHint, screen, trackRegistry, sendEvent]
|
|
830
866
|
);
|
|
831
|
-
|
|
867
|
+
useEffect5(() => {
|
|
832
868
|
const interval = window.setInterval(() => evaluate(null), EVAL_INTERVAL_MS);
|
|
833
869
|
const onEvent = (e) => evaluate(e.detail.eventName);
|
|
834
870
|
window.addEventListener(XBNOVA_EVENT_NAME, onEvent);
|
|
@@ -857,24 +893,36 @@ function XbNovaProvider({
|
|
|
857
893
|
apiBaseUrl = DEFAULT_API_BASE_URL,
|
|
858
894
|
clientActions,
|
|
859
895
|
theme,
|
|
896
|
+
userKey,
|
|
897
|
+
threadKey,
|
|
898
|
+
sessionKey,
|
|
860
899
|
children
|
|
861
900
|
}) {
|
|
862
|
-
|
|
901
|
+
useEffect6(() => {
|
|
863
902
|
if (apiKey.startsWith("sk_")) {
|
|
864
903
|
console.error(
|
|
865
904
|
"[XbNova] sk_ \uD0A4\uB97C \uBE0C\uB77C\uC6B0\uC800\uC5D0 \uB178\uCD9C\uD588\uC2B5\uB2C8\uB2E4 \u2014 pk_(publishable) \uD0A4\uB97C \uC4F0\uC138\uC694. sk_\uB294 \uC11C\uBC84 \uC804\uC6A9\uC785\uB2C8\uB2E4."
|
|
866
905
|
);
|
|
867
906
|
}
|
|
868
907
|
}, [apiKey]);
|
|
869
|
-
|
|
908
|
+
useEffect6(() => {
|
|
870
909
|
markSessionStart();
|
|
871
910
|
}, []);
|
|
872
|
-
|
|
911
|
+
useEffect6(() => {
|
|
873
912
|
if (screen) bumpVisitCount(screen);
|
|
874
913
|
}, [screen]);
|
|
875
914
|
const auth = useMemo2(
|
|
876
|
-
() => ({
|
|
877
|
-
|
|
915
|
+
() => ({
|
|
916
|
+
apiKey,
|
|
917
|
+
botId,
|
|
918
|
+
appId,
|
|
919
|
+
apiBaseUrl,
|
|
920
|
+
screen,
|
|
921
|
+
userKey: userKey || void 0,
|
|
922
|
+
threadKey: threadKey || void 0,
|
|
923
|
+
sessionKey: sessionKey || void 0
|
|
924
|
+
}),
|
|
925
|
+
[apiKey, botId, appId, apiBaseUrl, screen, userKey, threadKey, sessionKey]
|
|
878
926
|
);
|
|
879
927
|
const resolvedTheme = useMemo2(
|
|
880
928
|
() => ({ ...DEFAULT_THEME, ...theme }),
|
|
@@ -903,13 +951,13 @@ function XbNovaProvider({
|
|
|
903
951
|
}
|
|
904
952
|
|
|
905
953
|
// src/chat.tsx
|
|
906
|
-
import { useEffect as
|
|
954
|
+
import { useEffect as useEffect9, useRef as useRef8, useState as useState4 } from "react";
|
|
907
955
|
import ReactMarkdown from "react-markdown";
|
|
908
956
|
import remarkBreaks from "remark-breaks";
|
|
909
957
|
import remarkGfm from "remark-gfm";
|
|
910
958
|
|
|
911
959
|
// src/shadow-root.tsx
|
|
912
|
-
import { useEffect as
|
|
960
|
+
import { useEffect as useEffect7, useRef as useRef6, useState as useState2 } from "react";
|
|
913
961
|
import { createPortal } from "react-dom";
|
|
914
962
|
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
915
963
|
var cssPromise = null;
|
|
@@ -925,7 +973,7 @@ function loadCss() {
|
|
|
925
973
|
}
|
|
926
974
|
function useResolvedScheme(scheme) {
|
|
927
975
|
const [systemDark, setSystemDark] = useState2(false);
|
|
928
|
-
|
|
976
|
+
useEffect7(() => {
|
|
929
977
|
if (scheme !== "system") return;
|
|
930
978
|
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
931
979
|
setSystemDark(mq.matches);
|
|
@@ -945,10 +993,10 @@ function XbNovaShadowRoot({
|
|
|
945
993
|
const hostRef = useRef6(null);
|
|
946
994
|
const [mountNode, setMountNode] = useState2(null);
|
|
947
995
|
const [portalTarget, setPortalTarget] = useState2(null);
|
|
948
|
-
|
|
996
|
+
useEffect7(() => {
|
|
949
997
|
setPortalTarget(document.body);
|
|
950
998
|
}, []);
|
|
951
|
-
|
|
999
|
+
useEffect7(() => {
|
|
952
1000
|
const host = hostRef.current;
|
|
953
1001
|
if (!host || host.shadowRoot) return;
|
|
954
1002
|
const shadow = host.attachShadow({ mode: "open" });
|
|
@@ -963,7 +1011,7 @@ function XbNovaShadowRoot({
|
|
|
963
1011
|
shadow.appendChild(mount);
|
|
964
1012
|
setMountNode(mount);
|
|
965
1013
|
}, [portalTarget]);
|
|
966
|
-
|
|
1014
|
+
useEffect7(() => {
|
|
967
1015
|
if (!mountNode) return;
|
|
968
1016
|
mountNode.dataset.xbnovaScheme = scheme;
|
|
969
1017
|
mountNode.style.colorScheme = scheme;
|
|
@@ -976,15 +1024,44 @@ function XbNovaShadowRoot({
|
|
|
976
1024
|
}
|
|
977
1025
|
|
|
978
1026
|
// src/use-chat.ts
|
|
979
|
-
import { useEffect as
|
|
1027
|
+
import { useEffect as useEffect8, useRef as useRef7, useState as useState3 } from "react";
|
|
980
1028
|
|
|
981
1029
|
// src/stream.ts
|
|
1030
|
+
function fromStoredMessages(stored) {
|
|
1031
|
+
return stored.map((m) => ({
|
|
1032
|
+
id: m.role === "assistant" ? m.id : void 0,
|
|
1033
|
+
role: m.role === "assistant" ? "assistant" : "user",
|
|
1034
|
+
parts: m.parts.flatMap((p, i) => {
|
|
1035
|
+
const type = String(p.type ?? "");
|
|
1036
|
+
if (type === "text") {
|
|
1037
|
+
const text = typeof p.text === "string" ? p.text : "";
|
|
1038
|
+
return text ? [{ kind: "text", id: `${m.id}:${i}`, text }] : [];
|
|
1039
|
+
}
|
|
1040
|
+
if (type.startsWith("tool-")) {
|
|
1041
|
+
const meta = p.providerMetadata;
|
|
1042
|
+
return [
|
|
1043
|
+
{
|
|
1044
|
+
kind: "tool",
|
|
1045
|
+
toolCallId: String(p.toolCallId ?? `${m.id}:${i}`),
|
|
1046
|
+
toolName: type.slice("tool-".length),
|
|
1047
|
+
state: p.state === "output-error" ? "error" : "output",
|
|
1048
|
+
input: p.input,
|
|
1049
|
+
output: p.output,
|
|
1050
|
+
view: meta?.["kr.dsauto.chat"]?.view ?? null
|
|
1051
|
+
}
|
|
1052
|
+
];
|
|
1053
|
+
}
|
|
1054
|
+
return [];
|
|
1055
|
+
})
|
|
1056
|
+
}));
|
|
1057
|
+
}
|
|
982
1058
|
async function consumeSse(response, handlers) {
|
|
983
1059
|
if (!response.ok || !response.body) {
|
|
984
1060
|
handlers.onError?.(`\uC694\uCCAD \uC2E4\uD328 (${response.status})`);
|
|
985
1061
|
return null;
|
|
986
1062
|
}
|
|
987
1063
|
const conversationId = response.headers.get("x-conversation-id");
|
|
1064
|
+
if (conversationId) handlers.onConversationId?.(conversationId);
|
|
988
1065
|
const reader = response.body.getReader();
|
|
989
1066
|
const decoder = new TextDecoder();
|
|
990
1067
|
let buffer = "";
|
|
@@ -1089,12 +1166,19 @@ async function resumeToolResult(apiBaseUrl, apiKey, body, handlers) {
|
|
|
1089
1166
|
// src/use-chat.ts
|
|
1090
1167
|
var DEFAULT_CLIENT_ACTION_TIMEOUT_MS = 15e3;
|
|
1091
1168
|
function useXbNovaChat(options) {
|
|
1092
|
-
const { auth, trackRegistry, clientActionHandlers, visitorId } = useXbNovaInternal();
|
|
1169
|
+
const { auth, trackRegistry, contextRegistry, clientActionHandlers, visitorId, sessionId, sendEvent } = useXbNovaInternal();
|
|
1093
1170
|
const botId = options?.botId ?? auth.botId;
|
|
1094
1171
|
const nudgeNodeId = options?.nudgeNodeId;
|
|
1095
|
-
const { apiKey, apiBaseUrl } = auth;
|
|
1172
|
+
const { apiKey, apiBaseUrl, threadKey } = auth;
|
|
1173
|
+
const userKey = visitorId;
|
|
1096
1174
|
const [onboarding, setOnboarding] = useState3(null);
|
|
1097
1175
|
const [messages, setMessages] = useState3([]);
|
|
1176
|
+
const [resumed, setResumed] = useState3(false);
|
|
1177
|
+
const [sentOpening, setSentOpening] = useState3(null);
|
|
1178
|
+
const conversationIdRef = useRef7(null);
|
|
1179
|
+
const pendingOpeningRef = useRef7(null);
|
|
1180
|
+
const messagesRef = useRef7(messages);
|
|
1181
|
+
messagesRef.current = messages;
|
|
1098
1182
|
const [busy, setBusy] = useState3(false);
|
|
1099
1183
|
const [error, setError] = useState3(null);
|
|
1100
1184
|
const [clientResults, setClientResults] = useState3({});
|
|
@@ -1104,19 +1188,60 @@ function useXbNovaChat(options) {
|
|
|
1104
1188
|
const dispatchedClientActionsRef = useRef7(/* @__PURE__ */ new Set());
|
|
1105
1189
|
const nudgeNodeIdRef = useRef7(nudgeNodeId);
|
|
1106
1190
|
nudgeNodeIdRef.current = nudgeNodeId;
|
|
1107
|
-
|
|
1191
|
+
useEffect8(() => {
|
|
1192
|
+
let cancelled = false;
|
|
1108
1193
|
setOnboarding(null);
|
|
1109
1194
|
setMessages([]);
|
|
1195
|
+
setResumed(false);
|
|
1196
|
+
setSentOpening(null);
|
|
1110
1197
|
setError(null);
|
|
1111
1198
|
setClientResults({});
|
|
1112
1199
|
setFeedbackState({});
|
|
1113
1200
|
dispatchedClientActionsRef.current = /* @__PURE__ */ new Set();
|
|
1201
|
+
conversationIdRef.current = null;
|
|
1202
|
+
pendingOpeningRef.current = null;
|
|
1114
1203
|
fetch(`${apiBaseUrl}/v1/conversations`, {
|
|
1115
1204
|
method: "POST",
|
|
1116
1205
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
1117
|
-
body: JSON.stringify({
|
|
1118
|
-
|
|
1119
|
-
|
|
1206
|
+
body: JSON.stringify({
|
|
1207
|
+
bot_id: botId,
|
|
1208
|
+
nudge_node_id: nudgeNodeIdRef.current ?? null,
|
|
1209
|
+
visitor_id: userKey || null,
|
|
1210
|
+
thread_key: threadKey ?? null,
|
|
1211
|
+
session_id: sessionId,
|
|
1212
|
+
defer: true
|
|
1213
|
+
})
|
|
1214
|
+
}).then((r) => r.ok ? r.json() : Promise.reject(new Error(`${r.status}`))).then((data) => {
|
|
1215
|
+
if (cancelled) return;
|
|
1216
|
+
if (conversationIdRef.current || messagesRef.current.length > 0) {
|
|
1217
|
+
setOnboarding(data);
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
conversationIdRef.current = data.conversation_id;
|
|
1221
|
+
const restored = fromStoredMessages(data.messages ?? []);
|
|
1222
|
+
for (const m of restored) {
|
|
1223
|
+
for (const p of m.parts) if (p.kind === "tool") dispatchedClientActionsRef.current.add(p.toolCallId);
|
|
1224
|
+
}
|
|
1225
|
+
setMessages(restored);
|
|
1226
|
+
setResumed(restored.length > 0);
|
|
1227
|
+
setOnboarding(data);
|
|
1228
|
+
}).catch((e) => {
|
|
1229
|
+
if (!cancelled) setError(`\uBD07\uC744 \uBD88\uB7EC\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${e.message}`);
|
|
1230
|
+
});
|
|
1231
|
+
return () => {
|
|
1232
|
+
cancelled = true;
|
|
1233
|
+
};
|
|
1234
|
+
}, [apiBaseUrl, apiKey, botId, userKey, threadKey]);
|
|
1235
|
+
const openingMessage = options?.openingMessage;
|
|
1236
|
+
useEffect8(() => {
|
|
1237
|
+
if (!onboarding || !nudgeNodeId || !openingMessage) return;
|
|
1238
|
+
if (messagesRef.current.length === 0) return;
|
|
1239
|
+
setMessages((prev) => [
|
|
1240
|
+
...prev,
|
|
1241
|
+
{ role: "assistant", parts: [{ kind: "text", id: `nudge:${nudgeNodeId}:${prev.length}`, text: openingMessage }] }
|
|
1242
|
+
]);
|
|
1243
|
+
pendingOpeningRef.current = openingMessage;
|
|
1244
|
+
}, [onboarding, nudgeNodeId, openingMessage]);
|
|
1120
1245
|
const appendToLastAssistant = (existingParts, newParts) => setMessages((prev) => {
|
|
1121
1246
|
const next = [...prev];
|
|
1122
1247
|
next[next.length - 1] = { ...next[next.length - 1], role: "assistant", parts: [...existingParts, ...newParts] };
|
|
@@ -1129,7 +1254,7 @@ function useXbNovaChat(options) {
|
|
|
1129
1254
|
return next;
|
|
1130
1255
|
});
|
|
1131
1256
|
async function setFeedback(messageId, rating, reason) {
|
|
1132
|
-
const conversationId =
|
|
1257
|
+
const conversationId = conversationIdRef.current;
|
|
1133
1258
|
if (!conversationId || !messageId) return;
|
|
1134
1259
|
const prevRating = feedbackRef.current[messageId] ?? null;
|
|
1135
1260
|
const apply = (r) => setFeedbackState((prev) => {
|
|
@@ -1155,6 +1280,14 @@ function useXbNovaChat(options) {
|
|
|
1155
1280
|
apply(prevRating);
|
|
1156
1281
|
}
|
|
1157
1282
|
}
|
|
1283
|
+
function postClientActionEvent(kind, action, label) {
|
|
1284
|
+
const conversationId = conversationIdRef.current;
|
|
1285
|
+
if (!conversationId) return;
|
|
1286
|
+
sendEvent(kind, { conversation_id: conversationId, payload: label ? { action, label } : { action } });
|
|
1287
|
+
}
|
|
1288
|
+
function trackClientActionClick(action, label) {
|
|
1289
|
+
postClientActionEvent("cta_click", action, label);
|
|
1290
|
+
}
|
|
1158
1291
|
async function runClientAction(spec, part, existingParts) {
|
|
1159
1292
|
if (spec.dismissSheet) options?.onDismissSheet?.();
|
|
1160
1293
|
let result;
|
|
@@ -1178,7 +1311,7 @@ function useXbNovaChat(options) {
|
|
|
1178
1311
|
result = e instanceof Error ? `\uC624\uB958: ${e.message}` : "\uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
|
|
1179
1312
|
}
|
|
1180
1313
|
if (!spec.awaitResult) return;
|
|
1181
|
-
const conversationId =
|
|
1314
|
+
const conversationId = conversationIdRef.current;
|
|
1182
1315
|
if (!conversationId) return;
|
|
1183
1316
|
const structured = result && typeof result === "object" ? result : null;
|
|
1184
1317
|
if (structured?.view) {
|
|
@@ -1212,7 +1345,7 @@ function useXbNovaChat(options) {
|
|
|
1212
1345
|
setBusy(false);
|
|
1213
1346
|
}
|
|
1214
1347
|
}
|
|
1215
|
-
|
|
1348
|
+
useEffect8(() => {
|
|
1216
1349
|
if (clientActionHandlers.size === 0) return;
|
|
1217
1350
|
const last = messages[messages.length - 1];
|
|
1218
1351
|
if (!last || last.role !== "assistant") return;
|
|
@@ -1222,6 +1355,7 @@ function useXbNovaChat(options) {
|
|
|
1222
1355
|
if (!spec) continue;
|
|
1223
1356
|
if (dispatchedClientActionsRef.current.has(part.toolCallId)) continue;
|
|
1224
1357
|
dispatchedClientActionsRef.current.add(part.toolCallId);
|
|
1358
|
+
if (spec.render) postClientActionEvent("cta_impression", part.toolName, null);
|
|
1225
1359
|
void runClientAction(spec, part, last.parts);
|
|
1226
1360
|
}
|
|
1227
1361
|
}, [messages, clientActionHandlers, onboarding, apiBaseUrl, apiKey, botId]);
|
|
@@ -1241,7 +1375,17 @@ function useXbNovaChat(options) {
|
|
|
1241
1375
|
return next;
|
|
1242
1376
|
});
|
|
1243
1377
|
const focused = getFocusedTrackEntry(trackRegistry);
|
|
1244
|
-
const
|
|
1378
|
+
const notes = collectContextEntries(contextRegistry);
|
|
1379
|
+
const screen = focused ? { region: focused.region, data: resolveFactValue(focused.data) } : {};
|
|
1380
|
+
const context = focused || notes.length ? { ...screen, ...notes.length ? { notes } : {} } : void 0;
|
|
1381
|
+
let openingMessage2;
|
|
1382
|
+
if (messages.length === 0) {
|
|
1383
|
+
openingMessage2 = (options?.openingMessage ?? onboarding?.greeting) || null;
|
|
1384
|
+
setSentOpening(openingMessage2);
|
|
1385
|
+
} else {
|
|
1386
|
+
openingMessage2 = pendingOpeningRef.current;
|
|
1387
|
+
}
|
|
1388
|
+
pendingOpeningRef.current = null;
|
|
1245
1389
|
try {
|
|
1246
1390
|
await streamChat(
|
|
1247
1391
|
apiBaseUrl,
|
|
@@ -1249,11 +1393,22 @@ function useXbNovaChat(options) {
|
|
|
1249
1393
|
{
|
|
1250
1394
|
bot_id: botId,
|
|
1251
1395
|
message: trimmed,
|
|
1252
|
-
conversation_id:
|
|
1396
|
+
conversation_id: conversationIdRef.current,
|
|
1253
1397
|
context,
|
|
1254
|
-
nudge_node_id: nudgeNodeId ?? null
|
|
1398
|
+
nudge_node_id: nudgeNodeId ?? null,
|
|
1399
|
+
opening_message: openingMessage2,
|
|
1400
|
+
visitor_id: userKey || null,
|
|
1401
|
+
thread_key: threadKey ?? null,
|
|
1402
|
+
session_id: sessionId
|
|
1255
1403
|
},
|
|
1256
|
-
{
|
|
1404
|
+
{
|
|
1405
|
+
onPart: replaceLast,
|
|
1406
|
+
onMessageId: setLastAssistantId,
|
|
1407
|
+
onConversationId: (id) => {
|
|
1408
|
+
conversationIdRef.current = id;
|
|
1409
|
+
},
|
|
1410
|
+
onError: setError
|
|
1411
|
+
}
|
|
1257
1412
|
);
|
|
1258
1413
|
} catch (e) {
|
|
1259
1414
|
setError(e instanceof Error ? e.message : String(e));
|
|
@@ -1261,7 +1416,19 @@ function useXbNovaChat(options) {
|
|
|
1261
1416
|
setBusy(false);
|
|
1262
1417
|
}
|
|
1263
1418
|
}
|
|
1264
|
-
|
|
1419
|
+
const opening = resumed ? null : sentOpening ?? options?.openingMessage ?? onboarding?.greeting ?? null;
|
|
1420
|
+
return {
|
|
1421
|
+
onboarding,
|
|
1422
|
+
opening,
|
|
1423
|
+
messages,
|
|
1424
|
+
busy,
|
|
1425
|
+
error,
|
|
1426
|
+
send,
|
|
1427
|
+
clientResults,
|
|
1428
|
+
feedback,
|
|
1429
|
+
setFeedback,
|
|
1430
|
+
trackClientActionClick
|
|
1431
|
+
};
|
|
1265
1432
|
}
|
|
1266
1433
|
|
|
1267
1434
|
// src/views.tsx
|
|
@@ -1394,6 +1561,13 @@ function hasVisiblePart(parts, clientActionHandlers, clientResults) {
|
|
|
1394
1561
|
}
|
|
1395
1562
|
var DISCLOSURE_SEEN_KEY = "xbnova_disclosure_seen";
|
|
1396
1563
|
var SUMMARY_FACT_KEY = "xbnova_summary";
|
|
1564
|
+
function clickedControl(e) {
|
|
1565
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
1566
|
+
const control = target?.closest("button, a, [role='button']");
|
|
1567
|
+
if (!control || !e.currentTarget.contains(control)) return null;
|
|
1568
|
+
const text = (control.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
1569
|
+
return { label: text || control.getAttribute("aria-label") || null };
|
|
1570
|
+
}
|
|
1397
1571
|
function readDisclosureSeen() {
|
|
1398
1572
|
if (typeof window === "undefined") return true;
|
|
1399
1573
|
try {
|
|
@@ -1412,7 +1586,7 @@ function markDisclosureSeen() {
|
|
|
1412
1586
|
var DESKTOP_MEDIA_QUERY = "(min-width: 640px)";
|
|
1413
1587
|
function useIsDesktopViewport() {
|
|
1414
1588
|
const [isDesktop, setIsDesktop] = useState4(false);
|
|
1415
|
-
|
|
1589
|
+
useEffect9(() => {
|
|
1416
1590
|
if (typeof window === "undefined") return;
|
|
1417
1591
|
const mql = window.matchMedia(DESKTOP_MEDIA_QUERY);
|
|
1418
1592
|
setIsDesktop(mql.matches);
|
|
@@ -1436,7 +1610,7 @@ function backdropBaseStyle(isDesktop) {
|
|
|
1436
1610
|
}
|
|
1437
1611
|
function useTypewriter(text, key, speedMs = 28) {
|
|
1438
1612
|
const [shown, setShown] = useState4("");
|
|
1439
|
-
|
|
1613
|
+
useEffect9(() => {
|
|
1440
1614
|
setShown("");
|
|
1441
1615
|
if (!text) return;
|
|
1442
1616
|
let i = 0;
|
|
@@ -1523,9 +1697,10 @@ function XbNovaChat({
|
|
|
1523
1697
|
avatarSize = 28,
|
|
1524
1698
|
onVerdictAction,
|
|
1525
1699
|
feedback: feedbackEnabled = true,
|
|
1526
|
-
onFeedback
|
|
1700
|
+
onFeedback,
|
|
1701
|
+
onOpen
|
|
1527
1702
|
}) {
|
|
1528
|
-
const { auth, trackRegistry, factsRegistry,
|
|
1703
|
+
const { auth, trackRegistry, factsRegistry, clientActionHandlers, theme, sendEvent } = useXbNovaInternal();
|
|
1529
1704
|
const themeVars = {
|
|
1530
1705
|
"--xbnova-accent-1": theme.accentFrom,
|
|
1531
1706
|
"--xbnova-accent-2": theme.accentTo
|
|
@@ -1546,15 +1721,16 @@ function XbNovaChat({
|
|
|
1546
1721
|
const [input, setInput] = useState4("");
|
|
1547
1722
|
const bottomRef = useRef8(null);
|
|
1548
1723
|
const typedHintText = useTypewriter(hint?.text ?? "", hint?.triggerNodeId ?? "");
|
|
1549
|
-
const { onboarding, messages, busy, error, send, clientResults, feedback, setFeedback } = useXbNovaChat({
|
|
1724
|
+
const { onboarding, opening, messages, busy, error, send, clientResults, feedback, setFeedback, trackClientActionClick } = useXbNovaChat({
|
|
1550
1725
|
onFeedback,
|
|
1551
1726
|
botId: activeBotId ?? botId,
|
|
1552
1727
|
nudgeNodeId: activeNudgeNodeId,
|
|
1728
|
+
openingMessage: openingLine ?? void 0,
|
|
1553
1729
|
onDismissSheet: () => setOpen(false)
|
|
1554
1730
|
});
|
|
1555
1731
|
const effectiveBotId = activeBotId ?? botId;
|
|
1556
1732
|
const [cachedBranding, setCachedBranding] = useState4(null);
|
|
1557
|
-
|
|
1733
|
+
useEffect9(() => {
|
|
1558
1734
|
try {
|
|
1559
1735
|
const raw = window.localStorage.getItem(`xbnova_branding:${effectiveBotId}`);
|
|
1560
1736
|
setCachedBranding(raw ? JSON.parse(raw) : null);
|
|
@@ -1562,22 +1738,29 @@ function XbNovaChat({
|
|
|
1562
1738
|
setCachedBranding(null);
|
|
1563
1739
|
}
|
|
1564
1740
|
}, [effectiveBotId]);
|
|
1565
|
-
|
|
1741
|
+
useEffect9(() => {
|
|
1566
1742
|
if (!onboarding?.branding) return;
|
|
1567
1743
|
try {
|
|
1568
1744
|
window.localStorage.setItem(
|
|
1569
1745
|
`xbnova_branding:${effectiveBotId}`,
|
|
1570
|
-
JSON.stringify({
|
|
1746
|
+
JSON.stringify({
|
|
1747
|
+
logo_url: onboarding.branding.logo_url,
|
|
1748
|
+
avatar_text: onboarding.branding.avatar_text,
|
|
1749
|
+
badge_text: onboarding.branding.badge_text,
|
|
1750
|
+
disclaimer: onboarding.branding.disclaimer
|
|
1751
|
+
})
|
|
1571
1752
|
);
|
|
1572
1753
|
} catch {
|
|
1573
1754
|
}
|
|
1574
1755
|
}, [onboarding?.branding, effectiveBotId]);
|
|
1575
1756
|
const branding = onboarding?.branding ?? cachedBranding ?? void 0;
|
|
1757
|
+
const badgeText = branding?.badge_text?.trim() ?? "";
|
|
1758
|
+
const disclaimer = branding?.disclaimer?.trim() ?? "";
|
|
1576
1759
|
const brandingReady = Boolean(onboarding) || cachedBranding !== null || Boolean(error);
|
|
1577
|
-
|
|
1760
|
+
useEffect9(() => {
|
|
1578
1761
|
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
1579
1762
|
}, [messages]);
|
|
1580
|
-
|
|
1763
|
+
useEffect9(() => {
|
|
1581
1764
|
if (!open) {
|
|
1582
1765
|
setEntered(false);
|
|
1583
1766
|
return;
|
|
@@ -1585,7 +1768,7 @@ function XbNovaChat({
|
|
|
1585
1768
|
const raf = requestAnimationFrame(() => setEntered(true));
|
|
1586
1769
|
return () => cancelAnimationFrame(raf);
|
|
1587
1770
|
}, [open]);
|
|
1588
|
-
|
|
1771
|
+
useEffect9(() => {
|
|
1589
1772
|
if (!hint || hint.autoHideMs <= 0) return;
|
|
1590
1773
|
console.debug(`[XbNova] \uD78C\uD2B8 "${hint.text}" \uD45C\uC2DC \u2014 ${hint.autoHideMs}ms \uD6C4 \uC790\uB3D9\uC73C\uB85C \uC0AC\uB77C\uC9D0`);
|
|
1591
1774
|
const timer = window.setTimeout(() => {
|
|
@@ -1602,18 +1785,7 @@ function XbNovaChat({
|
|
|
1602
1785
|
setOpeningLine(consumed.text);
|
|
1603
1786
|
setActiveNudgeNodeId(consumed.messageNodeId);
|
|
1604
1787
|
setNudgeQuickReplies(consumed.quickReplies);
|
|
1605
|
-
|
|
1606
|
-
method: "POST",
|
|
1607
|
-
headers: { "Content-Type": "application/json" },
|
|
1608
|
-
body: JSON.stringify({
|
|
1609
|
-
app_id: auth.appId,
|
|
1610
|
-
nudge_node_id: consumed.messageNodeId,
|
|
1611
|
-
kind: "nudge_open",
|
|
1612
|
-
bot_id: consumed.botId,
|
|
1613
|
-
visitor_id: visitorId
|
|
1614
|
-
})
|
|
1615
|
-
}).catch(() => {
|
|
1616
|
-
});
|
|
1788
|
+
sendEvent("nudge_open", { nudge_node_id: consumed.messageNodeId, bot_id: consumed.botId });
|
|
1617
1789
|
} else {
|
|
1618
1790
|
setNudgeQuickReplies([]);
|
|
1619
1791
|
setActiveNudgeNodeId(void 0);
|
|
@@ -1627,6 +1799,11 @@ function XbNovaChat({
|
|
|
1627
1799
|
markDisclosureSeen();
|
|
1628
1800
|
}
|
|
1629
1801
|
setOpen(true);
|
|
1802
|
+
try {
|
|
1803
|
+
onOpen?.({ fromNudge: Boolean(consumed), hintText: consumed?.text });
|
|
1804
|
+
} catch (err) {
|
|
1805
|
+
console.warn("[XbNova] onOpen \uCF5C\uBC31 \uC624\uB958", err);
|
|
1806
|
+
}
|
|
1630
1807
|
}
|
|
1631
1808
|
if (!open) {
|
|
1632
1809
|
if (barHidden) return null;
|
|
@@ -1722,7 +1899,6 @@ function XbNovaChat({
|
|
|
1722
1899
|
const summary = resolveFactValue(factsRegistry.get(SUMMARY_FACT_KEY));
|
|
1723
1900
|
const focusedLabel = getFocusedTrackEntry(trackRegistry)?.label;
|
|
1724
1901
|
const contextLine = summary ?? (focusedLabel ? `${focusedLabel} \uBCF4\uB294 \uC911` : void 0);
|
|
1725
|
-
const firstBubble = openingLine ?? onboarding?.greeting;
|
|
1726
1902
|
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1727
1903
|
!isDesktop ? /* @__PURE__ */ jsx7(
|
|
1728
1904
|
XbNovaShadowRoot,
|
|
@@ -1759,7 +1935,10 @@ function XbNovaChat({
|
|
|
1759
1935
|
}
|
|
1760
1936
|
),
|
|
1761
1937
|
/* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
|
|
1762
|
-
/* @__PURE__ */
|
|
1938
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-center gap-1.5", children: [
|
|
1939
|
+
/* @__PURE__ */ jsx7("span", { className: "truncate text-sm font-semibold", children: onboarding?.branding?.title ?? "AI \uB3D9\uD589" }),
|
|
1940
|
+
badgeText ? /* @__PURE__ */ jsx7("span", { className: "shrink-0 rounded-full border border-neutral-300 px-1.5 py-px text-[10px] font-semibold leading-4 text-neutral-500 dark:border-neutral-600 dark:text-neutral-400", children: badgeText }) : null
|
|
1941
|
+
] }),
|
|
1763
1942
|
contextLine ? /* @__PURE__ */ jsx7("div", { className: "truncate text-[11px] text-neutral-400", children: contextLine }) : null
|
|
1764
1943
|
] }),
|
|
1765
1944
|
/* @__PURE__ */ jsx7(
|
|
@@ -1775,7 +1954,7 @@ function XbNovaChat({
|
|
|
1775
1954
|
] }),
|
|
1776
1955
|
/* @__PURE__ */ jsxs3("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
|
|
1777
1956
|
showDisclosure ? /* @__PURE__ */ jsx7("div", { className: "rounded-lg bg-neutral-50 px-3 py-2 text-[11px] text-neutral-400 dark:bg-neutral-800", children: "\uC9C0\uAE08 \uBCF4\uACE0 \uACC4\uC2E0 \uD654\uBA74\uACFC \uB9E4\uBB3C \uC815\uBCF4\uB97C \uAE30\uC900\uC73C\uB85C \uC548\uB0B4\uD574 \uB4DC\uB824\uC694." }) : null,
|
|
1778
|
-
|
|
1957
|
+
opening ? /* @__PURE__ */ jsx7(Bubble, { role: "assistant", children: /* @__PURE__ */ jsx7(AssistantMarkdown, { text: opening }) }) : null,
|
|
1779
1958
|
messages.map((message, i) => /* @__PURE__ */ jsx7("div", { children: message.role === "user" ? /* @__PURE__ */ jsx7(Bubble, { role: "user", children: message.parts.map((p) => p.kind === "text" ? p.text : "").join("") }) : /* @__PURE__ */ jsxs3(
|
|
1780
1959
|
AnswerActionsFrame,
|
|
1781
1960
|
{
|
|
@@ -1803,7 +1982,17 @@ function XbNovaChat({
|
|
|
1803
1982
|
);
|
|
1804
1983
|
}
|
|
1805
1984
|
const spec = clientActionHandlers.get(part.toolName);
|
|
1806
|
-
return spec?.render ? /* @__PURE__ */ jsx7(
|
|
1985
|
+
return spec?.render ? /* @__PURE__ */ jsx7(
|
|
1986
|
+
"div",
|
|
1987
|
+
{
|
|
1988
|
+
onClickCapture: (e) => {
|
|
1989
|
+
const control = clickedControl(e);
|
|
1990
|
+
if (control) trackClientActionClick(part.toolName, control.label);
|
|
1991
|
+
},
|
|
1992
|
+
children: spec.render(part.input)
|
|
1993
|
+
},
|
|
1994
|
+
j
|
|
1995
|
+
) : null;
|
|
1807
1996
|
}
|
|
1808
1997
|
return /* @__PURE__ */ jsx7(ToolView, { part, viewRenderers, onVerdictAction }, j);
|
|
1809
1998
|
}),
|
|
@@ -1874,7 +2063,8 @@ function XbNovaChat({
|
|
|
1874
2063
|
)
|
|
1875
2064
|
]
|
|
1876
2065
|
}
|
|
1877
|
-
)
|
|
2066
|
+
),
|
|
2067
|
+
disclaimer ? /* @__PURE__ */ jsx7("p", { className: "-mt-1 px-4 pb-2.5 text-center text-[11px] leading-snug text-neutral-400 dark:text-neutral-500", children: disclaimer }) : null
|
|
1878
2068
|
] })
|
|
1879
2069
|
}
|
|
1880
2070
|
)
|
|
@@ -1976,8 +2166,8 @@ function AnswerActions({
|
|
|
1976
2166
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
1977
2167
|
timerRef.current = null;
|
|
1978
2168
|
};
|
|
1979
|
-
|
|
1980
|
-
|
|
2169
|
+
useEffect9(() => clearTimer, []);
|
|
2170
|
+
useEffect9(() => {
|
|
1981
2171
|
if (!menuOpen) {
|
|
1982
2172
|
setEntered(false);
|
|
1983
2173
|
return;
|
|
@@ -2124,13 +2314,13 @@ function AnswerActionsFrame({
|
|
|
2124
2314
|
}
|
|
2125
2315
|
|
|
2126
2316
|
// src/facts.ts
|
|
2127
|
-
import { useEffect as
|
|
2317
|
+
import { useEffect as useEffect10, useId, useRef as useRef9 } from "react";
|
|
2128
2318
|
function useXbNovaFact(key, value) {
|
|
2129
2319
|
const { factsRegistry } = useXbNovaInternal();
|
|
2130
|
-
|
|
2320
|
+
useEffect10(() => {
|
|
2131
2321
|
factsRegistry.set(key, value);
|
|
2132
2322
|
});
|
|
2133
|
-
|
|
2323
|
+
useEffect10(() => {
|
|
2134
2324
|
return () => {
|
|
2135
2325
|
factsRegistry.delete(key);
|
|
2136
2326
|
};
|
|
@@ -2139,13 +2329,13 @@ function useXbNovaFact(key, value) {
|
|
|
2139
2329
|
function useXbNovaFacts(values) {
|
|
2140
2330
|
const { factsRegistry } = useXbNovaInternal();
|
|
2141
2331
|
const keysRef = useRef9([]);
|
|
2142
|
-
|
|
2332
|
+
useEffect10(() => {
|
|
2143
2333
|
for (const [key, value] of Object.entries(values)) {
|
|
2144
2334
|
factsRegistry.set(key, value);
|
|
2145
2335
|
}
|
|
2146
2336
|
keysRef.current = Object.keys(values);
|
|
2147
2337
|
});
|
|
2148
|
-
|
|
2338
|
+
useEffect10(() => {
|
|
2149
2339
|
return () => {
|
|
2150
2340
|
for (const key of keysRef.current) {
|
|
2151
2341
|
factsRegistry.delete(key);
|
|
@@ -2153,6 +2343,18 @@ function useXbNovaFacts(values) {
|
|
|
2153
2343
|
};
|
|
2154
2344
|
}, [factsRegistry]);
|
|
2155
2345
|
}
|
|
2346
|
+
function useXbNovaContext(entries) {
|
|
2347
|
+
const { contextRegistry } = useXbNovaInternal();
|
|
2348
|
+
const id = useId();
|
|
2349
|
+
useEffect10(() => {
|
|
2350
|
+
contextRegistry.set(id, entries);
|
|
2351
|
+
});
|
|
2352
|
+
useEffect10(() => {
|
|
2353
|
+
return () => {
|
|
2354
|
+
contextRegistry.delete(id);
|
|
2355
|
+
};
|
|
2356
|
+
}, [id, contextRegistry]);
|
|
2357
|
+
}
|
|
2156
2358
|
function useXbNovaVisitor() {
|
|
2157
2359
|
const { visitorId } = useXbNovaInternal();
|
|
2158
2360
|
return visitorId;
|
|
@@ -2173,6 +2375,7 @@ export {
|
|
|
2173
2375
|
XbNovaTrack,
|
|
2174
2376
|
trackXbNovaEvent,
|
|
2175
2377
|
useXbNovaChat,
|
|
2378
|
+
useXbNovaContext,
|
|
2176
2379
|
useXbNovaFact,
|
|
2177
2380
|
useXbNovaFacts,
|
|
2178
2381
|
useXbNovaSettings,
|