@xbrain-swe/nova-chat-widget 0.1.3 → 0.1.6
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 +102 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -30
- package/dist/index.d.ts +57 -30
- package/dist/index.js +102 -15
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -57,7 +57,8 @@ var import_react = require("react");
|
|
|
57
57
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
58
58
|
var DEFAULT_THEME = {
|
|
59
59
|
accentFrom: "#4f46e5",
|
|
60
|
-
accentTo: "#7c3aed"
|
|
60
|
+
accentTo: "#7c3aed",
|
|
61
|
+
colorScheme: "system"
|
|
61
62
|
};
|
|
62
63
|
var InternalContext = (0, import_react.createContext)(null);
|
|
63
64
|
function useXbNovaInternal() {
|
|
@@ -933,6 +934,7 @@ function XbNovaProvider({
|
|
|
933
934
|
{
|
|
934
935
|
handler: spec.handler,
|
|
935
936
|
awaitResult: spec.awaitResult ?? false,
|
|
937
|
+
timeoutMs: spec.timeoutMs,
|
|
936
938
|
dismissSheet: spec.dismissSheet ?? false,
|
|
937
939
|
render: spec.render
|
|
938
940
|
}
|
|
@@ -968,10 +970,25 @@ function loadCss() {
|
|
|
968
970
|
}
|
|
969
971
|
return cssPromise;
|
|
970
972
|
}
|
|
973
|
+
function useResolvedScheme(scheme) {
|
|
974
|
+
const [systemDark, setSystemDark] = (0, import_react7.useState)(false);
|
|
975
|
+
(0, import_react7.useEffect)(() => {
|
|
976
|
+
if (scheme !== "system") return;
|
|
977
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
978
|
+
setSystemDark(mq.matches);
|
|
979
|
+
const onChange = (e) => setSystemDark(e.matches);
|
|
980
|
+
mq.addEventListener("change", onChange);
|
|
981
|
+
return () => mq.removeEventListener("change", onChange);
|
|
982
|
+
}, [scheme]);
|
|
983
|
+
if (scheme === "system") return systemDark ? "dark" : "light";
|
|
984
|
+
return scheme;
|
|
985
|
+
}
|
|
971
986
|
function XbNovaShadowRoot({
|
|
972
987
|
children,
|
|
973
988
|
style
|
|
974
989
|
}) {
|
|
990
|
+
const { theme } = useXbNovaInternal();
|
|
991
|
+
const scheme = useResolvedScheme(theme.colorScheme);
|
|
975
992
|
const hostRef = (0, import_react7.useRef)(null);
|
|
976
993
|
const [mountNode, setMountNode] = (0, import_react7.useState)(null);
|
|
977
994
|
const [portalTarget, setPortalTarget] = (0, import_react7.useState)(null);
|
|
@@ -993,6 +1010,11 @@ function XbNovaShadowRoot({
|
|
|
993
1010
|
shadow.appendChild(mount);
|
|
994
1011
|
setMountNode(mount);
|
|
995
1012
|
}, [portalTarget]);
|
|
1013
|
+
(0, import_react7.useEffect)(() => {
|
|
1014
|
+
if (!mountNode) return;
|
|
1015
|
+
mountNode.dataset.xbnovaScheme = scheme;
|
|
1016
|
+
mountNode.style.colorScheme = scheme;
|
|
1017
|
+
}, [mountNode, scheme]);
|
|
996
1018
|
if (!portalTarget) return null;
|
|
997
1019
|
return (0, import_react_dom.createPortal)(
|
|
998
1020
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: hostRef, style, children: mountNode ? (0, import_react_dom.createPortal)(children, mountNode) : null }),
|
|
@@ -1109,6 +1131,7 @@ async function resumeToolResult(apiBaseUrl, apiKey, body, handlers) {
|
|
|
1109
1131
|
}
|
|
1110
1132
|
|
|
1111
1133
|
// src/use-chat.ts
|
|
1134
|
+
var DEFAULT_CLIENT_ACTION_TIMEOUT_MS = 15e3;
|
|
1112
1135
|
function useXbNovaChat(options) {
|
|
1113
1136
|
const { auth, trackRegistry, clientActionHandlers } = useXbNovaInternal();
|
|
1114
1137
|
const botId = options?.botId ?? auth.botId;
|
|
@@ -1118,6 +1141,7 @@ function useXbNovaChat(options) {
|
|
|
1118
1141
|
const [messages, setMessages] = (0, import_react8.useState)([]);
|
|
1119
1142
|
const [busy, setBusy] = (0, import_react8.useState)(false);
|
|
1120
1143
|
const [error, setError] = (0, import_react8.useState)(null);
|
|
1144
|
+
const [clientResults, setClientResults] = (0, import_react8.useState)({});
|
|
1121
1145
|
const dispatchedClientActionsRef = (0, import_react8.useRef)(/* @__PURE__ */ new Set());
|
|
1122
1146
|
const nudgeNodeIdRef = (0, import_react8.useRef)(nudgeNodeId);
|
|
1123
1147
|
nudgeNodeIdRef.current = nudgeNodeId;
|
|
@@ -1125,6 +1149,7 @@ function useXbNovaChat(options) {
|
|
|
1125
1149
|
setOnboarding(null);
|
|
1126
1150
|
setMessages([]);
|
|
1127
1151
|
setError(null);
|
|
1152
|
+
setClientResults({});
|
|
1128
1153
|
dispatchedClientActionsRef.current = /* @__PURE__ */ new Set();
|
|
1129
1154
|
fetch(`${apiBaseUrl}/v1/conversations`, {
|
|
1130
1155
|
method: "POST",
|
|
@@ -1141,7 +1166,20 @@ function useXbNovaChat(options) {
|
|
|
1141
1166
|
if (spec.dismissSheet) options?.onDismissSheet?.();
|
|
1142
1167
|
let result;
|
|
1143
1168
|
try {
|
|
1144
|
-
|
|
1169
|
+
if (!spec.awaitResult) {
|
|
1170
|
+
await spec.handler(part.input);
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
const timeoutMs = spec.timeoutMs ?? DEFAULT_CLIENT_ACTION_TIMEOUT_MS;
|
|
1174
|
+
let timer;
|
|
1175
|
+
const timeout = new Promise((resolve) => {
|
|
1176
|
+
timer = setTimeout(() => resolve("\uC624\uB958: \uC751\uB2F5 \uC2DC\uAC04\uC774 \uCD08\uACFC\uB418\uC5C8\uC2B5\uB2C8\uB2E4."), timeoutMs);
|
|
1177
|
+
});
|
|
1178
|
+
try {
|
|
1179
|
+
result = await Promise.race([Promise.resolve(spec.handler(part.input)), timeout]);
|
|
1180
|
+
} finally {
|
|
1181
|
+
clearTimeout(timer);
|
|
1182
|
+
}
|
|
1145
1183
|
} catch (e) {
|
|
1146
1184
|
if (!spec.awaitResult) return;
|
|
1147
1185
|
result = e instanceof Error ? `\uC624\uB958: ${e.message}` : "\uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
|
|
@@ -1149,7 +1187,15 @@ function useXbNovaChat(options) {
|
|
|
1149
1187
|
if (!spec.awaitResult) return;
|
|
1150
1188
|
const conversationId = onboarding?.conversation_id;
|
|
1151
1189
|
if (!conversationId) return;
|
|
1152
|
-
const
|
|
1190
|
+
const structured = result && typeof result === "object" ? result : null;
|
|
1191
|
+
if (structured?.view) {
|
|
1192
|
+
setClientResults((prev) => ({
|
|
1193
|
+
...prev,
|
|
1194
|
+
[part.toolCallId]: { view: structured.view, output: structured.output }
|
|
1195
|
+
}));
|
|
1196
|
+
}
|
|
1197
|
+
const rawText = structured ? structured.text : result;
|
|
1198
|
+
const resultText = typeof rawText === "string" && rawText.trim() ? rawText : "\uD578\uB4E4\uB7EC\uAC00 \uACB0\uACFC\uB97C \uBC18\uD658\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.";
|
|
1153
1199
|
setBusy(true);
|
|
1154
1200
|
try {
|
|
1155
1201
|
await resumeToolResult(
|
|
@@ -1218,7 +1264,7 @@ function useXbNovaChat(options) {
|
|
|
1218
1264
|
setBusy(false);
|
|
1219
1265
|
}
|
|
1220
1266
|
}
|
|
1221
|
-
return { onboarding, messages, busy, error, send };
|
|
1267
|
+
return { onboarding, messages, busy, error, send, clientResults };
|
|
1222
1268
|
}
|
|
1223
1269
|
|
|
1224
1270
|
// src/views.tsx
|
|
@@ -1341,9 +1387,10 @@ function ToolView({
|
|
|
1341
1387
|
|
|
1342
1388
|
// src/chat.tsx
|
|
1343
1389
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1344
|
-
function hasVisiblePart(parts, clientActionHandlers) {
|
|
1390
|
+
function hasVisiblePart(parts, clientActionHandlers, clientResults) {
|
|
1345
1391
|
return parts.some((part) => {
|
|
1346
1392
|
if (part.kind === "text") return true;
|
|
1393
|
+
if (clientResults[part.toolCallId]) return true;
|
|
1347
1394
|
const spec = clientActionHandlers.get(part.toolName);
|
|
1348
1395
|
return spec ? Boolean(spec.render) : true;
|
|
1349
1396
|
});
|
|
@@ -1498,7 +1545,7 @@ function XbNovaChat({
|
|
|
1498
1545
|
const [input, setInput] = (0, import_react9.useState)("");
|
|
1499
1546
|
const bottomRef = (0, import_react9.useRef)(null);
|
|
1500
1547
|
const typedHintText = useTypewriter(hint?.text ?? "", hint?.triggerNodeId ?? "");
|
|
1501
|
-
const { onboarding, messages, busy, error, send } = useXbNovaChat({
|
|
1548
|
+
const { onboarding, messages, busy, error, send, clientResults } = useXbNovaChat({
|
|
1502
1549
|
botId: activeBotId ?? botId,
|
|
1503
1550
|
nudgeNodeId: activeNudgeNodeId,
|
|
1504
1551
|
onDismissSheet: () => setOpen(false)
|
|
@@ -1628,11 +1675,11 @@ function XbNovaChat({
|
|
|
1628
1675
|
className: "flex shrink-0 items-center justify-center",
|
|
1629
1676
|
style: { width: collapsedSize, height: collapsedSize },
|
|
1630
1677
|
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1631
|
-
|
|
1678
|
+
Avatar,
|
|
1632
1679
|
{
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1680
|
+
branding: onboarding?.branding,
|
|
1681
|
+
className: "h-7 w-7 text-[11px] shadow-md",
|
|
1682
|
+
gradient: avatarGradientStyle
|
|
1636
1683
|
}
|
|
1637
1684
|
)
|
|
1638
1685
|
}
|
|
@@ -1676,11 +1723,11 @@ function XbNovaChat({
|
|
|
1676
1723
|
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex h-full w-full flex-col rounded-t-[20px] bg-white shadow-2xl sm:rounded-2xl sm:border sm:border-neutral-200 dark:bg-neutral-900 sm:dark:border-neutral-700", children: [
|
|
1677
1724
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("header", { className: "flex items-center gap-2 border-b border-neutral-200 px-3.5 py-2.5 dark:border-neutral-700", children: [
|
|
1678
1725
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1679
|
-
|
|
1726
|
+
Avatar,
|
|
1680
1727
|
{
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1728
|
+
branding: onboarding?.branding,
|
|
1729
|
+
className: "h-6 w-6 shrink-0 text-[10px]",
|
|
1730
|
+
gradient: avatarGradientStyle
|
|
1684
1731
|
}
|
|
1685
1732
|
),
|
|
1686
1733
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
@@ -1707,12 +1754,24 @@ function XbNovaChat({
|
|
|
1707
1754
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Bubble, { role: "assistant", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AssistantMarkdown, { text: part.text }) }, j);
|
|
1708
1755
|
}
|
|
1709
1756
|
if (clientActionHandlers.has(part.toolName)) {
|
|
1757
|
+
const result = clientResults[part.toolCallId];
|
|
1758
|
+
if (result) {
|
|
1759
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1760
|
+
ToolView,
|
|
1761
|
+
{
|
|
1762
|
+
part: { ...part, state: "output", view: result.view, output: result.output },
|
|
1763
|
+
viewRenderers,
|
|
1764
|
+
onVerdictAction
|
|
1765
|
+
},
|
|
1766
|
+
j
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1710
1769
|
const spec = clientActionHandlers.get(part.toolName);
|
|
1711
1770
|
return spec?.render ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: spec.render(part.input) }, j) : null;
|
|
1712
1771
|
}
|
|
1713
1772
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ToolView, { part, viewRenderers, onVerdictAction }, j);
|
|
1714
1773
|
}),
|
|
1715
|
-
!hasVisiblePart(message.parts, clientActionHandlers) && busy ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Bubble, { role: "assistant", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-neutral-400", children: "\uC0DD\uAC01\uD558\uB294 \uC911\u2026" }) }) : null
|
|
1774
|
+
!hasVisiblePart(message.parts, clientActionHandlers, clientResults) && busy ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Bubble, { role: "assistant", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-neutral-400", children: "\uC0DD\uAC01\uD558\uB294 \uC911\u2026" }) }) : null
|
|
1716
1775
|
] }) }, i)),
|
|
1717
1776
|
error ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rounded-lg bg-red-50 px-3 py-2 text-xs text-red-700 dark:bg-red-950 dark:text-red-300", children: error }) : null,
|
|
1718
1777
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: bottomRef })
|
|
@@ -1783,6 +1842,34 @@ function XbNovaChat({
|
|
|
1783
1842
|
)
|
|
1784
1843
|
] });
|
|
1785
1844
|
}
|
|
1845
|
+
function Avatar({
|
|
1846
|
+
branding,
|
|
1847
|
+
className,
|
|
1848
|
+
gradient
|
|
1849
|
+
}) {
|
|
1850
|
+
const logoUrl = branding?.logo_url?.trim() ?? "";
|
|
1851
|
+
const [failedUrl, setFailedUrl] = (0, import_react9.useState)("");
|
|
1852
|
+
if (logoUrl && failedUrl !== logoUrl) {
|
|
1853
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1854
|
+
"img",
|
|
1855
|
+
{
|
|
1856
|
+
src: logoUrl,
|
|
1857
|
+
alt: "",
|
|
1858
|
+
onError: () => setFailedUrl(logoUrl),
|
|
1859
|
+
className: `${className} flex-none rounded-full bg-white object-cover`
|
|
1860
|
+
}
|
|
1861
|
+
);
|
|
1862
|
+
}
|
|
1863
|
+
const text = branding?.avatar_text?.trim() || "AI";
|
|
1864
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1865
|
+
"span",
|
|
1866
|
+
{
|
|
1867
|
+
className: `${className} flex items-center justify-center rounded-full font-bold text-white`,
|
|
1868
|
+
style: gradient,
|
|
1869
|
+
children: text
|
|
1870
|
+
}
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1786
1873
|
function Bubble({ role, children }) {
|
|
1787
1874
|
const mine = role === "user";
|
|
1788
1875
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: mine ? "flex justify-end" : "flex justify-start", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|