@xbrain-swe/nova-chat-widget 0.3.0-dev.16 → 0.3.0-dev.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +91 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +91 -58
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -107,7 +107,7 @@ type XbNovaTheme = {
|
|
|
107
107
|
colorScheme: "light" | "dark" | "system";
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
-
declare function XbNovaProvider({ apiKey, botId, appId, screen, apiBaseUrl, clientActions, theme, userKey, threadKey, sessionKey, children, }: {
|
|
110
|
+
declare function XbNovaProvider({ apiKey, botId, appId, screen, apiBaseUrl, clientActions, theme, userKey, threadKey, sessionKey, eventProperties, children, }: {
|
|
111
111
|
apiKey: string;
|
|
112
112
|
botId: string;
|
|
113
113
|
appId: string;
|
|
@@ -123,6 +123,8 @@ declare function XbNovaProvider({ apiKey, botId, appId, screen, apiBaseUrl, clie
|
|
|
123
123
|
/** 이 방문의 세션 키(예: GA4 session_id). nova가 남기는 이벤트(위젯 노출·넛지·상담 버튼)와 대화에 실려
|
|
124
124
|
* 콘솔에서 세션 단위로 집계된다. 값이 바뀌면(세션 갱신) 그 뒤 이벤트부터 새 값으로 실린다. 안 주면 세션은 비운다. */
|
|
125
125
|
sessionKey?: string;
|
|
126
|
+
/** 모든 분석 이벤트에 함께 실을 값(예: `{ vehicle_id: data.id }`). 평평한 원시값만 넣고 개인정보는 넣지 않는다. */
|
|
127
|
+
eventProperties?: Record<string, string | number | boolean | null | undefined>;
|
|
126
128
|
/** 이 페이지가 AI에게 내어줄 client action tool — 이름별로 설명·스키마·실행 함수를
|
|
127
129
|
* 등록한다("SDK 초기화 시점에 등록"). 스펙은 App 레벨 레지스트리로 자동 동기화되고
|
|
128
130
|
* (`XbNovaClientActionSync`), 콘솔의 tool 목록에 반영된다. 핸들러는 여기서만 산다. */
|
|
@@ -171,7 +173,11 @@ type UseXbNovaChatResult = {
|
|
|
171
173
|
messages: ChatMessage[];
|
|
172
174
|
busy: boolean;
|
|
173
175
|
error: string | null;
|
|
174
|
-
|
|
176
|
+
/** `nudgeOpening`은 대화 중 넛지를 눌러 띄운 질문이다. 답을 보낼 때 그 앞에 AI 발화로 남긴다. */
|
|
177
|
+
send: (text: string, options?: {
|
|
178
|
+
nudgeOpening?: string | null;
|
|
179
|
+
inputType?: "typed" | "quick_reply";
|
|
180
|
+
}) => Promise<void>;
|
|
175
181
|
/** client action 핸들러가 돌려준 정규화 결과(tool_call_id별). 채팅 화면이 view 렌더러로 그린다. */
|
|
176
182
|
clientResults: ClientResults;
|
|
177
183
|
/** messageId → 현재 평가. 평가 안 한 메시지는 키가 없다. */
|
package/dist/index.d.ts
CHANGED
|
@@ -107,7 +107,7 @@ type XbNovaTheme = {
|
|
|
107
107
|
colorScheme: "light" | "dark" | "system";
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
-
declare function XbNovaProvider({ apiKey, botId, appId, screen, apiBaseUrl, clientActions, theme, userKey, threadKey, sessionKey, children, }: {
|
|
110
|
+
declare function XbNovaProvider({ apiKey, botId, appId, screen, apiBaseUrl, clientActions, theme, userKey, threadKey, sessionKey, eventProperties, children, }: {
|
|
111
111
|
apiKey: string;
|
|
112
112
|
botId: string;
|
|
113
113
|
appId: string;
|
|
@@ -123,6 +123,8 @@ declare function XbNovaProvider({ apiKey, botId, appId, screen, apiBaseUrl, clie
|
|
|
123
123
|
/** 이 방문의 세션 키(예: GA4 session_id). nova가 남기는 이벤트(위젯 노출·넛지·상담 버튼)와 대화에 실려
|
|
124
124
|
* 콘솔에서 세션 단위로 집계된다. 값이 바뀌면(세션 갱신) 그 뒤 이벤트부터 새 값으로 실린다. 안 주면 세션은 비운다. */
|
|
125
125
|
sessionKey?: string;
|
|
126
|
+
/** 모든 분석 이벤트에 함께 실을 값(예: `{ vehicle_id: data.id }`). 평평한 원시값만 넣고 개인정보는 넣지 않는다. */
|
|
127
|
+
eventProperties?: Record<string, string | number | boolean | null | undefined>;
|
|
126
128
|
/** 이 페이지가 AI에게 내어줄 client action tool — 이름별로 설명·스키마·실행 함수를
|
|
127
129
|
* 등록한다("SDK 초기화 시점에 등록"). 스펙은 App 레벨 레지스트리로 자동 동기화되고
|
|
128
130
|
* (`XbNovaClientActionSync`), 콘솔의 tool 목록에 반영된다. 핸들러는 여기서만 산다. */
|
|
@@ -171,7 +173,11 @@ type UseXbNovaChatResult = {
|
|
|
171
173
|
messages: ChatMessage[];
|
|
172
174
|
busy: boolean;
|
|
173
175
|
error: string | null;
|
|
174
|
-
|
|
176
|
+
/** `nudgeOpening`은 대화 중 넛지를 눌러 띄운 질문이다. 답을 보낼 때 그 앞에 AI 발화로 남긴다. */
|
|
177
|
+
send: (text: string, options?: {
|
|
178
|
+
nudgeOpening?: string | null;
|
|
179
|
+
inputType?: "typed" | "quick_reply";
|
|
180
|
+
}) => Promise<void>;
|
|
175
181
|
/** client action 핸들러가 돌려준 정규화 결과(tool_call_id별). 채팅 화면이 view 렌더러로 그린다. */
|
|
176
182
|
clientResults: ClientResults;
|
|
177
183
|
/** messageId → 현재 평가. 평가 안 한 메시지는 키가 없다. */
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@ function useXbNovaInternal() {
|
|
|
25
25
|
return ctx;
|
|
26
26
|
}
|
|
27
27
|
var VISITOR_STORAGE_KEY = "xbnova_visitor_id";
|
|
28
|
-
var
|
|
28
|
+
var HOST_IDENTITY_WAIT_MS = 1e4;
|
|
29
29
|
function resolveVisitorId() {
|
|
30
30
|
if (typeof window === "undefined") return "";
|
|
31
31
|
try {
|
|
@@ -70,10 +70,10 @@ 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
|
|
73
|
+
const postEvent = useCallback((event, properties) => {
|
|
74
74
|
const { auth: a, visitorId: v, sessionId: sid } = latestRef.current;
|
|
75
75
|
if (!v) return;
|
|
76
|
-
const { conversation_id: conversationId, ...rest } = properties;
|
|
76
|
+
const { conversation_id: conversationId, occurred_at: occurredAt, ...rest } = properties;
|
|
77
77
|
fetch(`${a.apiBaseUrl}/v1/events`, {
|
|
78
78
|
method: "POST",
|
|
79
79
|
keepalive: true,
|
|
@@ -84,26 +84,45 @@ function InternalProvider({
|
|
|
84
84
|
visitor_id: v,
|
|
85
85
|
session_id: sid,
|
|
86
86
|
conversation_id: conversationId ?? null,
|
|
87
|
-
|
|
87
|
+
occurred_at: occurredAt,
|
|
88
|
+
properties: { screen: a.screen, ...a.eventProperties, ...rest }
|
|
88
89
|
})
|
|
89
90
|
}).catch(() => {
|
|
90
91
|
});
|
|
91
92
|
}, []);
|
|
92
|
-
const
|
|
93
|
+
const eventQueueRef = useRef([]);
|
|
94
|
+
const identitySettledRef = useRef(false);
|
|
95
|
+
const flushEvents = useCallback(() => {
|
|
96
|
+
identitySettledRef.current = true;
|
|
97
|
+
for (const [event, properties] of eventQueueRef.current.splice(0)) postEvent(event, properties);
|
|
98
|
+
}, [postEvent]);
|
|
99
|
+
const sendEvent = useCallback(
|
|
100
|
+
(event, properties = {}) => {
|
|
101
|
+
const stamped = { ...properties, occurred_at: properties.occurred_at ?? (/* @__PURE__ */ new Date()).toISOString() };
|
|
102
|
+
if (identitySettledRef.current) postEvent(event, stamped);
|
|
103
|
+
else eventQueueRef.current.push([event, stamped]);
|
|
104
|
+
},
|
|
105
|
+
[postEvent]
|
|
106
|
+
);
|
|
107
|
+
const hostIdentity = auth.userKey || sessionId;
|
|
93
108
|
useEffect(() => {
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
if (widgetViewSentRef.current) return;
|
|
97
|
-
widgetViewSentRef.current = true;
|
|
98
|
-
sendEvent("widget_view");
|
|
99
|
-
};
|
|
100
|
-
if (sessionId) {
|
|
101
|
-
send();
|
|
109
|
+
if (hostIdentity) {
|
|
110
|
+
flushEvents();
|
|
102
111
|
return;
|
|
103
112
|
}
|
|
104
|
-
const timer = setTimeout(
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
const timer = setTimeout(flushEvents, HOST_IDENTITY_WAIT_MS);
|
|
114
|
+
window.addEventListener("pagehide", flushEvents);
|
|
115
|
+
return () => {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
window.removeEventListener("pagehide", flushEvents);
|
|
118
|
+
};
|
|
119
|
+
}, [hostIdentity, flushEvents]);
|
|
120
|
+
const widgetViewSentRef = useRef(false);
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (widgetViewSentRef.current) return;
|
|
123
|
+
widgetViewSentRef.current = true;
|
|
124
|
+
sendEvent("view_vehicle");
|
|
125
|
+
}, [sendEvent]);
|
|
107
126
|
const value = useMemo(
|
|
108
127
|
() => ({
|
|
109
128
|
auth,
|
|
@@ -699,7 +718,7 @@ function XbNovaNudgeLoop({ appId }) {
|
|
|
699
718
|
autoHideMs: messageNode.auto_hide_ms ?? 2e4,
|
|
700
719
|
quickReplies: messageNode.quick_replies ?? []
|
|
701
720
|
});
|
|
702
|
-
sendEvent("
|
|
721
|
+
sendEvent("nudge_AI_view", { nudge_node_id: messageNode.id, bot_id: botId });
|
|
703
722
|
};
|
|
704
723
|
const tryFireFrom = (node) => {
|
|
705
724
|
const nextEdge = (edgesFrom.get(node.id) ?? [])[0];
|
|
@@ -905,6 +924,7 @@ function XbNovaProvider({
|
|
|
905
924
|
userKey,
|
|
906
925
|
threadKey,
|
|
907
926
|
sessionKey,
|
|
927
|
+
eventProperties,
|
|
908
928
|
children
|
|
909
929
|
}) {
|
|
910
930
|
useEffect6(() => {
|
|
@@ -920,6 +940,7 @@ function XbNovaProvider({
|
|
|
920
940
|
useEffect6(() => {
|
|
921
941
|
if (screen) bumpVisitCount(screen);
|
|
922
942
|
}, [screen]);
|
|
943
|
+
const eventPropertiesJson = JSON.stringify(eventProperties ?? null);
|
|
923
944
|
const auth = useMemo2(
|
|
924
945
|
() => ({
|
|
925
946
|
apiKey,
|
|
@@ -929,9 +950,10 @@ function XbNovaProvider({
|
|
|
929
950
|
screen,
|
|
930
951
|
userKey: userKey || void 0,
|
|
931
952
|
threadKey: threadKey || void 0,
|
|
932
|
-
sessionKey: sessionKey || void 0
|
|
953
|
+
sessionKey: sessionKey || void 0,
|
|
954
|
+
eventProperties: JSON.parse(eventPropertiesJson) ?? void 0
|
|
933
955
|
}),
|
|
934
|
-
[apiKey, botId, appId, apiBaseUrl, screen, userKey, threadKey, sessionKey]
|
|
956
|
+
[apiKey, botId, appId, apiBaseUrl, screen, userKey, threadKey, sessionKey, eventPropertiesJson]
|
|
935
957
|
);
|
|
936
958
|
const resolvedTheme = useMemo2(
|
|
937
959
|
() => ({ ...DEFAULT_THEME, ...theme }),
|
|
@@ -1174,6 +1196,10 @@ async function resumeToolResult(apiBaseUrl, apiKey, body, handlers) {
|
|
|
1174
1196
|
|
|
1175
1197
|
// src/use-chat.ts
|
|
1176
1198
|
var DEFAULT_CLIENT_ACTION_TIMEOUT_MS = 15e3;
|
|
1199
|
+
var CTA_CLICK_EVENTS = {
|
|
1200
|
+
suggest_consultation: { event: "apply_AI_click", ctaType: "consult" },
|
|
1201
|
+
suggest_finance_limit: { event: "finance_AI_click", ctaType: "finance" }
|
|
1202
|
+
};
|
|
1177
1203
|
function useXbNovaChat(options) {
|
|
1178
1204
|
const { auth, trackRegistry, contextRegistry, clientActionHandlers, visitorId, sessionId, sendEvent } = useXbNovaInternal();
|
|
1179
1205
|
const botId = options?.botId ?? auth.botId;
|
|
@@ -1185,7 +1211,6 @@ function useXbNovaChat(options) {
|
|
|
1185
1211
|
const [resumed, setResumed] = useState3(false);
|
|
1186
1212
|
const [sentOpening, setSentOpening] = useState3(null);
|
|
1187
1213
|
const conversationIdRef = useRef7(null);
|
|
1188
|
-
const pendingOpeningRef = useRef7(null);
|
|
1189
1214
|
const messagesRef = useRef7(messages);
|
|
1190
1215
|
messagesRef.current = messages;
|
|
1191
1216
|
const [busy, setBusy] = useState3(false);
|
|
@@ -1208,7 +1233,6 @@ function useXbNovaChat(options) {
|
|
|
1208
1233
|
setFeedbackState({});
|
|
1209
1234
|
dispatchedClientActionsRef.current = /* @__PURE__ */ new Set();
|
|
1210
1235
|
conversationIdRef.current = null;
|
|
1211
|
-
pendingOpeningRef.current = null;
|
|
1212
1236
|
fetch(`${apiBaseUrl}/v1/conversations`, {
|
|
1213
1237
|
method: "POST",
|
|
1214
1238
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
@@ -1241,16 +1265,6 @@ function useXbNovaChat(options) {
|
|
|
1241
1265
|
cancelled = true;
|
|
1242
1266
|
};
|
|
1243
1267
|
}, [apiBaseUrl, apiKey, botId, userKey, threadKey]);
|
|
1244
|
-
const openingMessage = options?.openingMessage;
|
|
1245
|
-
useEffect8(() => {
|
|
1246
|
-
if (!onboarding || !nudgeNodeId || !openingMessage) return;
|
|
1247
|
-
if (messagesRef.current.length === 0) return;
|
|
1248
|
-
setMessages((prev) => [
|
|
1249
|
-
...prev,
|
|
1250
|
-
{ role: "assistant", parts: [{ kind: "text", id: `nudge:${nudgeNodeId}:${prev.length}`, text: openingMessage }] }
|
|
1251
|
-
]);
|
|
1252
|
-
pendingOpeningRef.current = openingMessage;
|
|
1253
|
-
}, [onboarding, nudgeNodeId, openingMessage]);
|
|
1254
1268
|
const appendToLastAssistant = (existingParts, newParts) => setMessages((prev) => {
|
|
1255
1269
|
const next = [...prev];
|
|
1256
1270
|
next[next.length - 1] = { ...next[next.length - 1], role: "assistant", parts: [...existingParts, ...newParts] };
|
|
@@ -1290,13 +1304,14 @@ function useXbNovaChat(options) {
|
|
|
1290
1304
|
apply(prevRating);
|
|
1291
1305
|
}
|
|
1292
1306
|
}
|
|
1293
|
-
function postClientActionEvent(
|
|
1307
|
+
function postClientActionEvent(event, action, label, ctaType) {
|
|
1294
1308
|
const conversationId = conversationIdRef.current;
|
|
1295
1309
|
if (!conversationId) return;
|
|
1296
|
-
sendEvent(
|
|
1310
|
+
sendEvent(event, { conversation_id: conversationId, action, label, cta_type: ctaType });
|
|
1297
1311
|
}
|
|
1298
1312
|
function trackClientActionClick(action, label) {
|
|
1299
|
-
|
|
1313
|
+
const cta = CTA_CLICK_EVENTS[action];
|
|
1314
|
+
postClientActionEvent(cta?.event ?? "cta_click", action, label, cta?.ctaType);
|
|
1300
1315
|
}
|
|
1301
1316
|
async function runClientAction(spec, part, existingParts) {
|
|
1302
1317
|
if (spec.dismissSheet) options?.onDismissSheet?.();
|
|
@@ -1369,7 +1384,7 @@ function useXbNovaChat(options) {
|
|
|
1369
1384
|
void runClientAction(spec, part, last.parts);
|
|
1370
1385
|
}
|
|
1371
1386
|
}, [messages, clientActionHandlers, onboarding, apiBaseUrl, apiKey, botId]);
|
|
1372
|
-
async function send(text) {
|
|
1387
|
+
async function send(text, { nudgeOpening, inputType = "typed" } = {}) {
|
|
1373
1388
|
const trimmed = text.trim();
|
|
1374
1389
|
if (!trimmed || busy) return;
|
|
1375
1390
|
sendEvent("message_send", {
|
|
@@ -1377,10 +1392,19 @@ function useXbNovaChat(options) {
|
|
|
1377
1392
|
is_first: messages.length === 0,
|
|
1378
1393
|
length: trimmed.length
|
|
1379
1394
|
});
|
|
1395
|
+
let firstQuestionPending = messages.length === 0;
|
|
1396
|
+
const reportFirstQuestion = (conversationId) => {
|
|
1397
|
+
if (!firstQuestionPending) return;
|
|
1398
|
+
firstQuestionPending = false;
|
|
1399
|
+
sendEvent("question_AI_first", { conversation_id: conversationId, input_type: inputType });
|
|
1400
|
+
};
|
|
1401
|
+
if (conversationIdRef.current) reportFirstQuestion(conversationIdRef.current);
|
|
1380
1402
|
setError(null);
|
|
1381
1403
|
setBusy(true);
|
|
1404
|
+
const resumedOpening = messages.length > 0 && nudgeOpening ? nudgeOpening : null;
|
|
1382
1405
|
setMessages((prev) => [
|
|
1383
1406
|
...prev,
|
|
1407
|
+
...resumedOpening ? [{ role: "assistant", parts: [{ kind: "text", id: `nudge:${prev.length}`, text: resumedOpening }] }] : [],
|
|
1384
1408
|
{ role: "user", parts: [{ kind: "text", id: "u", text: trimmed }] },
|
|
1385
1409
|
{ role: "assistant", parts: [] }
|
|
1386
1410
|
]);
|
|
@@ -1393,14 +1417,13 @@ function useXbNovaChat(options) {
|
|
|
1393
1417
|
const notes = collectContextEntries(contextRegistry);
|
|
1394
1418
|
const screen = focused ? { region: focused.region, data: resolveFactValue(focused.data) } : {};
|
|
1395
1419
|
const context = focused || notes.length ? { ...screen, ...notes.length ? { notes } : {} } : void 0;
|
|
1396
|
-
let
|
|
1420
|
+
let openingMessage;
|
|
1397
1421
|
if (messages.length === 0) {
|
|
1398
|
-
|
|
1399
|
-
setSentOpening(
|
|
1422
|
+
openingMessage = (options?.openingMessage ?? onboarding?.greeting) || null;
|
|
1423
|
+
setSentOpening(openingMessage);
|
|
1400
1424
|
} else {
|
|
1401
|
-
|
|
1425
|
+
openingMessage = resumedOpening;
|
|
1402
1426
|
}
|
|
1403
|
-
pendingOpeningRef.current = null;
|
|
1404
1427
|
try {
|
|
1405
1428
|
await streamChat(
|
|
1406
1429
|
apiBaseUrl,
|
|
@@ -1411,7 +1434,7 @@ function useXbNovaChat(options) {
|
|
|
1411
1434
|
conversation_id: conversationIdRef.current,
|
|
1412
1435
|
context,
|
|
1413
1436
|
nudge_node_id: nudgeNodeId ?? null,
|
|
1414
|
-
opening_message:
|
|
1437
|
+
opening_message: openingMessage,
|
|
1415
1438
|
visitor_id: userKey || null,
|
|
1416
1439
|
thread_key: threadKey ?? null,
|
|
1417
1440
|
session_id: sessionId
|
|
@@ -1421,6 +1444,7 @@ function useXbNovaChat(options) {
|
|
|
1421
1444
|
onMessageId: setLastAssistantId,
|
|
1422
1445
|
onConversationId: (id) => {
|
|
1423
1446
|
conversationIdRef.current = id;
|
|
1447
|
+
reportFirstQuestion(id);
|
|
1424
1448
|
},
|
|
1425
1449
|
onError: setError
|
|
1426
1450
|
}
|
|
@@ -1431,7 +1455,7 @@ function useXbNovaChat(options) {
|
|
|
1431
1455
|
setBusy(false);
|
|
1432
1456
|
}
|
|
1433
1457
|
}
|
|
1434
|
-
const opening = resumed ? null : sentOpening
|
|
1458
|
+
const opening = resumed ? null : messages.length > 0 ? sentOpening : options?.openingMessage ?? onboarding?.greeting ?? null;
|
|
1435
1459
|
return {
|
|
1436
1460
|
onboarding,
|
|
1437
1461
|
opening,
|
|
@@ -1731,6 +1755,7 @@ function XbNovaChat({
|
|
|
1731
1755
|
const [activeBotId, setActiveBotId] = useState4(void 0);
|
|
1732
1756
|
const [activeNudgeNodeId, setActiveNudgeNodeId] = useState4(void 0);
|
|
1733
1757
|
const [nudgeQuickReplies, setNudgeQuickReplies] = useState4([]);
|
|
1758
|
+
const [nudgePrompt, setNudgePrompt] = useState4(null);
|
|
1734
1759
|
const [openingLine, setOpeningLine] = useState4(null);
|
|
1735
1760
|
const [showDisclosure, setShowDisclosure] = useState4(false);
|
|
1736
1761
|
const [input, setInput] = useState4("");
|
|
@@ -1800,10 +1825,13 @@ function XbNovaChat({
|
|
|
1800
1825
|
setOpeningLine(consumed.text);
|
|
1801
1826
|
setActiveNudgeNodeId(consumed.messageNodeId);
|
|
1802
1827
|
setNudgeQuickReplies(consumed.quickReplies);
|
|
1803
|
-
|
|
1828
|
+
setNudgePrompt(consumed.text);
|
|
1829
|
+
sendEvent("nudge_AI_click", { nudge_node_id: consumed.messageNodeId, bot_id: consumed.botId });
|
|
1804
1830
|
} else {
|
|
1805
1831
|
setNudgeQuickReplies([]);
|
|
1832
|
+
setNudgePrompt(null);
|
|
1806
1833
|
setActiveNudgeNodeId(void 0);
|
|
1834
|
+
sendEvent("chat_AI_open", { source: "direct" });
|
|
1807
1835
|
const focused = getFocusedTrackEntry(trackRegistry);
|
|
1808
1836
|
setOpeningLine(
|
|
1809
1837
|
focused ? `${focused.label ?? focused.region}\uC744(\uB97C) \uBCF4\uACE0 \uACC4\uC2DC\uB124\uC694. \uAD81\uAE08\uD558\uC2E0 \uC810\uC744 \uD3B8\uD558\uAC8C \uBB3C\uC5B4\uBCF4\uC138\uC694.` : null
|
|
@@ -1814,7 +1842,6 @@ function XbNovaChat({
|
|
|
1814
1842
|
markDisclosureSeen();
|
|
1815
1843
|
}
|
|
1816
1844
|
setOpen(true);
|
|
1817
|
-
sendEvent("chat_open", { from_nudge: Boolean(consumed) });
|
|
1818
1845
|
try {
|
|
1819
1846
|
onOpen?.({ fromNudge: Boolean(consumed), hintText: consumed?.text });
|
|
1820
1847
|
} catch (err) {
|
|
@@ -2024,26 +2051,31 @@ function XbNovaChat({
|
|
|
2024
2051
|
/* @__PURE__ */ jsx7("div", { ref: bottomRef })
|
|
2025
2052
|
] }),
|
|
2026
2053
|
(() => {
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
{
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2054
|
+
const prompt = messages.length > 0 ? nudgePrompt : null;
|
|
2055
|
+
if (prompt || nudgeQuickReplies.length > 0) {
|
|
2056
|
+
return /* @__PURE__ */ jsxs3("div", { className: "space-y-2 border-t border-neutral-200 px-3.5 py-2.5 dark:border-neutral-700", children: [
|
|
2057
|
+
prompt ? /* @__PURE__ */ jsx7("div", { className: "text-sm font-medium", children: /* @__PURE__ */ jsx7(AssistantMarkdown, { text: prompt }) }) : null,
|
|
2058
|
+
nudgeQuickReplies.length > 0 ? /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-1.5", children: nudgeQuickReplies.map((q) => /* @__PURE__ */ jsx7(
|
|
2059
|
+
"button",
|
|
2060
|
+
{
|
|
2061
|
+
onClick: () => {
|
|
2062
|
+
setNudgeQuickReplies([]);
|
|
2063
|
+
setNudgePrompt(null);
|
|
2064
|
+
send(q, { nudgeOpening: prompt, inputType: "quick_reply" });
|
|
2065
|
+
},
|
|
2066
|
+
className: "xbnova-chip rounded-full border px-2.5 py-1 text-[11px] font-semibold transition",
|
|
2067
|
+
children: q
|
|
2034
2068
|
},
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
q
|
|
2039
|
-
)) });
|
|
2069
|
+
q
|
|
2070
|
+
)) }) : null
|
|
2071
|
+
] });
|
|
2040
2072
|
}
|
|
2041
2073
|
const quickReplies = onboarding?.quick_replies ?? [];
|
|
2042
2074
|
if (messages.length !== 0 || quickReplies.length === 0) return null;
|
|
2043
2075
|
return /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-1.5 border-t border-neutral-200 px-3.5 py-2.5 dark:border-neutral-700", children: quickReplies.map((q) => /* @__PURE__ */ jsx7(
|
|
2044
2076
|
"button",
|
|
2045
2077
|
{
|
|
2046
|
-
onClick: () => send(q),
|
|
2078
|
+
onClick: () => send(q, { inputType: "quick_reply" }),
|
|
2047
2079
|
className: "xbnova-chip rounded-full border px-2.5 py-1 text-[11px] font-semibold transition",
|
|
2048
2080
|
children: q
|
|
2049
2081
|
},
|
|
@@ -2056,7 +2088,8 @@ function XbNovaChat({
|
|
|
2056
2088
|
onSubmit: (e) => {
|
|
2057
2089
|
e.preventDefault();
|
|
2058
2090
|
setNudgeQuickReplies([]);
|
|
2059
|
-
|
|
2091
|
+
setNudgePrompt(null);
|
|
2092
|
+
send(input, { nudgeOpening: messages.length > 0 ? nudgePrompt : null });
|
|
2060
2093
|
setInput("");
|
|
2061
2094
|
},
|
|
2062
2095
|
className: "flex items-center gap-2 border-t border-neutral-200 px-3 py-2.5 dark:border-neutral-700",
|