@xbrain-swe/nova-chat-widget 0.1.4 → 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 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
  });
@@ -1519,7 +1545,7 @@ function XbNovaChat({
1519
1545
  const [input, setInput] = (0, import_react9.useState)("");
1520
1546
  const bottomRef = (0, import_react9.useRef)(null);
1521
1547
  const typedHintText = useTypewriter(hint?.text ?? "", hint?.triggerNodeId ?? "");
1522
- const { onboarding, messages, busy, error, send } = useXbNovaChat({
1548
+ const { onboarding, messages, busy, error, send, clientResults } = useXbNovaChat({
1523
1549
  botId: activeBotId ?? botId,
1524
1550
  nudgeNodeId: activeNudgeNodeId,
1525
1551
  onDismissSheet: () => setOpen(false)
@@ -1649,11 +1675,11 @@ function XbNovaChat({
1649
1675
  className: "flex shrink-0 items-center justify-center",
1650
1676
  style: { width: collapsedSize, height: collapsedSize },
1651
1677
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1652
- "span",
1678
+ Avatar,
1653
1679
  {
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"
1680
+ branding: onboarding?.branding,
1681
+ className: "h-7 w-7 text-[11px] shadow-md",
1682
+ gradient: avatarGradientStyle
1657
1683
  }
1658
1684
  )
1659
1685
  }
@@ -1697,11 +1723,11 @@ function XbNovaChat({
1697
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: [
1698
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: [
1699
1725
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1700
- "span",
1726
+ Avatar,
1701
1727
  {
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"
1728
+ branding: onboarding?.branding,
1729
+ className: "h-6 w-6 shrink-0 text-[10px]",
1730
+ gradient: avatarGradientStyle
1705
1731
  }
1706
1732
  ),
1707
1733
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "min-w-0 flex-1", children: [
@@ -1728,12 +1754,24 @@ function XbNovaChat({
1728
1754
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Bubble, { role: "assistant", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AssistantMarkdown, { text: part.text }) }, j);
1729
1755
  }
1730
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
+ }
1731
1769
  const spec = clientActionHandlers.get(part.toolName);
1732
1770
  return spec?.render ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: spec.render(part.input) }, j) : null;
1733
1771
  }
1734
1772
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ToolView, { part, viewRenderers, onVerdictAction }, j);
1735
1773
  }),
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
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
1737
1775
  ] }) }, i)),
1738
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,
1739
1777
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: bottomRef })
@@ -1804,6 +1842,34 @@ function XbNovaChat({
1804
1842
  )
1805
1843
  ] });
1806
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
+ }
1807
1873
  function Bubble({ role, children }) {
1808
1874
  const mine = role === "user";
1809
1875
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: mine ? "flex justify-end" : "flex justify-start", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(