@xbrain-swe/nova-chat-widget 0.1.4 → 0.1.7

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 CHANGED
@@ -934,6 +934,7 @@ function XbNovaProvider({
934
934
  {
935
935
  handler: spec.handler,
936
936
  awaitResult: spec.awaitResult ?? false,
937
+ timeoutMs: spec.timeoutMs,
937
938
  dismissSheet: spec.dismissSheet ?? false,
938
939
  render: spec.render
939
940
  }
@@ -1130,6 +1131,7 @@ async function resumeToolResult(apiBaseUrl, apiKey, body, handlers) {
1130
1131
  }
1131
1132
 
1132
1133
  // src/use-chat.ts
1134
+ var DEFAULT_CLIENT_ACTION_TIMEOUT_MS = 15e3;
1133
1135
  function useXbNovaChat(options) {
1134
1136
  const { auth, trackRegistry, clientActionHandlers } = useXbNovaInternal();
1135
1137
  const botId = options?.botId ?? auth.botId;
@@ -1139,6 +1141,7 @@ function useXbNovaChat(options) {
1139
1141
  const [messages, setMessages] = (0, import_react8.useState)([]);
1140
1142
  const [busy, setBusy] = (0, import_react8.useState)(false);
1141
1143
  const [error, setError] = (0, import_react8.useState)(null);
1144
+ const [clientResults, setClientResults] = (0, import_react8.useState)({});
1142
1145
  const dispatchedClientActionsRef = (0, import_react8.useRef)(/* @__PURE__ */ new Set());
1143
1146
  const nudgeNodeIdRef = (0, import_react8.useRef)(nudgeNodeId);
1144
1147
  nudgeNodeIdRef.current = nudgeNodeId;
@@ -1146,6 +1149,7 @@ function useXbNovaChat(options) {
1146
1149
  setOnboarding(null);
1147
1150
  setMessages([]);
1148
1151
  setError(null);
1152
+ setClientResults({});
1149
1153
  dispatchedClientActionsRef.current = /* @__PURE__ */ new Set();
1150
1154
  fetch(`${apiBaseUrl}/v1/conversations`, {
1151
1155
  method: "POST",
@@ -1162,7 +1166,20 @@ function useXbNovaChat(options) {
1162
1166
  if (spec.dismissSheet) options?.onDismissSheet?.();
1163
1167
  let result;
1164
1168
  try {
1165
- result = await spec.handler(part.input);
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
+ }
1166
1183
  } catch (e) {
1167
1184
  if (!spec.awaitResult) return;
1168
1185
  result = e instanceof Error ? `\uC624\uB958: ${e.message}` : "\uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
@@ -1170,7 +1187,15 @@ function useXbNovaChat(options) {
1170
1187
  if (!spec.awaitResult) return;
1171
1188
  const conversationId = onboarding?.conversation_id;
1172
1189
  if (!conversationId) return;
1173
- const resultText = typeof result === "string" && result.trim() ? result : "\uD578\uB4E4\uB7EC\uAC00 \uACB0\uACFC\uB97C \uBC18\uD658\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.";
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.";
1174
1199
  setBusy(true);
1175
1200
  try {
1176
1201
  await resumeToolResult(
@@ -1239,7 +1264,7 @@ function useXbNovaChat(options) {
1239
1264
  setBusy(false);
1240
1265
  }
1241
1266
  }
1242
- return { onboarding, messages, busy, error, send };
1267
+ return { onboarding, messages, busy, error, send, clientResults };
1243
1268
  }
1244
1269
 
1245
1270
  // src/views.tsx
@@ -1362,9 +1387,10 @@ function ToolView({
1362
1387
 
1363
1388
  // src/chat.tsx
1364
1389
  var import_jsx_runtime7 = require("react/jsx-runtime");
1365
- function hasVisiblePart(parts, clientActionHandlers) {
1390
+ function hasVisiblePart(parts, clientActionHandlers, clientResults) {
1366
1391
  return parts.some((part) => {
1367
1392
  if (part.kind === "text") return true;
1393
+ if (clientResults[part.toolCallId]) return true;
1368
1394
  const spec = clientActionHandlers.get(part.toolName);
1369
1395
  return spec ? Boolean(spec.render) : true;
1370
1396
  });
@@ -1497,6 +1523,7 @@ function XbNovaChat({
1497
1523
  backdropStyle,
1498
1524
  defaultOpen = false,
1499
1525
  idleText = "AI\uC5D0\uAC8C \uC774 \uCC28 \uBB3C\uC5B4\uBCF4\uAE30",
1526
+ avatarSize = 28,
1500
1527
  onVerdictAction
1501
1528
  }) {
1502
1529
  const { auth, trackRegistry, factsRegistry, visitorId, clientActionHandlers, theme } = useXbNovaInternal();
@@ -1504,6 +1531,7 @@ function XbNovaChat({
1504
1531
  "--xbnova-accent-1": theme.accentFrom,
1505
1532
  "--xbnova-accent-2": theme.accentTo
1506
1533
  };
1534
+ const headerAvatarSize = Math.min(36, Math.max(24, Math.round(avatarSize * 0.8)));
1507
1535
  const avatarGradientStyle = {
1508
1536
  background: "linear-gradient(135deg, var(--xbnova-accent-1), var(--xbnova-accent-2))"
1509
1537
  };
@@ -1519,7 +1547,7 @@ function XbNovaChat({
1519
1547
  const [input, setInput] = (0, import_react9.useState)("");
1520
1548
  const bottomRef = (0, import_react9.useRef)(null);
1521
1549
  const typedHintText = useTypewriter(hint?.text ?? "", hint?.triggerNodeId ?? "");
1522
- const { onboarding, messages, busy, error, send } = useXbNovaChat({
1550
+ const { onboarding, messages, busy, error, send, clientResults } = useXbNovaChat({
1523
1551
  botId: activeBotId ?? botId,
1524
1552
  nudgeNodeId: activeNudgeNodeId,
1525
1553
  onDismissSheet: () => setOpen(false)
@@ -1649,11 +1677,12 @@ function XbNovaChat({
1649
1677
  className: "flex shrink-0 items-center justify-center",
1650
1678
  style: { width: collapsedSize, height: collapsedSize },
1651
1679
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1652
- "span",
1680
+ Avatar,
1653
1681
  {
1654
- className: "flex h-7 w-7 items-center justify-center rounded-full text-[11px] font-bold text-white shadow-md",
1655
- style: avatarGradientStyle,
1656
- children: "AI"
1682
+ branding: onboarding?.branding,
1683
+ size: avatarSize,
1684
+ className: "shadow-md",
1685
+ gradient: avatarGradientStyle
1657
1686
  }
1658
1687
  )
1659
1688
  }
@@ -1697,11 +1726,12 @@ function XbNovaChat({
1697
1726
  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: [
1698
1727
  /* @__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: [
1699
1728
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1700
- "span",
1729
+ Avatar,
1701
1730
  {
1702
- className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[10px] font-bold text-white",
1703
- style: avatarGradientStyle,
1704
- children: "AI"
1731
+ branding: onboarding?.branding,
1732
+ size: headerAvatarSize,
1733
+ className: "shrink-0",
1734
+ gradient: avatarGradientStyle
1705
1735
  }
1706
1736
  ),
1707
1737
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "min-w-0 flex-1", children: [
@@ -1728,12 +1758,24 @@ function XbNovaChat({
1728
1758
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Bubble, { role: "assistant", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AssistantMarkdown, { text: part.text }) }, j);
1729
1759
  }
1730
1760
  if (clientActionHandlers.has(part.toolName)) {
1761
+ const result = clientResults[part.toolCallId];
1762
+ if (result) {
1763
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1764
+ ToolView,
1765
+ {
1766
+ part: { ...part, state: "output", view: result.view, output: result.output },
1767
+ viewRenderers,
1768
+ onVerdictAction
1769
+ },
1770
+ j
1771
+ );
1772
+ }
1731
1773
  const spec = clientActionHandlers.get(part.toolName);
1732
1774
  return spec?.render ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: spec.render(part.input) }, j) : null;
1733
1775
  }
1734
1776
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ToolView, { part, viewRenderers, onVerdictAction }, j);
1735
1777
  }),
1736
- !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
1778
+ !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
1737
1779
  ] }) }, i)),
1738
1780
  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,
1739
1781
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: bottomRef })
@@ -1804,6 +1846,37 @@ function XbNovaChat({
1804
1846
  )
1805
1847
  ] });
1806
1848
  }
1849
+ function Avatar({
1850
+ branding,
1851
+ size,
1852
+ className = "",
1853
+ gradient
1854
+ }) {
1855
+ const box = { width: size, height: size };
1856
+ const logoUrl = branding?.logo_url?.trim() ?? "";
1857
+ const [failedUrl, setFailedUrl] = (0, import_react9.useState)("");
1858
+ if (logoUrl && failedUrl !== logoUrl) {
1859
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1860
+ "img",
1861
+ {
1862
+ src: logoUrl,
1863
+ alt: "",
1864
+ onError: () => setFailedUrl(logoUrl),
1865
+ style: box,
1866
+ className: `${className} flex-none rounded-full bg-white object-cover`
1867
+ }
1868
+ );
1869
+ }
1870
+ const text = branding?.avatar_text?.trim() || "AI";
1871
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1872
+ "span",
1873
+ {
1874
+ className: `${className} flex flex-none items-center justify-center rounded-full font-bold text-white`,
1875
+ style: { ...gradient, ...box, fontSize: Math.max(10, Math.round(size * 0.4)) },
1876
+ children: text
1877
+ }
1878
+ );
1879
+ }
1807
1880
  function Bubble({ role, children }) {
1808
1881
  const mine = role === "user";
1809
1882
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: mine ? "flex justify-end" : "flex justify-start", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(