@xbrain-swe/nova-chat-widget 0.2.4-dev.15 → 0.3.0-dev.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +62 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +62 -42
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -171,7 +171,8 @@ type UseXbNovaChatResult = {
|
|
|
171
171
|
messages: ChatMessage[];
|
|
172
172
|
busy: boolean;
|
|
173
173
|
error: string | null;
|
|
174
|
-
|
|
174
|
+
/** `nudgeOpening`은 대화 중 넛지를 눌러 띄운 질문이다. 답을 보낼 때 그 앞에 AI 발화로 남긴다. */
|
|
175
|
+
send: (text: string, nudgeOpening?: string | null) => Promise<void>;
|
|
175
176
|
/** client action 핸들러가 돌려준 정규화 결과(tool_call_id별). 채팅 화면이 view 렌더러로 그린다. */
|
|
176
177
|
clientResults: ClientResults;
|
|
177
178
|
/** messageId → 현재 평가. 평가 안 한 메시지는 키가 없다. */
|
package/dist/index.d.ts
CHANGED
|
@@ -171,7 +171,8 @@ type UseXbNovaChatResult = {
|
|
|
171
171
|
messages: ChatMessage[];
|
|
172
172
|
busy: boolean;
|
|
173
173
|
error: string | null;
|
|
174
|
-
|
|
174
|
+
/** `nudgeOpening`은 대화 중 넛지를 눌러 띄운 질문이다. 답을 보낼 때 그 앞에 AI 발화로 남긴다. */
|
|
175
|
+
send: (text: string, nudgeOpening?: string | null) => Promise<void>;
|
|
175
176
|
/** client action 핸들러가 돌려준 정규화 결과(tool_call_id별). 채팅 화면이 view 렌더러로 그린다. */
|
|
176
177
|
clientResults: ClientResults;
|
|
177
178
|
/** messageId → 현재 평가. 평가 안 한 메시지는 키가 없다. */
|
package/dist/index.js
CHANGED
|
@@ -70,23 +70,34 @@ function InternalProvider({
|
|
|
70
70
|
const sessionId = auth.sessionKey || null;
|
|
71
71
|
const latestRef = useRef({ auth, visitorId, sessionId });
|
|
72
72
|
latestRef.current = { auth, visitorId, sessionId };
|
|
73
|
-
const sendEvent = useCallback((
|
|
73
|
+
const sendEvent = useCallback((event, properties = {}) => {
|
|
74
74
|
const { auth: a, visitorId: v, sessionId: sid } = latestRef.current;
|
|
75
|
-
|
|
75
|
+
if (!v) return;
|
|
76
|
+
const { conversation_id: conversationId, occurred_at: occurredAt, ...rest } = properties;
|
|
77
|
+
fetch(`${a.apiBaseUrl}/v1/events`, {
|
|
76
78
|
method: "POST",
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
keepalive: true,
|
|
80
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${a.apiKey}` },
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
app_id: a.appId,
|
|
83
|
+
event,
|
|
84
|
+
visitor_id: v,
|
|
85
|
+
session_id: sid,
|
|
86
|
+
conversation_id: conversationId ?? null,
|
|
87
|
+
occurred_at: occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
88
|
+
properties: { screen: a.screen, ...rest }
|
|
89
|
+
})
|
|
79
90
|
}).catch(() => {
|
|
80
91
|
});
|
|
81
92
|
}, []);
|
|
82
93
|
const widgetViewSentRef = useRef(false);
|
|
94
|
+
const mountedAtRef = useRef((/* @__PURE__ */ new Date()).toISOString());
|
|
83
95
|
useEffect(() => {
|
|
84
96
|
if (widgetViewSentRef.current) return;
|
|
85
97
|
const send = () => {
|
|
86
98
|
if (widgetViewSentRef.current) return;
|
|
87
99
|
widgetViewSentRef.current = true;
|
|
88
|
-
|
|
89
|
-
sendEvent("widget_view", screen ? { payload: { screen } } : {});
|
|
100
|
+
sendEvent("widget_view", { occurred_at: mountedAtRef.current });
|
|
90
101
|
};
|
|
91
102
|
if (sessionId) {
|
|
92
103
|
send();
|
|
@@ -1176,7 +1187,6 @@ function useXbNovaChat(options) {
|
|
|
1176
1187
|
const [resumed, setResumed] = useState3(false);
|
|
1177
1188
|
const [sentOpening, setSentOpening] = useState3(null);
|
|
1178
1189
|
const conversationIdRef = useRef7(null);
|
|
1179
|
-
const pendingOpeningRef = useRef7(null);
|
|
1180
1190
|
const messagesRef = useRef7(messages);
|
|
1181
1191
|
messagesRef.current = messages;
|
|
1182
1192
|
const [busy, setBusy] = useState3(false);
|
|
@@ -1199,7 +1209,6 @@ function useXbNovaChat(options) {
|
|
|
1199
1209
|
setFeedbackState({});
|
|
1200
1210
|
dispatchedClientActionsRef.current = /* @__PURE__ */ new Set();
|
|
1201
1211
|
conversationIdRef.current = null;
|
|
1202
|
-
pendingOpeningRef.current = null;
|
|
1203
1212
|
fetch(`${apiBaseUrl}/v1/conversations`, {
|
|
1204
1213
|
method: "POST",
|
|
1205
1214
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
@@ -1232,16 +1241,6 @@ function useXbNovaChat(options) {
|
|
|
1232
1241
|
cancelled = true;
|
|
1233
1242
|
};
|
|
1234
1243
|
}, [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]);
|
|
1245
1244
|
const appendToLastAssistant = (existingParts, newParts) => setMessages((prev) => {
|
|
1246
1245
|
const next = [...prev];
|
|
1247
1246
|
next[next.length - 1] = { ...next[next.length - 1], role: "assistant", parts: [...existingParts, ...newParts] };
|
|
@@ -1274,6 +1273,7 @@ function useXbNovaChat(options) {
|
|
|
1274
1273
|
}
|
|
1275
1274
|
);
|
|
1276
1275
|
if (!res.ok) throw new Error(`${res.status}`);
|
|
1276
|
+
sendEvent("feedback_submit", { conversation_id: conversationId, message_id: messageId, rating, reason });
|
|
1277
1277
|
options?.onFeedback?.({ conversationId, messageId, rating, reason });
|
|
1278
1278
|
} catch (e) {
|
|
1279
1279
|
console.warn("[xbnova] \uD53C\uB4DC\uBC31 \uC800\uC7A5 \uC2E4\uD328", e);
|
|
@@ -1283,7 +1283,7 @@ function useXbNovaChat(options) {
|
|
|
1283
1283
|
function postClientActionEvent(kind, action, label) {
|
|
1284
1284
|
const conversationId = conversationIdRef.current;
|
|
1285
1285
|
if (!conversationId) return;
|
|
1286
|
-
sendEvent(kind, { conversation_id: conversationId,
|
|
1286
|
+
sendEvent(kind, { conversation_id: conversationId, action, label });
|
|
1287
1287
|
}
|
|
1288
1288
|
function trackClientActionClick(action, label) {
|
|
1289
1289
|
postClientActionEvent("cta_click", action, label);
|
|
@@ -1359,13 +1359,20 @@ function useXbNovaChat(options) {
|
|
|
1359
1359
|
void runClientAction(spec, part, last.parts);
|
|
1360
1360
|
}
|
|
1361
1361
|
}, [messages, clientActionHandlers, onboarding, apiBaseUrl, apiKey, botId]);
|
|
1362
|
-
async function send(text) {
|
|
1362
|
+
async function send(text, nudgeOpening) {
|
|
1363
1363
|
const trimmed = text.trim();
|
|
1364
1364
|
if (!trimmed || busy) return;
|
|
1365
|
+
sendEvent("message_send", {
|
|
1366
|
+
conversation_id: conversationIdRef.current,
|
|
1367
|
+
is_first: messages.length === 0,
|
|
1368
|
+
length: trimmed.length
|
|
1369
|
+
});
|
|
1365
1370
|
setError(null);
|
|
1366
1371
|
setBusy(true);
|
|
1372
|
+
const resumedOpening = messages.length > 0 && nudgeOpening ? nudgeOpening : null;
|
|
1367
1373
|
setMessages((prev) => [
|
|
1368
1374
|
...prev,
|
|
1375
|
+
...resumedOpening ? [{ role: "assistant", parts: [{ kind: "text", id: `nudge:${prev.length}`, text: resumedOpening }] }] : [],
|
|
1369
1376
|
{ role: "user", parts: [{ kind: "text", id: "u", text: trimmed }] },
|
|
1370
1377
|
{ role: "assistant", parts: [] }
|
|
1371
1378
|
]);
|
|
@@ -1378,14 +1385,13 @@ function useXbNovaChat(options) {
|
|
|
1378
1385
|
const notes = collectContextEntries(contextRegistry);
|
|
1379
1386
|
const screen = focused ? { region: focused.region, data: resolveFactValue(focused.data) } : {};
|
|
1380
1387
|
const context = focused || notes.length ? { ...screen, ...notes.length ? { notes } : {} } : void 0;
|
|
1381
|
-
let
|
|
1388
|
+
let openingMessage;
|
|
1382
1389
|
if (messages.length === 0) {
|
|
1383
|
-
|
|
1384
|
-
setSentOpening(
|
|
1390
|
+
openingMessage = (options?.openingMessage ?? onboarding?.greeting) || null;
|
|
1391
|
+
setSentOpening(openingMessage);
|
|
1385
1392
|
} else {
|
|
1386
|
-
|
|
1393
|
+
openingMessage = resumedOpening;
|
|
1387
1394
|
}
|
|
1388
|
-
pendingOpeningRef.current = null;
|
|
1389
1395
|
try {
|
|
1390
1396
|
await streamChat(
|
|
1391
1397
|
apiBaseUrl,
|
|
@@ -1396,7 +1402,7 @@ function useXbNovaChat(options) {
|
|
|
1396
1402
|
conversation_id: conversationIdRef.current,
|
|
1397
1403
|
context,
|
|
1398
1404
|
nudge_node_id: nudgeNodeId ?? null,
|
|
1399
|
-
opening_message:
|
|
1405
|
+
opening_message: openingMessage,
|
|
1400
1406
|
visitor_id: userKey || null,
|
|
1401
1407
|
thread_key: threadKey ?? null,
|
|
1402
1408
|
session_id: sessionId
|
|
@@ -1416,7 +1422,7 @@ function useXbNovaChat(options) {
|
|
|
1416
1422
|
setBusy(false);
|
|
1417
1423
|
}
|
|
1418
1424
|
}
|
|
1419
|
-
const opening = resumed ? null : sentOpening
|
|
1425
|
+
const opening = resumed ? null : messages.length > 0 ? sentOpening : options?.openingMessage ?? onboarding?.greeting ?? null;
|
|
1420
1426
|
return {
|
|
1421
1427
|
onboarding,
|
|
1422
1428
|
opening,
|
|
@@ -1716,6 +1722,7 @@ function XbNovaChat({
|
|
|
1716
1722
|
const [activeBotId, setActiveBotId] = useState4(void 0);
|
|
1717
1723
|
const [activeNudgeNodeId, setActiveNudgeNodeId] = useState4(void 0);
|
|
1718
1724
|
const [nudgeQuickReplies, setNudgeQuickReplies] = useState4([]);
|
|
1725
|
+
const [nudgePrompt, setNudgePrompt] = useState4(null);
|
|
1719
1726
|
const [openingLine, setOpeningLine] = useState4(null);
|
|
1720
1727
|
const [showDisclosure, setShowDisclosure] = useState4(false);
|
|
1721
1728
|
const [input, setInput] = useState4("");
|
|
@@ -1785,9 +1792,11 @@ function XbNovaChat({
|
|
|
1785
1792
|
setOpeningLine(consumed.text);
|
|
1786
1793
|
setActiveNudgeNodeId(consumed.messageNodeId);
|
|
1787
1794
|
setNudgeQuickReplies(consumed.quickReplies);
|
|
1795
|
+
setNudgePrompt(consumed.text);
|
|
1788
1796
|
sendEvent("nudge_open", { nudge_node_id: consumed.messageNodeId, bot_id: consumed.botId });
|
|
1789
1797
|
} else {
|
|
1790
1798
|
setNudgeQuickReplies([]);
|
|
1799
|
+
setNudgePrompt(null);
|
|
1791
1800
|
setActiveNudgeNodeId(void 0);
|
|
1792
1801
|
const focused = getFocusedTrackEntry(trackRegistry);
|
|
1793
1802
|
setOpeningLine(
|
|
@@ -1799,12 +1808,17 @@ function XbNovaChat({
|
|
|
1799
1808
|
markDisclosureSeen();
|
|
1800
1809
|
}
|
|
1801
1810
|
setOpen(true);
|
|
1811
|
+
sendEvent("chat_open", { from_nudge: Boolean(consumed) });
|
|
1802
1812
|
try {
|
|
1803
1813
|
onOpen?.({ fromNudge: Boolean(consumed), hintText: consumed?.text });
|
|
1804
1814
|
} catch (err) {
|
|
1805
1815
|
console.warn("[XbNova] onOpen \uCF5C\uBC31 \uC624\uB958", err);
|
|
1806
1816
|
}
|
|
1807
1817
|
}
|
|
1818
|
+
function closeSheet() {
|
|
1819
|
+
setOpen(false);
|
|
1820
|
+
sendEvent("chat_close");
|
|
1821
|
+
}
|
|
1808
1822
|
if (!open) {
|
|
1809
1823
|
if (barHidden) return null;
|
|
1810
1824
|
const showHint = Boolean(hint && hintsEnabled);
|
|
@@ -1909,7 +1923,7 @@ function XbNovaChat({
|
|
|
1909
1923
|
transition: "opacity 260ms ease-out",
|
|
1910
1924
|
...backdropStyle
|
|
1911
1925
|
},
|
|
1912
|
-
children: /* @__PURE__ */ jsx7("div", { className: "h-full w-full bg-black/45", onClick:
|
|
1926
|
+
children: /* @__PURE__ */ jsx7("div", { className: "h-full w-full bg-black/45", onClick: closeSheet })
|
|
1913
1927
|
}
|
|
1914
1928
|
) : null,
|
|
1915
1929
|
/* @__PURE__ */ jsx7(
|
|
@@ -1945,7 +1959,7 @@ function XbNovaChat({
|
|
|
1945
1959
|
"button",
|
|
1946
1960
|
{
|
|
1947
1961
|
type: "button",
|
|
1948
|
-
onClick:
|
|
1962
|
+
onClick: closeSheet,
|
|
1949
1963
|
"aria-label": "\uCC44\uD305 \uB2EB\uAE30",
|
|
1950
1964
|
className: "shrink-0 text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-200",
|
|
1951
1965
|
children: "\xD7"
|
|
@@ -2004,19 +2018,24 @@ function XbNovaChat({
|
|
|
2004
2018
|
/* @__PURE__ */ jsx7("div", { ref: bottomRef })
|
|
2005
2019
|
] }),
|
|
2006
2020
|
(() => {
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
{
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2021
|
+
const prompt = messages.length > 0 ? nudgePrompt : null;
|
|
2022
|
+
if (prompt || nudgeQuickReplies.length > 0) {
|
|
2023
|
+
return /* @__PURE__ */ jsxs3("div", { className: "space-y-2 border-t border-neutral-200 px-3.5 py-2.5 dark:border-neutral-700", children: [
|
|
2024
|
+
prompt ? /* @__PURE__ */ jsx7("div", { className: "text-sm font-medium", children: /* @__PURE__ */ jsx7(AssistantMarkdown, { text: prompt }) }) : null,
|
|
2025
|
+
nudgeQuickReplies.length > 0 ? /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-1.5", children: nudgeQuickReplies.map((q) => /* @__PURE__ */ jsx7(
|
|
2026
|
+
"button",
|
|
2027
|
+
{
|
|
2028
|
+
onClick: () => {
|
|
2029
|
+
setNudgeQuickReplies([]);
|
|
2030
|
+
setNudgePrompt(null);
|
|
2031
|
+
send(q, prompt);
|
|
2032
|
+
},
|
|
2033
|
+
className: "xbnova-chip rounded-full border px-2.5 py-1 text-[11px] font-semibold transition",
|
|
2034
|
+
children: q
|
|
2014
2035
|
},
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
q
|
|
2019
|
-
)) });
|
|
2036
|
+
q
|
|
2037
|
+
)) }) : null
|
|
2038
|
+
] });
|
|
2020
2039
|
}
|
|
2021
2040
|
const quickReplies = onboarding?.quick_replies ?? [];
|
|
2022
2041
|
if (messages.length !== 0 || quickReplies.length === 0) return null;
|
|
@@ -2036,7 +2055,8 @@ function XbNovaChat({
|
|
|
2036
2055
|
onSubmit: (e) => {
|
|
2037
2056
|
e.preventDefault();
|
|
2038
2057
|
setNudgeQuickReplies([]);
|
|
2039
|
-
|
|
2058
|
+
setNudgePrompt(null);
|
|
2059
|
+
send(input, messages.length > 0 ? nudgePrompt : null);
|
|
2040
2060
|
setInput("");
|
|
2041
2061
|
},
|
|
2042
2062
|
className: "flex items-center gap-2 border-t border-neutral-200 px-3 py-2.5 dark:border-neutral-700",
|