@xapp/chat-widget 1.98.4 → 1.100.0

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.js CHANGED
@@ -4491,23 +4491,48 @@ var MCPChat = /** @class */ (function () {
4491
4491
  log("SERVER: account_status: ".concat(JSON.stringify(status)));
4492
4492
  this.dispatch(setAccountStatus(status));
4493
4493
  };
4494
- MCPChat.prototype.getEndpoint = function () {
4494
+ /**
4495
+ * The paths this mode hangs off its base URL: [stream, close].
4496
+ */
4497
+ MCPChat.prototype.getEndpointPaths = function () {
4498
+ return (this.config.mode || "homeowner") === "business_owner"
4499
+ ? [BUSINESS_OWNER_ENDPOINT_PATH, BUSINESS_OWNER_CLOSE_ENDPOINT_PATH]
4500
+ : [HOMEOWNER_ENDPOINT_PATH, HOMEOWNER_CLOSE_ENDPOINT_PATH];
4501
+ };
4502
+ /**
4503
+ * The base the endpoint paths are built from.
4504
+ *
4505
+ * A configured `serverUrl` arrives in either of two shapes: the base
4506
+ * (`https://agentic.xapp.ai/<appId>`), which is what this class was written for, or the
4507
+ * full stream endpoint (`https://agentic.xapp.ai/<appId>/chat/stream`), which is the form
4508
+ * issue #1403 documents as the Dev and Prod defaults. Appending unconditionally turns the
4509
+ * second into `.../chat/stream/chat/stream`.
4510
+ *
4511
+ * Taking `serverUrl` verbatim instead is not the fix: `/chat/close` has to be derived
4512
+ * from something, and a widget configured with the full stream URL would then send every
4513
+ * close request to the stream endpoint. So normalise back to the base and build both
4514
+ * paths from it, which leaves either configured form working.
4515
+ *
4516
+ * Only the paths belonging to the CONFIGURED mode come off. A homeowner URL handed to a
4517
+ * business_owner widget is a misconfiguration, not something to quietly reinterpret, and
4518
+ * an appId that merely ends in `/stream` is not an endpoint path.
4519
+ */
4520
+ MCPChat.prototype.getBaseUrl = function () {
4495
4521
  // Normalize serverUrl to remove trailing slashes
4496
4522
  var serverUrl = this.config.serverUrl.replace(/\/+$/, "");
4497
- var mode = this.config.mode || "homeowner";
4498
- if (mode === "business_owner") {
4499
- return "".concat(serverUrl).concat(BUSINESS_OWNER_ENDPOINT_PATH);
4523
+ for (var _i = 0, _a = this.getEndpointPaths(); _i < _a.length; _i++) {
4524
+ var path = _a[_i];
4525
+ if (serverUrl.endsWith(path)) {
4526
+ return serverUrl.slice(0, -path.length);
4527
+ }
4500
4528
  }
4501
- // homeowner mode - serverUrl should already include appId
4502
- return "".concat(serverUrl).concat(HOMEOWNER_ENDPOINT_PATH);
4529
+ return serverUrl;
4530
+ };
4531
+ MCPChat.prototype.getEndpoint = function () {
4532
+ return "".concat(this.getBaseUrl()).concat(this.getEndpointPaths()[0]);
4503
4533
  };
4504
4534
  MCPChat.prototype.getCloseEndpoint = function () {
4505
- var serverUrl = this.config.serverUrl.replace(/\/+$/, "");
4506
- var mode = this.config.mode || "homeowner";
4507
- if (mode === "business_owner") {
4508
- return "".concat(serverUrl).concat(BUSINESS_OWNER_CLOSE_ENDPOINT_PATH);
4509
- }
4510
- return "".concat(serverUrl).concat(HOMEOWNER_CLOSE_ENDPOINT_PATH);
4535
+ return "".concat(this.getBaseUrl()).concat(this.getEndpointPaths()[1]);
4511
4536
  };
4512
4537
  MCPChat.prototype.sendNewMessage = function (msg, user) {
4513
4538
  log("SERVER: new message: ".concat(JSON.stringify(msg)));
@@ -12953,7 +12978,7 @@ var ChatMessage = function (props) {
12953
12978
  return (require$$0.jsxs(require$$0.Fragment, { children: [showToolProgressWithAvatar && (require$$0.jsx(ChatMessagePart, { showAvatar: true, user: user, avatarPosition: (_g = (_f = (_e = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _e === void 0 ? void 0 : _e.theme) === null || _f === void 0 ? void 0 : _f.messages) === null || _g === void 0 ? void 0 : _g.avatarPosition, hideUserInfo: hideUserInfo, isStreaming: true, children: require$$0.jsx("div", { className: "chat-msg", children: require$$0.jsx(ToolCallDisplay, { toolCalls: toolCalls, debugMode: debugMode }) }) })), msg.text && (require$$0.jsx(ChatMessagePart, { showAvatar: avaKey === "text", user: user, avatarPosition: (_k = (_j = (_h = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _h === void 0 ? void 0 : _h.theme) === null || _j === void 0 ? void 0 : _j.messages) === null || _k === void 0 ? void 0 : _k.avatarPosition, hideUserInfo: hideUserInfo, isStreaming: isStreaming_1 || wasStreamed, children: require$$0.jsx(ChatTextMessage, { message: props.message, sibling: props.sibling, user: user, toolCalls: hasToolCalls ? toolCalls : undefined, debugMode: debugMode }) })), msg.html &&
12954
12979
  require$$0.jsx(ChatMessagePart, { showAvatar: avaKey === "html", user: user, avatarPosition: (_o = (_m = (_l = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _l === void 0 ? void 0 : _l.theme) === null || _m === void 0 ? void 0 : _m.messages) === null || _o === void 0 ? void 0 : _o.avatarPosition, hideUserInfo: hideUserInfo, children: require$$0.jsx(ChatMarkdownMessage, { message: props.message, sibling: props.sibling, onOpenUrl: (_p = props.middlewareContext) === null || _p === void 0 ? void 0 : _p.openUrl }) }), msg.displays && middleware && msg.displays.map(function (display, index) {
12955
12980
  if (display.type === "ScheduleButton") {
12956
- return (require$$0.jsx(ChatScheduleWidget, { minimizeOnClick: props.minimizeOnClick, display: display }));
12981
+ return (require$$0.jsx(ChatScheduleWidget, { minimizeOnClick: props.minimizeOnClick, display: display }, index));
12957
12982
  }
12958
12983
  var Middleware = middleware;
12959
12984
  return (require$$0.jsx(Middleware, { msg: display, ctx: props.middlewareContext }, index));
@@ -13451,11 +13476,14 @@ var Suggestions = function (props) {
13451
13476
  var actions = React.useMemo(function () { return createActions(onItemUse); }, [onItemUse]);
13452
13477
  var transformContent = function (content) {
13453
13478
  var lines = content.split('\n');
13454
- return lines.map(function (line) {
13479
+ // Keyed by position: the lines have no id of their own, and a blank line between
13480
+ // paragraphs is not distinguishable by content either. `<></>` cannot carry a key,
13481
+ // hence the explicit Fragment.
13482
+ return lines.map(function (line, index) {
13455
13483
  if (line) {
13456
- return require$$0.jsx(MarkdownBuilder, { text: line });
13484
+ return require$$0.jsx(MarkdownBuilder, { text: line }, "line-".concat(index));
13457
13485
  }
13458
- return require$$0.jsx(require$$0.Fragment, {});
13486
+ return require$$0.jsx(React.Fragment, {}, "line-".concat(index));
13459
13487
  });
13460
13488
  };
13461
13489
  var containerClass = "xappw-suggestions ".concat(props.className || "", " ").concat(props.hasWsButton ? "xappw-suggestions--with-ws-button" : "").trim();
@@ -35435,12 +35463,37 @@ var MessageList = function (_a) {
35435
35463
  var _a = containerRef.current, scrollTop = _a.scrollTop, scrollHeight = _a.scrollHeight, clientHeight = _a.clientHeight;
35436
35464
  return Math.abs(scrollHeight - clientHeight - scrollTop) < 10;
35437
35465
  }, []);
35438
- // Scroll to bottom
35466
+ /**
35467
+ * Pin the list to its newest message.
35468
+ *
35469
+ * Scrolls THIS container, deliberately - not `messagesEndRef.scrollIntoView()`.
35470
+ * scrollIntoView walks every scrollable ancestor, and its default `block: "start"` puts
35471
+ * the target at the top of each one. On first open the greeting and its chips arrive,
35472
+ * this effect fires, and the whole chat panel was smoothly dragged upward until the end
35473
+ * marker reached the top - leaving only the suggestion chips and the input on screen
35474
+ * until the user scrolled back by hand. Device, 2026-09-10.
35475
+ *
35476
+ * A container scroll cannot touch an ancestor, which is the entire point.
35477
+ *
35478
+ * `scrollTo` is feature-detected rather than assumed. It is absent in jsdom, so every
35479
+ * test that mounts this component through a passive effect throws on it -- but the
35480
+ * reason to guard is not the test runner: this widget renders on whatever browser a
35481
+ * customer's visitor brought, and a missing `Element.prototype.scrollTo` would throw
35482
+ * inside a passive effect and take the message list down with it. Assigning
35483
+ * `scrollTop` is the universally supported equivalent; it loses the smooth animation
35484
+ * and nothing else.
35485
+ */
35439
35486
  var scrollToBottom = React.useCallback(function (behavior) {
35440
35487
  if (behavior === void 0) { behavior = "smooth"; }
35441
- if (messagesEndRef.current) {
35442
- messagesEndRef.current.scrollIntoView({ behavior: behavior });
35488
+ var container = containerRef.current;
35489
+ if (!container) {
35490
+ return;
35491
+ }
35492
+ if (typeof container.scrollTo === "function") {
35493
+ container.scrollTo({ top: container.scrollHeight, behavior: behavior });
35494
+ return;
35443
35495
  }
35496
+ container.scrollTop = container.scrollHeight;
35444
35497
  }, []);
35445
35498
  // Always scroll to bottom when messages change (unless disabled)
35446
35499
  React.useEffect(function () {
@@ -35472,16 +35525,16 @@ var MessageList = function (_a) {
35472
35525
  }, [hasWsButton, isAtBottom, scrollToBottom, disableAutoScroll]);
35473
35526
  var handleSend = React.useCallback(function (msg) {
35474
35527
  onSend(msg);
35475
- if (!disableAutoScroll && messagesEndRef.current) {
35476
- messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
35528
+ if (!disableAutoScroll) {
35529
+ scrollToBottom("smooth");
35477
35530
  }
35478
- }, [onSend, disableAutoScroll]);
35531
+ }, [onSend, disableAutoScroll, scrollToBottom]);
35479
35532
  var handleWrite = React.useCallback(function (msg) {
35480
35533
  onWrite(msg);
35481
- if (!disableAutoScroll && messagesEndRef.current) {
35482
- messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
35534
+ if (!disableAutoScroll) {
35535
+ scrollToBottom("smooth");
35483
35536
  }
35484
- }, [onWrite, disableAutoScroll]);
35537
+ }, [onWrite, disableAutoScroll, scrollToBottom]);
35485
35538
  var ctxCache = React.useMemo(function () {
35486
35539
  return new MiddlewareContextFactory({
35487
35540
  send: handleSend,
@@ -35531,7 +35584,7 @@ var MessageList = function (_a) {
35531
35584
  case "chat.prechat":
35532
35585
  return require$$0.jsx(PrechatFormContainer, { time: time }, "prechat");
35533
35586
  default:
35534
- return (require$$0.jsxs("span", { children: [require$$0.jsx("span", { className: "message-sr-only", children: "at " + time + " system message" }), require$$0.jsxs("div", { children: ["Unhandled message: ", JSON.stringify(msg)] }, "uhm-".concat(+new Date()))] }, "default-".concat(msg.timestamp)));
35587
+ return (require$$0.jsxs("span", { children: [require$$0.jsx("span", { className: "message-sr-only", children: "at " + time + " system message" }), require$$0.jsxs("div", { "data-testid": "unhandled-message", children: ["Unhandled message: ", JSON.stringify(msg)] }, "uhm-".concat(msg.timestamp))] }, "default-".concat(msg.timestamp)));
35535
35588
  }
35536
35589
  }
35537
35590
  /**