@xbrain-swe/nova-chat-widget 0.1.11 → 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 +228 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -5
- package/dist/index.d.ts +42 -5
- package/dist/index.js +278 -105
- 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 {
|
|
@@ -65,6 +66,35 @@ function InternalProvider({
|
|
|
65
66
|
if (visitorIdRef.current === null) {
|
|
66
67
|
visitorIdRef.current = resolveVisitorId();
|
|
67
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]);
|
|
68
98
|
const value = useMemo(
|
|
69
99
|
() => ({
|
|
70
100
|
auth,
|
|
@@ -72,10 +102,12 @@ function InternalProvider({
|
|
|
72
102
|
factsRegistry,
|
|
73
103
|
contextRegistry,
|
|
74
104
|
clientActionHandlers: clientActionHandlers ?? emptyHandlers,
|
|
75
|
-
visitorId
|
|
105
|
+
visitorId,
|
|
106
|
+
sessionId,
|
|
107
|
+
sendEvent,
|
|
76
108
|
theme
|
|
77
109
|
}),
|
|
78
|
-
[auth, trackRegistry, factsRegistry, contextRegistry, clientActionHandlers, emptyHandlers, theme]
|
|
110
|
+
[auth, trackRegistry, factsRegistry, contextRegistry, clientActionHandlers, emptyHandlers, visitorId, sessionId, sendEvent, theme]
|
|
79
111
|
);
|
|
80
112
|
return /* @__PURE__ */ jsx(InternalContext.Provider, { value, children });
|
|
81
113
|
}
|
|
@@ -96,7 +128,7 @@ function XbNovaClientActionSync({ actions }) {
|
|
|
96
128
|
const { auth } = useXbNovaInternal();
|
|
97
129
|
const { apiKey, apiBaseUrl, appId } = auth;
|
|
98
130
|
const lastSyncedRef = useRef2(null);
|
|
99
|
-
|
|
131
|
+
useEffect2(() => {
|
|
100
132
|
if (!actions || Object.keys(actions).length === 0) return;
|
|
101
133
|
const serialized = JSON.stringify(toSyncPayload(actions));
|
|
102
134
|
if (serialized === lastSyncedRef.current) return;
|
|
@@ -150,7 +182,7 @@ function peekVisitCount(screen) {
|
|
|
150
182
|
}
|
|
151
183
|
|
|
152
184
|
// src/nudge-context.tsx
|
|
153
|
-
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";
|
|
154
186
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
155
187
|
var NudgeContext = createContext2(null);
|
|
156
188
|
var HINTS_DISABLED_KEY = "xbnova_hints_disabled";
|
|
@@ -194,43 +226,43 @@ function NudgeStateProvider({ children }) {
|
|
|
194
226
|
const [hintsEnabled, setHintsEnabled] = useState(() => !readFlag(HINTS_DISABLED_KEY));
|
|
195
227
|
const [barHidden, setBarHidden] = useState(() => readFlag(BAR_HIDDEN_KEY));
|
|
196
228
|
const streakRef = useRef3(readStreak());
|
|
197
|
-
const disableHints =
|
|
229
|
+
const disableHints = useCallback2(() => {
|
|
198
230
|
setHintsEnabled(false);
|
|
199
231
|
writeFlag(HINTS_DISABLED_KEY, true);
|
|
200
232
|
}, []);
|
|
201
|
-
const enableHints =
|
|
233
|
+
const enableHints = useCallback2(() => {
|
|
202
234
|
setHintsEnabled(true);
|
|
203
235
|
writeFlag(HINTS_DISABLED_KEY, false);
|
|
204
236
|
streakRef.current = 0;
|
|
205
237
|
writeStreak(0);
|
|
206
238
|
}, []);
|
|
207
|
-
const hideBar =
|
|
239
|
+
const hideBar = useCallback2(() => {
|
|
208
240
|
setBarHidden(true);
|
|
209
241
|
writeFlag(BAR_HIDDEN_KEY, true);
|
|
210
242
|
}, []);
|
|
211
|
-
const showBar =
|
|
243
|
+
const showBar = useCallback2(() => {
|
|
212
244
|
setBarHidden(false);
|
|
213
245
|
writeFlag(BAR_HIDDEN_KEY, false);
|
|
214
246
|
}, []);
|
|
215
|
-
const offerHint =
|
|
247
|
+
const offerHint = useCallback2((next) => {
|
|
216
248
|
setHint((current) => current ? current : next);
|
|
217
249
|
}, []);
|
|
218
|
-
|
|
250
|
+
useEffect3(() => {
|
|
219
251
|
if (hint) setLastHint(hint);
|
|
220
252
|
}, [hint]);
|
|
221
|
-
const dismissHint =
|
|
253
|
+
const dismissHint = useCallback2(() => {
|
|
222
254
|
const limit = hint?.autoDisableAfterDismissals ?? DEFAULT_DISMISS_STREAK_LIMIT;
|
|
223
255
|
setHint(null);
|
|
224
256
|
streakRef.current += 1;
|
|
225
257
|
writeStreak(streakRef.current);
|
|
226
258
|
if (streakRef.current >= limit) disableHints();
|
|
227
259
|
}, [hint, disableHints]);
|
|
228
|
-
const openHint =
|
|
260
|
+
const openHint = useCallback2(() => {
|
|
229
261
|
setHint(null);
|
|
230
262
|
streakRef.current = 0;
|
|
231
263
|
writeStreak(0);
|
|
232
264
|
}, []);
|
|
233
|
-
const autoHideHint =
|
|
265
|
+
const autoHideHint = useCallback2(() => {
|
|
234
266
|
setHint(null);
|
|
235
267
|
}, []);
|
|
236
268
|
return /* @__PURE__ */ jsx2(
|
|
@@ -267,10 +299,10 @@ function useXbNovaSettings() {
|
|
|
267
299
|
}
|
|
268
300
|
|
|
269
301
|
// src/nudge-loop.tsx
|
|
270
|
-
import { useCallback as
|
|
302
|
+
import { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef5 } from "react";
|
|
271
303
|
|
|
272
304
|
// src/track.tsx
|
|
273
|
-
import { useCallback as
|
|
305
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4 } from "react";
|
|
274
306
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
275
307
|
var VISIBILITY_THRESHOLD = 0;
|
|
276
308
|
function finalizeIfVisible(registry, region) {
|
|
@@ -287,7 +319,7 @@ function useXbNovaTrack(region, data, label) {
|
|
|
287
319
|
dataRef.current = data;
|
|
288
320
|
const labelRef = useRef4(label);
|
|
289
321
|
labelRef.current = label;
|
|
290
|
-
|
|
322
|
+
useEffect4(() => {
|
|
291
323
|
const entry = trackRegistry.get(region);
|
|
292
324
|
if (entry) {
|
|
293
325
|
entry.data = data;
|
|
@@ -296,7 +328,7 @@ function useXbNovaTrack(region, data, label) {
|
|
|
296
328
|
trackRegistry.set(region, { region, data, label, enteredAt: null, totalMs: 0, node: null });
|
|
297
329
|
}
|
|
298
330
|
}, [region, data, label, trackRegistry]);
|
|
299
|
-
|
|
331
|
+
useEffect4(() => {
|
|
300
332
|
return () => {
|
|
301
333
|
observerRef.current?.disconnect();
|
|
302
334
|
finalizeIfVisible(trackRegistry, region);
|
|
@@ -304,7 +336,7 @@ function useXbNovaTrack(region, data, label) {
|
|
|
304
336
|
if (entry) entry.node = null;
|
|
305
337
|
};
|
|
306
338
|
}, [region, trackRegistry]);
|
|
307
|
-
return
|
|
339
|
+
return useCallback3(
|
|
308
340
|
(node) => {
|
|
309
341
|
observerRef.current?.disconnect();
|
|
310
342
|
observerRef.current = null;
|
|
@@ -378,7 +410,7 @@ function getFocusedTrackEntry(trackRegistry) {
|
|
|
378
410
|
}
|
|
379
411
|
|
|
380
412
|
// src/nudge-loop.tsx
|
|
381
|
-
var EVAL_INTERVAL_MS =
|
|
413
|
+
var EVAL_INTERVAL_MS = 250;
|
|
382
414
|
var XBNOVA_EVENT_NAME = "xbnova:event";
|
|
383
415
|
var SESSION_COUNT_KEY_PREFIX = "xbnova_hint_session_count:";
|
|
384
416
|
var SCROLL_SETTLE_MS = 500;
|
|
@@ -571,7 +603,7 @@ function clearDebugOverlay(refs) {
|
|
|
571
603
|
refs.bandLinesRef.current.clear();
|
|
572
604
|
}
|
|
573
605
|
function XbNovaNudgeLoop({ appId }) {
|
|
574
|
-
const { auth, trackRegistry, factsRegistry,
|
|
606
|
+
const { auth, trackRegistry, factsRegistry, sendEvent } = useXbNovaInternal();
|
|
575
607
|
const { apiKey, apiBaseUrl, screen } = auth;
|
|
576
608
|
const { hint, offerHint, autoHideHint, hintsEnabled } = useNudgeState();
|
|
577
609
|
const graphRef = useRef5(null);
|
|
@@ -586,7 +618,7 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
586
618
|
hintRef.current = hint;
|
|
587
619
|
const hintsEnabledRef = useRef5(hintsEnabled);
|
|
588
620
|
hintsEnabledRef.current = hintsEnabled;
|
|
589
|
-
|
|
621
|
+
useEffect5(() => {
|
|
590
622
|
let cancelled = false;
|
|
591
623
|
fetch(`${apiBaseUrl}/v1/apps/${appId}/nudge-graph`, {
|
|
592
624
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
@@ -598,14 +630,16 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
598
630
|
cancelled = true;
|
|
599
631
|
};
|
|
600
632
|
}, [apiBaseUrl, apiKey, appId]);
|
|
601
|
-
const evaluate =
|
|
633
|
+
const evaluate = useCallback4(
|
|
602
634
|
(firedEventName) => {
|
|
603
635
|
const graph = graphRef.current;
|
|
604
636
|
const debugEnabled = isDebugEnabled();
|
|
605
637
|
const debugRefs = { panelRef: debugPanelRef, boxesRef: debugBoxesRef, bandLinesRef: debugBandLinesRef };
|
|
606
638
|
if (!debugEnabled && debugPanelRef.current) clearDebugOverlay(debugRefs);
|
|
639
|
+
const tickDebug = debugEnabled ? (msg) => console.debug(msg) : () => {
|
|
640
|
+
};
|
|
607
641
|
if (!graph) {
|
|
608
|
-
|
|
642
|
+
tickDebug("[XbNova] nudge \uD3C9\uAC00 \uAC74\uB108\uB700: \uADF8\uB798\uD504\uB97C \uC544\uC9C1 \uBABB \uBD88\uB7EC\uC634");
|
|
609
643
|
return;
|
|
610
644
|
}
|
|
611
645
|
const canOffer = !hintRef.current && hintsEnabledRef.current;
|
|
@@ -656,18 +690,7 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
656
690
|
autoHideMs: messageNode.auto_hide_ms ?? 2e4,
|
|
657
691
|
quickReplies: messageNode.quick_replies ?? []
|
|
658
692
|
});
|
|
659
|
-
|
|
660
|
-
method: "POST",
|
|
661
|
-
headers: { "Content-Type": "application/json" },
|
|
662
|
-
body: JSON.stringify({
|
|
663
|
-
app_id: appId,
|
|
664
|
-
nudge_node_id: messageNode.id,
|
|
665
|
-
kind: "nudge_impression",
|
|
666
|
-
bot_id: botId,
|
|
667
|
-
visitor_id: visitorId
|
|
668
|
-
})
|
|
669
|
-
}).catch(() => {
|
|
670
|
-
});
|
|
693
|
+
sendEvent("nudge_impression", { nudge_node_id: messageNode.id, bot_id: botId });
|
|
671
694
|
};
|
|
672
695
|
const tryFireFrom = (node) => {
|
|
673
696
|
const nextEdge = (edgesFrom.get(node.id) ?? [])[0];
|
|
@@ -695,7 +718,7 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
695
718
|
const maxPerSession = node.max_per_session === 0 ? Infinity : node.max_per_session ?? 3;
|
|
696
719
|
const sessionCount = peekSessionCount(node.id);
|
|
697
720
|
if (sessionCount >= maxPerSession) {
|
|
698
|
-
|
|
721
|
+
tickDebug(`[XbNova] \uD2B8\uB9AC\uAC70 ${node.id}(${node.trigger_type}) \uAC74\uB108\uB700: \uC138\uC158\uB2F9 \uC0C1\uD55C(${maxPerSession}) \uB3C4\uB2EC`);
|
|
699
722
|
if (debugEnabled && node.trigger_type === "dwell") {
|
|
700
723
|
debugLines.push(`- [${node.id}] \uC138\uC158\uB2F9 \uC0C1\uD55C(${maxPerSession}) \uB3C4\uB2EC \u2014 \uB354 \uC548 \uC7BC`);
|
|
701
724
|
}
|
|
@@ -730,7 +753,7 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
730
753
|
overlapPx = status.overlapPx;
|
|
731
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}`;
|
|
732
755
|
}
|
|
733
|
-
|
|
756
|
+
tickDebug(
|
|
734
757
|
`[XbNova] band dwell \uD2B8\uB9AC\uAC70=${node.id} region="${region}" band%=${fromPercent}~${toPercent} | ${statusDetail}`
|
|
735
758
|
);
|
|
736
759
|
dwellRecords.push({ node, state, requiredMs, region, found, screenMatches, inBand, overlapPx });
|
|
@@ -739,7 +762,7 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
739
762
|
const fires = triggerFires(node, { screen, firedEventName });
|
|
740
763
|
if (!fires) continue;
|
|
741
764
|
if (!canOffer) {
|
|
742
|
-
|
|
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`);
|
|
743
766
|
continue;
|
|
744
767
|
}
|
|
745
768
|
if (tryFireFrom(node)) {
|
|
@@ -839,9 +862,9 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
839
862
|
ensureDebugPanel(debugPanelRef, debugCollapsedRef).body.textContent = debugLines.join("\n");
|
|
840
863
|
}
|
|
841
864
|
},
|
|
842
|
-
[apiBaseUrl, appId, autoHideHint, factsRegistry, offerHint, screen, trackRegistry,
|
|
865
|
+
[apiBaseUrl, appId, autoHideHint, factsRegistry, offerHint, screen, trackRegistry, sendEvent]
|
|
843
866
|
);
|
|
844
|
-
|
|
867
|
+
useEffect5(() => {
|
|
845
868
|
const interval = window.setInterval(() => evaluate(null), EVAL_INTERVAL_MS);
|
|
846
869
|
const onEvent = (e) => evaluate(e.detail.eventName);
|
|
847
870
|
window.addEventListener(XBNOVA_EVENT_NAME, onEvent);
|
|
@@ -870,24 +893,36 @@ function XbNovaProvider({
|
|
|
870
893
|
apiBaseUrl = DEFAULT_API_BASE_URL,
|
|
871
894
|
clientActions,
|
|
872
895
|
theme,
|
|
896
|
+
userKey,
|
|
897
|
+
threadKey,
|
|
898
|
+
sessionKey,
|
|
873
899
|
children
|
|
874
900
|
}) {
|
|
875
|
-
|
|
901
|
+
useEffect6(() => {
|
|
876
902
|
if (apiKey.startsWith("sk_")) {
|
|
877
903
|
console.error(
|
|
878
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."
|
|
879
905
|
);
|
|
880
906
|
}
|
|
881
907
|
}, [apiKey]);
|
|
882
|
-
|
|
908
|
+
useEffect6(() => {
|
|
883
909
|
markSessionStart();
|
|
884
910
|
}, []);
|
|
885
|
-
|
|
911
|
+
useEffect6(() => {
|
|
886
912
|
if (screen) bumpVisitCount(screen);
|
|
887
913
|
}, [screen]);
|
|
888
914
|
const auth = useMemo2(
|
|
889
|
-
() => ({
|
|
890
|
-
|
|
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]
|
|
891
926
|
);
|
|
892
927
|
const resolvedTheme = useMemo2(
|
|
893
928
|
() => ({ ...DEFAULT_THEME, ...theme }),
|
|
@@ -916,13 +951,13 @@ function XbNovaProvider({
|
|
|
916
951
|
}
|
|
917
952
|
|
|
918
953
|
// src/chat.tsx
|
|
919
|
-
import { useEffect as
|
|
954
|
+
import { useEffect as useEffect9, useRef as useRef8, useState as useState4 } from "react";
|
|
920
955
|
import ReactMarkdown from "react-markdown";
|
|
921
956
|
import remarkBreaks from "remark-breaks";
|
|
922
957
|
import remarkGfm from "remark-gfm";
|
|
923
958
|
|
|
924
959
|
// src/shadow-root.tsx
|
|
925
|
-
import { useEffect as
|
|
960
|
+
import { useEffect as useEffect7, useRef as useRef6, useState as useState2 } from "react";
|
|
926
961
|
import { createPortal } from "react-dom";
|
|
927
962
|
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
928
963
|
var cssPromise = null;
|
|
@@ -938,7 +973,7 @@ function loadCss() {
|
|
|
938
973
|
}
|
|
939
974
|
function useResolvedScheme(scheme) {
|
|
940
975
|
const [systemDark, setSystemDark] = useState2(false);
|
|
941
|
-
|
|
976
|
+
useEffect7(() => {
|
|
942
977
|
if (scheme !== "system") return;
|
|
943
978
|
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
944
979
|
setSystemDark(mq.matches);
|
|
@@ -958,10 +993,10 @@ function XbNovaShadowRoot({
|
|
|
958
993
|
const hostRef = useRef6(null);
|
|
959
994
|
const [mountNode, setMountNode] = useState2(null);
|
|
960
995
|
const [portalTarget, setPortalTarget] = useState2(null);
|
|
961
|
-
|
|
996
|
+
useEffect7(() => {
|
|
962
997
|
setPortalTarget(document.body);
|
|
963
998
|
}, []);
|
|
964
|
-
|
|
999
|
+
useEffect7(() => {
|
|
965
1000
|
const host = hostRef.current;
|
|
966
1001
|
if (!host || host.shadowRoot) return;
|
|
967
1002
|
const shadow = host.attachShadow({ mode: "open" });
|
|
@@ -976,7 +1011,7 @@ function XbNovaShadowRoot({
|
|
|
976
1011
|
shadow.appendChild(mount);
|
|
977
1012
|
setMountNode(mount);
|
|
978
1013
|
}, [portalTarget]);
|
|
979
|
-
|
|
1014
|
+
useEffect7(() => {
|
|
980
1015
|
if (!mountNode) return;
|
|
981
1016
|
mountNode.dataset.xbnovaScheme = scheme;
|
|
982
1017
|
mountNode.style.colorScheme = scheme;
|
|
@@ -989,15 +1024,44 @@ function XbNovaShadowRoot({
|
|
|
989
1024
|
}
|
|
990
1025
|
|
|
991
1026
|
// src/use-chat.ts
|
|
992
|
-
import { useEffect as
|
|
1027
|
+
import { useEffect as useEffect8, useRef as useRef7, useState as useState3 } from "react";
|
|
993
1028
|
|
|
994
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
|
+
}
|
|
995
1058
|
async function consumeSse(response, handlers) {
|
|
996
1059
|
if (!response.ok || !response.body) {
|
|
997
1060
|
handlers.onError?.(`\uC694\uCCAD \uC2E4\uD328 (${response.status})`);
|
|
998
1061
|
return null;
|
|
999
1062
|
}
|
|
1000
1063
|
const conversationId = response.headers.get("x-conversation-id");
|
|
1064
|
+
if (conversationId) handlers.onConversationId?.(conversationId);
|
|
1001
1065
|
const reader = response.body.getReader();
|
|
1002
1066
|
const decoder = new TextDecoder();
|
|
1003
1067
|
let buffer = "";
|
|
@@ -1102,12 +1166,19 @@ async function resumeToolResult(apiBaseUrl, apiKey, body, handlers) {
|
|
|
1102
1166
|
// src/use-chat.ts
|
|
1103
1167
|
var DEFAULT_CLIENT_ACTION_TIMEOUT_MS = 15e3;
|
|
1104
1168
|
function useXbNovaChat(options) {
|
|
1105
|
-
const { auth, trackRegistry, contextRegistry, clientActionHandlers, visitorId } = useXbNovaInternal();
|
|
1169
|
+
const { auth, trackRegistry, contextRegistry, clientActionHandlers, visitorId, sessionId, sendEvent } = useXbNovaInternal();
|
|
1106
1170
|
const botId = options?.botId ?? auth.botId;
|
|
1107
1171
|
const nudgeNodeId = options?.nudgeNodeId;
|
|
1108
|
-
const { apiKey, apiBaseUrl } = auth;
|
|
1172
|
+
const { apiKey, apiBaseUrl, threadKey } = auth;
|
|
1173
|
+
const userKey = visitorId;
|
|
1109
1174
|
const [onboarding, setOnboarding] = useState3(null);
|
|
1110
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;
|
|
1111
1182
|
const [busy, setBusy] = useState3(false);
|
|
1112
1183
|
const [error, setError] = useState3(null);
|
|
1113
1184
|
const [clientResults, setClientResults] = useState3({});
|
|
@@ -1117,19 +1188,60 @@ function useXbNovaChat(options) {
|
|
|
1117
1188
|
const dispatchedClientActionsRef = useRef7(/* @__PURE__ */ new Set());
|
|
1118
1189
|
const nudgeNodeIdRef = useRef7(nudgeNodeId);
|
|
1119
1190
|
nudgeNodeIdRef.current = nudgeNodeId;
|
|
1120
|
-
|
|
1191
|
+
useEffect8(() => {
|
|
1192
|
+
let cancelled = false;
|
|
1121
1193
|
setOnboarding(null);
|
|
1122
1194
|
setMessages([]);
|
|
1195
|
+
setResumed(false);
|
|
1196
|
+
setSentOpening(null);
|
|
1123
1197
|
setError(null);
|
|
1124
1198
|
setClientResults({});
|
|
1125
1199
|
setFeedbackState({});
|
|
1126
1200
|
dispatchedClientActionsRef.current = /* @__PURE__ */ new Set();
|
|
1201
|
+
conversationIdRef.current = null;
|
|
1202
|
+
pendingOpeningRef.current = null;
|
|
1127
1203
|
fetch(`${apiBaseUrl}/v1/conversations`, {
|
|
1128
1204
|
method: "POST",
|
|
1129
1205
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
1130
|
-
body: JSON.stringify({
|
|
1131
|
-
|
|
1132
|
-
|
|
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]);
|
|
1133
1245
|
const appendToLastAssistant = (existingParts, newParts) => setMessages((prev) => {
|
|
1134
1246
|
const next = [...prev];
|
|
1135
1247
|
next[next.length - 1] = { ...next[next.length - 1], role: "assistant", parts: [...existingParts, ...newParts] };
|
|
@@ -1142,7 +1254,7 @@ function useXbNovaChat(options) {
|
|
|
1142
1254
|
return next;
|
|
1143
1255
|
});
|
|
1144
1256
|
async function setFeedback(messageId, rating, reason) {
|
|
1145
|
-
const conversationId =
|
|
1257
|
+
const conversationId = conversationIdRef.current;
|
|
1146
1258
|
if (!conversationId || !messageId) return;
|
|
1147
1259
|
const prevRating = feedbackRef.current[messageId] ?? null;
|
|
1148
1260
|
const apply = (r) => setFeedbackState((prev) => {
|
|
@@ -1168,6 +1280,14 @@ function useXbNovaChat(options) {
|
|
|
1168
1280
|
apply(prevRating);
|
|
1169
1281
|
}
|
|
1170
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
|
+
}
|
|
1171
1291
|
async function runClientAction(spec, part, existingParts) {
|
|
1172
1292
|
if (spec.dismissSheet) options?.onDismissSheet?.();
|
|
1173
1293
|
let result;
|
|
@@ -1191,7 +1311,7 @@ function useXbNovaChat(options) {
|
|
|
1191
1311
|
result = e instanceof Error ? `\uC624\uB958: ${e.message}` : "\uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
|
|
1192
1312
|
}
|
|
1193
1313
|
if (!spec.awaitResult) return;
|
|
1194
|
-
const conversationId =
|
|
1314
|
+
const conversationId = conversationIdRef.current;
|
|
1195
1315
|
if (!conversationId) return;
|
|
1196
1316
|
const structured = result && typeof result === "object" ? result : null;
|
|
1197
1317
|
if (structured?.view) {
|
|
@@ -1225,7 +1345,7 @@ function useXbNovaChat(options) {
|
|
|
1225
1345
|
setBusy(false);
|
|
1226
1346
|
}
|
|
1227
1347
|
}
|
|
1228
|
-
|
|
1348
|
+
useEffect8(() => {
|
|
1229
1349
|
if (clientActionHandlers.size === 0) return;
|
|
1230
1350
|
const last = messages[messages.length - 1];
|
|
1231
1351
|
if (!last || last.role !== "assistant") return;
|
|
@@ -1235,6 +1355,7 @@ function useXbNovaChat(options) {
|
|
|
1235
1355
|
if (!spec) continue;
|
|
1236
1356
|
if (dispatchedClientActionsRef.current.has(part.toolCallId)) continue;
|
|
1237
1357
|
dispatchedClientActionsRef.current.add(part.toolCallId);
|
|
1358
|
+
if (spec.render) postClientActionEvent("cta_impression", part.toolName, null);
|
|
1238
1359
|
void runClientAction(spec, part, last.parts);
|
|
1239
1360
|
}
|
|
1240
1361
|
}, [messages, clientActionHandlers, onboarding, apiBaseUrl, apiKey, botId]);
|
|
@@ -1257,6 +1378,14 @@ function useXbNovaChat(options) {
|
|
|
1257
1378
|
const notes = collectContextEntries(contextRegistry);
|
|
1258
1379
|
const screen = focused ? { region: focused.region, data: resolveFactValue(focused.data) } : {};
|
|
1259
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;
|
|
1260
1389
|
try {
|
|
1261
1390
|
await streamChat(
|
|
1262
1391
|
apiBaseUrl,
|
|
@@ -1264,12 +1393,22 @@ function useXbNovaChat(options) {
|
|
|
1264
1393
|
{
|
|
1265
1394
|
bot_id: botId,
|
|
1266
1395
|
message: trimmed,
|
|
1267
|
-
conversation_id:
|
|
1396
|
+
conversation_id: conversationIdRef.current,
|
|
1268
1397
|
context,
|
|
1269
1398
|
nudge_node_id: nudgeNodeId ?? null,
|
|
1270
|
-
opening_message:
|
|
1399
|
+
opening_message: openingMessage2,
|
|
1400
|
+
visitor_id: userKey || null,
|
|
1401
|
+
thread_key: threadKey ?? null,
|
|
1402
|
+
session_id: sessionId
|
|
1271
1403
|
},
|
|
1272
|
-
{
|
|
1404
|
+
{
|
|
1405
|
+
onPart: replaceLast,
|
|
1406
|
+
onMessageId: setLastAssistantId,
|
|
1407
|
+
onConversationId: (id) => {
|
|
1408
|
+
conversationIdRef.current = id;
|
|
1409
|
+
},
|
|
1410
|
+
onError: setError
|
|
1411
|
+
}
|
|
1273
1412
|
);
|
|
1274
1413
|
} catch (e) {
|
|
1275
1414
|
setError(e instanceof Error ? e.message : String(e));
|
|
@@ -1277,7 +1416,19 @@ function useXbNovaChat(options) {
|
|
|
1277
1416
|
setBusy(false);
|
|
1278
1417
|
}
|
|
1279
1418
|
}
|
|
1280
|
-
|
|
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
|
+
};
|
|
1281
1432
|
}
|
|
1282
1433
|
|
|
1283
1434
|
// src/views.tsx
|
|
@@ -1410,6 +1561,13 @@ function hasVisiblePart(parts, clientActionHandlers, clientResults) {
|
|
|
1410
1561
|
}
|
|
1411
1562
|
var DISCLOSURE_SEEN_KEY = "xbnova_disclosure_seen";
|
|
1412
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
|
+
}
|
|
1413
1571
|
function readDisclosureSeen() {
|
|
1414
1572
|
if (typeof window === "undefined") return true;
|
|
1415
1573
|
try {
|
|
@@ -1428,7 +1586,7 @@ function markDisclosureSeen() {
|
|
|
1428
1586
|
var DESKTOP_MEDIA_QUERY = "(min-width: 640px)";
|
|
1429
1587
|
function useIsDesktopViewport() {
|
|
1430
1588
|
const [isDesktop, setIsDesktop] = useState4(false);
|
|
1431
|
-
|
|
1589
|
+
useEffect9(() => {
|
|
1432
1590
|
if (typeof window === "undefined") return;
|
|
1433
1591
|
const mql = window.matchMedia(DESKTOP_MEDIA_QUERY);
|
|
1434
1592
|
setIsDesktop(mql.matches);
|
|
@@ -1452,7 +1610,7 @@ function backdropBaseStyle(isDesktop) {
|
|
|
1452
1610
|
}
|
|
1453
1611
|
function useTypewriter(text, key, speedMs = 28) {
|
|
1454
1612
|
const [shown, setShown] = useState4("");
|
|
1455
|
-
|
|
1613
|
+
useEffect9(() => {
|
|
1456
1614
|
setShown("");
|
|
1457
1615
|
if (!text) return;
|
|
1458
1616
|
let i = 0;
|
|
@@ -1539,9 +1697,10 @@ function XbNovaChat({
|
|
|
1539
1697
|
avatarSize = 28,
|
|
1540
1698
|
onVerdictAction,
|
|
1541
1699
|
feedback: feedbackEnabled = true,
|
|
1542
|
-
onFeedback
|
|
1700
|
+
onFeedback,
|
|
1701
|
+
onOpen
|
|
1543
1702
|
}) {
|
|
1544
|
-
const { auth, trackRegistry, factsRegistry,
|
|
1703
|
+
const { auth, trackRegistry, factsRegistry, clientActionHandlers, theme, sendEvent } = useXbNovaInternal();
|
|
1545
1704
|
const themeVars = {
|
|
1546
1705
|
"--xbnova-accent-1": theme.accentFrom,
|
|
1547
1706
|
"--xbnova-accent-2": theme.accentTo
|
|
@@ -1562,7 +1721,7 @@ function XbNovaChat({
|
|
|
1562
1721
|
const [input, setInput] = useState4("");
|
|
1563
1722
|
const bottomRef = useRef8(null);
|
|
1564
1723
|
const typedHintText = useTypewriter(hint?.text ?? "", hint?.triggerNodeId ?? "");
|
|
1565
|
-
const { onboarding, messages, busy, error, send, clientResults, feedback, setFeedback } = useXbNovaChat({
|
|
1724
|
+
const { onboarding, opening, messages, busy, error, send, clientResults, feedback, setFeedback, trackClientActionClick } = useXbNovaChat({
|
|
1566
1725
|
onFeedback,
|
|
1567
1726
|
botId: activeBotId ?? botId,
|
|
1568
1727
|
nudgeNodeId: activeNudgeNodeId,
|
|
@@ -1571,7 +1730,7 @@ function XbNovaChat({
|
|
|
1571
1730
|
});
|
|
1572
1731
|
const effectiveBotId = activeBotId ?? botId;
|
|
1573
1732
|
const [cachedBranding, setCachedBranding] = useState4(null);
|
|
1574
|
-
|
|
1733
|
+
useEffect9(() => {
|
|
1575
1734
|
try {
|
|
1576
1735
|
const raw = window.localStorage.getItem(`xbnova_branding:${effectiveBotId}`);
|
|
1577
1736
|
setCachedBranding(raw ? JSON.parse(raw) : null);
|
|
@@ -1579,22 +1738,29 @@ function XbNovaChat({
|
|
|
1579
1738
|
setCachedBranding(null);
|
|
1580
1739
|
}
|
|
1581
1740
|
}, [effectiveBotId]);
|
|
1582
|
-
|
|
1741
|
+
useEffect9(() => {
|
|
1583
1742
|
if (!onboarding?.branding) return;
|
|
1584
1743
|
try {
|
|
1585
1744
|
window.localStorage.setItem(
|
|
1586
1745
|
`xbnova_branding:${effectiveBotId}`,
|
|
1587
|
-
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
|
+
})
|
|
1588
1752
|
);
|
|
1589
1753
|
} catch {
|
|
1590
1754
|
}
|
|
1591
1755
|
}, [onboarding?.branding, effectiveBotId]);
|
|
1592
1756
|
const branding = onboarding?.branding ?? cachedBranding ?? void 0;
|
|
1757
|
+
const badgeText = branding?.badge_text?.trim() ?? "";
|
|
1758
|
+
const disclaimer = branding?.disclaimer?.trim() ?? "";
|
|
1593
1759
|
const brandingReady = Boolean(onboarding) || cachedBranding !== null || Boolean(error);
|
|
1594
|
-
|
|
1760
|
+
useEffect9(() => {
|
|
1595
1761
|
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
1596
1762
|
}, [messages]);
|
|
1597
|
-
|
|
1763
|
+
useEffect9(() => {
|
|
1598
1764
|
if (!open) {
|
|
1599
1765
|
setEntered(false);
|
|
1600
1766
|
return;
|
|
@@ -1602,7 +1768,7 @@ function XbNovaChat({
|
|
|
1602
1768
|
const raf = requestAnimationFrame(() => setEntered(true));
|
|
1603
1769
|
return () => cancelAnimationFrame(raf);
|
|
1604
1770
|
}, [open]);
|
|
1605
|
-
|
|
1771
|
+
useEffect9(() => {
|
|
1606
1772
|
if (!hint || hint.autoHideMs <= 0) return;
|
|
1607
1773
|
console.debug(`[XbNova] \uD78C\uD2B8 "${hint.text}" \uD45C\uC2DC \u2014 ${hint.autoHideMs}ms \uD6C4 \uC790\uB3D9\uC73C\uB85C \uC0AC\uB77C\uC9D0`);
|
|
1608
1774
|
const timer = window.setTimeout(() => {
|
|
@@ -1619,18 +1785,7 @@ function XbNovaChat({
|
|
|
1619
1785
|
setOpeningLine(consumed.text);
|
|
1620
1786
|
setActiveNudgeNodeId(consumed.messageNodeId);
|
|
1621
1787
|
setNudgeQuickReplies(consumed.quickReplies);
|
|
1622
|
-
|
|
1623
|
-
method: "POST",
|
|
1624
|
-
headers: { "Content-Type": "application/json" },
|
|
1625
|
-
body: JSON.stringify({
|
|
1626
|
-
app_id: auth.appId,
|
|
1627
|
-
nudge_node_id: consumed.messageNodeId,
|
|
1628
|
-
kind: "nudge_open",
|
|
1629
|
-
bot_id: consumed.botId,
|
|
1630
|
-
visitor_id: visitorId
|
|
1631
|
-
})
|
|
1632
|
-
}).catch(() => {
|
|
1633
|
-
});
|
|
1788
|
+
sendEvent("nudge_open", { nudge_node_id: consumed.messageNodeId, bot_id: consumed.botId });
|
|
1634
1789
|
} else {
|
|
1635
1790
|
setNudgeQuickReplies([]);
|
|
1636
1791
|
setActiveNudgeNodeId(void 0);
|
|
@@ -1644,6 +1799,11 @@ function XbNovaChat({
|
|
|
1644
1799
|
markDisclosureSeen();
|
|
1645
1800
|
}
|
|
1646
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
|
+
}
|
|
1647
1807
|
}
|
|
1648
1808
|
if (!open) {
|
|
1649
1809
|
if (barHidden) return null;
|
|
@@ -1739,7 +1899,6 @@ function XbNovaChat({
|
|
|
1739
1899
|
const summary = resolveFactValue(factsRegistry.get(SUMMARY_FACT_KEY));
|
|
1740
1900
|
const focusedLabel = getFocusedTrackEntry(trackRegistry)?.label;
|
|
1741
1901
|
const contextLine = summary ?? (focusedLabel ? `${focusedLabel} \uBCF4\uB294 \uC911` : void 0);
|
|
1742
|
-
const firstBubble = openingLine ?? onboarding?.greeting;
|
|
1743
1902
|
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1744
1903
|
!isDesktop ? /* @__PURE__ */ jsx7(
|
|
1745
1904
|
XbNovaShadowRoot,
|
|
@@ -1776,7 +1935,10 @@ function XbNovaChat({
|
|
|
1776
1935
|
}
|
|
1777
1936
|
),
|
|
1778
1937
|
/* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
|
|
1779
|
-
/* @__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
|
+
] }),
|
|
1780
1942
|
contextLine ? /* @__PURE__ */ jsx7("div", { className: "truncate text-[11px] text-neutral-400", children: contextLine }) : null
|
|
1781
1943
|
] }),
|
|
1782
1944
|
/* @__PURE__ */ jsx7(
|
|
@@ -1792,7 +1954,7 @@ function XbNovaChat({
|
|
|
1792
1954
|
] }),
|
|
1793
1955
|
/* @__PURE__ */ jsxs3("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
|
|
1794
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,
|
|
1795
|
-
|
|
1957
|
+
opening ? /* @__PURE__ */ jsx7(Bubble, { role: "assistant", children: /* @__PURE__ */ jsx7(AssistantMarkdown, { text: opening }) }) : null,
|
|
1796
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(
|
|
1797
1959
|
AnswerActionsFrame,
|
|
1798
1960
|
{
|
|
@@ -1820,7 +1982,17 @@ function XbNovaChat({
|
|
|
1820
1982
|
);
|
|
1821
1983
|
}
|
|
1822
1984
|
const spec = clientActionHandlers.get(part.toolName);
|
|
1823
|
-
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;
|
|
1824
1996
|
}
|
|
1825
1997
|
return /* @__PURE__ */ jsx7(ToolView, { part, viewRenderers, onVerdictAction }, j);
|
|
1826
1998
|
}),
|
|
@@ -1891,7 +2063,8 @@ function XbNovaChat({
|
|
|
1891
2063
|
)
|
|
1892
2064
|
]
|
|
1893
2065
|
}
|
|
1894
|
-
)
|
|
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
|
|
1895
2068
|
] })
|
|
1896
2069
|
}
|
|
1897
2070
|
)
|
|
@@ -1993,8 +2166,8 @@ function AnswerActions({
|
|
|
1993
2166
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
1994
2167
|
timerRef.current = null;
|
|
1995
2168
|
};
|
|
1996
|
-
|
|
1997
|
-
|
|
2169
|
+
useEffect9(() => clearTimer, []);
|
|
2170
|
+
useEffect9(() => {
|
|
1998
2171
|
if (!menuOpen) {
|
|
1999
2172
|
setEntered(false);
|
|
2000
2173
|
return;
|
|
@@ -2141,13 +2314,13 @@ function AnswerActionsFrame({
|
|
|
2141
2314
|
}
|
|
2142
2315
|
|
|
2143
2316
|
// src/facts.ts
|
|
2144
|
-
import { useEffect as
|
|
2317
|
+
import { useEffect as useEffect10, useId, useRef as useRef9 } from "react";
|
|
2145
2318
|
function useXbNovaFact(key, value) {
|
|
2146
2319
|
const { factsRegistry } = useXbNovaInternal();
|
|
2147
|
-
|
|
2320
|
+
useEffect10(() => {
|
|
2148
2321
|
factsRegistry.set(key, value);
|
|
2149
2322
|
});
|
|
2150
|
-
|
|
2323
|
+
useEffect10(() => {
|
|
2151
2324
|
return () => {
|
|
2152
2325
|
factsRegistry.delete(key);
|
|
2153
2326
|
};
|
|
@@ -2156,13 +2329,13 @@ function useXbNovaFact(key, value) {
|
|
|
2156
2329
|
function useXbNovaFacts(values) {
|
|
2157
2330
|
const { factsRegistry } = useXbNovaInternal();
|
|
2158
2331
|
const keysRef = useRef9([]);
|
|
2159
|
-
|
|
2332
|
+
useEffect10(() => {
|
|
2160
2333
|
for (const [key, value] of Object.entries(values)) {
|
|
2161
2334
|
factsRegistry.set(key, value);
|
|
2162
2335
|
}
|
|
2163
2336
|
keysRef.current = Object.keys(values);
|
|
2164
2337
|
});
|
|
2165
|
-
|
|
2338
|
+
useEffect10(() => {
|
|
2166
2339
|
return () => {
|
|
2167
2340
|
for (const key of keysRef.current) {
|
|
2168
2341
|
factsRegistry.delete(key);
|
|
@@ -2173,10 +2346,10 @@ function useXbNovaFacts(values) {
|
|
|
2173
2346
|
function useXbNovaContext(entries) {
|
|
2174
2347
|
const { contextRegistry } = useXbNovaInternal();
|
|
2175
2348
|
const id = useId();
|
|
2176
|
-
|
|
2349
|
+
useEffect10(() => {
|
|
2177
2350
|
contextRegistry.set(id, entries);
|
|
2178
2351
|
});
|
|
2179
|
-
|
|
2352
|
+
useEffect10(() => {
|
|
2180
2353
|
return () => {
|
|
2181
2354
|
contextRegistry.delete(id);
|
|
2182
2355
|
};
|