@xapp/chat-widget 1.101.0 → 1.102.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
@@ -920,6 +920,15 @@ var ActionBarCta = function (_a) {
920
920
  * in `_mixins.scss`. Keep this in step with the `mobile()` mixin. XappMedia/chat-widget#1594.
921
921
  */
922
922
  var MOBILE_MEDIA_QUERY = "(max-width: 640px), (max-height: 480px) and (pointer: coarse)";
923
+ /**
924
+ * Whether the chat panel renders full-screen right now. False where matchMedia is missing
925
+ * (jsdom), matching ActionBar's assume-desktop fallback.
926
+ */
927
+ function isMobileViewport(win) {
928
+ if (win === void 0) { win = window; }
929
+ return typeof win.matchMedia === "function" && win.matchMedia(MOBILE_MEDIA_QUERY).matches;
930
+ }
931
+
923
932
  /**
924
933
  * ChatWidget.scss sizes the chat panel against this variable. Nothing used to set it, so it always
925
934
  * fell back to the hardcoded 80px in the SCSS - and the real mobile bar is taller than that once
@@ -2323,8 +2332,100 @@ function setVisualStatus(status) {
2323
2332
  };
2324
2333
  }
2325
2334
 
2326
- function execute(url, dispatch, visuals, behavior) {
2335
+ /**
2336
+ * Carries "this visitor just clicked a link in the chat" across a same-tab navigation.
2337
+ *
2338
+ * Persisted widget state already survives the page load, but it cannot tell a chat link
2339
+ * click from a visitor coming back to the site later. The param can; it is read on arrival
2340
+ * and stripped from the address bar so it is never bookmarked or shared.
2341
+ */
2342
+ var CHAT_CONTINUE_PARAM = "xa_chat";
2343
+ var CHAT_CONTINUE_VALUE = "resume";
2344
+ var CONTINUE_CTA_MESSAGE = "Continue your conversation";
2345
+ /**
2346
+ * Marks a link the widget is about to follow in this tab. Only same-origin links: widget
2347
+ * state lives in this origin's localStorage, so the widget on any other origin - a
2348
+ * subdomain included - has no conversation to continue.
2349
+ */
2350
+ function withChatContinueParam(url, currentOrigin) {
2351
+ try {
2352
+ var urlObj = new URL(url);
2353
+ if (urlObj.origin !== currentOrigin || urlObj.searchParams.get(CHAT_CONTINUE_PARAM) === CHAT_CONTINUE_VALUE) {
2354
+ return url;
2355
+ }
2356
+ urlObj.searchParams.set(CHAT_CONTINUE_PARAM, CHAT_CONTINUE_VALUE);
2357
+ return urlObj.toString();
2358
+ }
2359
+ catch (_) {
2360
+ return url;
2361
+ }
2362
+ }
2363
+ /**
2364
+ * True when this page was reached through a chat link. Pure, so it is safe inside a
2365
+ * render-time initializer that React may call twice (StrictMode); strip the param
2366
+ * separately with `stripChatContinueParam`, from an effect.
2367
+ */
2368
+ function hasChatContinueParam(win) {
2369
+ if (win === void 0) { win = window; }
2370
+ try {
2371
+ return new URL(win.location.href).searchParams.get(CHAT_CONTINUE_PARAM) === CHAT_CONTINUE_VALUE;
2372
+ }
2373
+ catch (_) {
2374
+ return false;
2375
+ }
2376
+ }
2377
+ /**
2378
+ * Removes the param from the address bar with replaceState, keeping the rest of the URL
2379
+ * and any SPA history state. A no-op when it is absent, so repeating it is harmless.
2380
+ */
2381
+ function stripChatContinueParam(win) {
2382
+ if (win === void 0) { win = window; }
2383
+ try {
2384
+ var url = new URL(win.location.href);
2385
+ if (!url.searchParams.has(CHAT_CONTINUE_PARAM)) {
2386
+ return;
2387
+ }
2388
+ url.searchParams.delete(CHAT_CONTINUE_PARAM);
2389
+ win.history.replaceState(win.history.state, "", url.toString());
2390
+ }
2391
+ catch (_) {
2392
+ // An address bar we cannot rewrite only leaves the param visible.
2393
+ }
2394
+ }
2395
+ /**
2396
+ * A conversation worth continuing: any message on screen. Join notices do not count - the
2397
+ * MCP connection posts one before the greeting, so a brand-new session has one too.
2398
+ */
2399
+ function hasConversation(chats) {
2400
+ return !!(chats === null || chats === void 0 ? void 0 : chats.some(function (chat) { return chat.type === "chat.msg"; }));
2401
+ }
2402
+ /**
2403
+ * The CTA to show, given whether there is a conversation to go back to.
2404
+ *
2405
+ * With one, the first-visit copy ("Need Help? Click Here") becomes a prompt to continue,
2406
+ * keeping the tenant's timing and styling. A tenant with no CTA gets none on an ordinary
2407
+ * page load; right after a chat link it does, immediately - on mobile that prompt is the
2408
+ * only sign the conversation is still there, because the panel stays closed over the page
2409
+ * they asked for.
2410
+ */
2411
+ function resolveCta(cta, options) {
2412
+ var arrivedViaChatLink = options.arrivedViaChatLink;
2413
+ if (!options.hasConversation || (!(cta === null || cta === void 0 ? void 0 : cta.message) && !arrivedViaChatLink)) {
2414
+ return cta;
2415
+ }
2416
+ var immediately = arrivedViaChatLink ? { delay: 0 } : {};
2417
+ var mobile = cta === null || cta === void 0 ? void 0 : cta.mobile;
2418
+ return __assign$1(__assign$1(__assign$1(__assign$1({}, cta), { message: CONTINUE_CTA_MESSAGE }), immediately), (mobile && {
2419
+ mobile: __assign$1(__assign$1(__assign$1({}, mobile), ((typeof mobile.message === "string" || arrivedViaChatLink) && { message: CONTINUE_CTA_MESSAGE })), immediately),
2420
+ }));
2421
+ }
2422
+
2423
+ /**
2424
+ * @internal Exported for unit testing; use `useOpenUrlCallback`.
2425
+ */
2426
+ function executeOpenUrl(url, dispatch, visuals, behavior, isMobile) {
2327
2427
  var _a, _b;
2428
+ if (isMobile === void 0) { isMobile = isMobileViewport(); }
2328
2429
  if (!dispatch || !visuals) {
2329
2430
  throw new Error("OpenUrl: No dispatch and/or visual.");
2330
2431
  }
@@ -2391,12 +2492,23 @@ function execute(url, dispatch, visuals, behavior) {
2391
2492
  window.open(url, "_blank");
2392
2493
  break;
2393
2494
  case "sameWindow":
2394
- if (!visuals.opened) {
2495
+ // Desktop: the next page reopens the chat beside the content, as before.
2496
+ // Mobile: the panel is full-screen, so reopening it would cover the page they
2497
+ // just asked for. Leave it closed; the param lets that page offer to continue.
2498
+ // Unconditional: callers can hand in a `visuals` captured before the chat opened
2499
+ // (ChatMarkdownMessage's click handler does), and skipping on a stale `opened`
2500
+ // leaves the persisted state open.
2501
+ if (isMobile) {
2502
+ dispatch(setVisualStatus({
2503
+ opened: false
2504
+ }));
2505
+ }
2506
+ else if (!visuals.opened) {
2395
2507
  dispatch(setVisualStatus({
2396
2508
  opened: true
2397
2509
  }));
2398
2510
  }
2399
- window.open(url, "_self");
2511
+ window.open(withChatContinueParam(url, window.location.origin), "_self");
2400
2512
  break;
2401
2513
  default:
2402
2514
  throwBadKind$1(type);
@@ -2408,7 +2520,7 @@ function useOpenUrlCallback() {
2408
2520
  if (!behavior) {
2409
2521
  behavior = (env === null || env === void 0 ? void 0 : env.urls) ? resolveUrlPolicy(url, env.urls) : defaultBehavior;
2410
2522
  }
2411
- execute(url, dispatch, visuals, behavior);
2523
+ executeOpenUrl(url, dispatch, visuals, behavior);
2412
2524
  }, [env]);
2413
2525
  }
2414
2526
 
@@ -4301,6 +4413,10 @@ var MCPChat = /** @class */ (function () {
4301
4413
  this.isInitializingSession = false;
4302
4414
  // Session validation state (silent handshake - no stream dispatch)
4303
4415
  this.isValidatingSession = false;
4416
+ // A conversation is on screen that the server should keep: restored from a previous page,
4417
+ // or confirmed by the server on this one. Sent as `continueConversation`, which also lets
4418
+ // the server resume a session idle longer than its default window.
4419
+ this.conversationOnScreen = false;
4304
4420
  // Tool call tracking for debug mode
4305
4421
  this.currentToolCalls = [];
4306
4422
  this.isReplaying = false;
@@ -4388,8 +4504,12 @@ var MCPChat = /** @class */ (function () {
4388
4504
  _a.label = 1;
4389
4505
  case 1:
4390
4506
  _a.trys.push([1, 3, 4, 5]);
4391
- return [4 /*yield*/, this.performSSERequest(this.buildRequestPayload({ resumeSession: true }))];
4507
+ // With a conversation on screen (e.g. after a same-tab link click), ask the
4508
+ // server to continue it: without the flag it clears the history and greets.
4509
+ return [4 /*yield*/, this.performSSERequest(this.buildRequestPayload(__assign$1({ resumeSession: true }, (this.conversationOnScreen && { continueConversation: true }))))];
4392
4510
  case 2:
4511
+ // With a conversation on screen (e.g. after a same-tab link click), ask the
4512
+ // server to continue it: without the flag it clears the history and greets.
4393
4513
  _a.sent();
4394
4514
  log("Session validation complete");
4395
4515
  return [3 /*break*/, 5];
@@ -5069,7 +5189,7 @@ var MCPChat = /** @class */ (function () {
5069
5189
  return [2 /*return*/];
5070
5190
  }
5071
5191
  log("[attemptSSERequest] Sending message: \"".concat(messageText, "\", userId: ").concat(this._userId, ", sessionId: ").concat(this._sessionId));
5072
- return [2 /*return*/, this.performSSERequest(this.buildRequestPayload({ message: messageText }))];
5192
+ return [2 /*return*/, this.performSSERequest(this.buildRequestPayload(__assign$1({ message: messageText }, (this.conversationOnScreen && { continueConversation: true }))))];
5073
5193
  });
5074
5194
  });
5075
5195
  };
@@ -5151,6 +5271,7 @@ var MCPChat = /** @class */ (function () {
5151
5271
  case "connected":
5152
5272
  // Connection established
5153
5273
  log("[SSE] Connected event received, messageId: ".concat(messageId, ", isReturningUser: ").concat(data.isReturningUser, ", isReplay: ").concat(data.isReplay));
5274
+ _this.conversationOnScreen = true;
5154
5275
  // Track if this is a replay connection
5155
5276
  if (data.isReplay) {
5156
5277
  _this.isReplaying = true;
@@ -5186,10 +5307,27 @@ var MCPChat = /** @class */ (function () {
5186
5307
  }
5187
5308
  _this.startStreamThrottle(bubbleId);
5188
5309
  break;
5310
+ case "session_resumed":
5311
+ // The server continued the conversation: no greeting follows,
5312
+ // so adopt the identity without opening a stream bubble.
5313
+ log("[SSE] Session resumed, sessionId: ".concat(data.sessionId, ", hasHistory: ").concat(data.hasHistory));
5314
+ _this.conversationOnScreen = true;
5315
+ if (data.userId) {
5316
+ _this._userId = data.userId;
5317
+ _this.userIdIsServerAssigned = true;
5318
+ }
5319
+ if (data.sessionId) {
5320
+ _this._sessionId = data.sessionId;
5321
+ _this.dispatch(setSessionId(_this._sessionId));
5322
+ }
5323
+ storeIdentity(data.userId, data.sessionId);
5324
+ _this.stopTyping();
5325
+ break;
5189
5326
  case "session_expired":
5190
5327
  // Session no longer exists on server - need to start fresh
5191
5328
  log("[SSE] Session expired: ".concat(data.reason || "Session not found"));
5192
5329
  // Clear stored session (and lastSeq) but keep userId for returning user identification
5330
+ _this.conversationOnScreen = false;
5193
5331
  clearStoredSession(_this._sessionId);
5194
5332
  _this._sessionId = "";
5195
5333
  _this.lastSeq = undefined;
@@ -5440,6 +5578,7 @@ var MCPChat = /** @class */ (function () {
5440
5578
  MCPChat.prototype.setVisitorInfo = function (visitorInfo, sessionId, cb) {
5441
5579
  this.visitorInfo = visitorInfo;
5442
5580
  this._attributes = __assign$1({}, this.visitorInfo.attributes);
5581
+ this.conversationOnScreen = visitorInfo.hasConversation === true;
5443
5582
  // do not set currentUrl if localhost
5444
5583
  var href = new URL(window.location.href);
5445
5584
  if (href === null || href === void 0 ? void 0 : href.host.toLowerCase().startsWith("localhost")) {
@@ -12954,7 +13093,21 @@ var ChatTextMessage = function (props) {
12954
13093
  var wasStreamed = message.wasStreamed;
12955
13094
  var showTail = agentMessage && !props.sibling && !isStreaming && !wasStreamed;
12956
13095
  var hasToolCalls = props.toolCalls && props.toolCalls.length > 0;
12957
- return (require$$0.jsxs("div", { className: "chat-msg".concat(isStreaming ? " chat-msg--streaming" : ""), children: [hasToolCalls && (require$$0.jsx(ToolCallDisplay, { toolCalls: props.toolCalls, debugMode: props.debugMode })), require$$0.jsx("div", { className: "chat-text-container", children: require$$0.jsxs(ChatMessageBubble, { owner: agentMessage ? "others" : "mine", hasTail: showTail, children: [require$$0.jsx("span", { className: "message-sr-only", children: "at " + time + (agentMessage ? (isStreaming ? " the bot is typing" : " the bot said") : " the user said") }), parsedHtml ? (require$$0.jsx("span", { className: "chat-msg__content", dangerouslySetInnerHTML: { __html: parsedHtml } })) : (require$$0.jsx("span", { className: "chat-msg__content", children: message.msg.text })), isStreaming && require$$0.jsx("span", { className: "streaming-cursor", "aria-hidden": "true" })] }) })] }));
13096
+ // Delegated, because the links are inside dangerouslySetInnerHTML. Modified clicks stay
13097
+ // with the browser so ctrl/cmd-click still opens a background tab.
13098
+ var onLinkClick = props.onLinkClick;
13099
+ var handleContentClick = function (ev) {
13100
+ var _a, _b;
13101
+ if (!onLinkClick || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) {
13102
+ return;
13103
+ }
13104
+ var link = (_b = (_a = ev.target).closest) === null || _b === void 0 ? void 0 : _b.call(_a, "a");
13105
+ if (link && ev.currentTarget.contains(link)) {
13106
+ ev.preventDefault();
13107
+ onLinkClick(link.href);
13108
+ }
13109
+ };
13110
+ return (require$$0.jsxs("div", { className: "chat-msg".concat(isStreaming ? " chat-msg--streaming" : ""), children: [hasToolCalls && (require$$0.jsx(ToolCallDisplay, { toolCalls: props.toolCalls, debugMode: props.debugMode })), require$$0.jsx("div", { className: "chat-text-container", children: require$$0.jsxs(ChatMessageBubble, { owner: agentMessage ? "others" : "mine", hasTail: showTail, children: [require$$0.jsx("span", { className: "message-sr-only", children: "at " + time + (agentMessage ? (isStreaming ? " the bot is typing" : " the bot said") : " the user said") }), parsedHtml ? (require$$0.jsx("span", { className: "chat-msg__content", onClick: handleContentClick, dangerouslySetInnerHTML: { __html: parsedHtml } })) : (require$$0.jsx("span", { className: "chat-msg__content", children: message.msg.text })), isStreaming && require$$0.jsx("span", { className: "streaming-cursor", "aria-hidden": "true" })] }) })] }));
12958
13111
  };
12959
13112
 
12960
13113
  var ChatScheduleWidget = function (props) {
@@ -12997,14 +13150,21 @@ var avaKeys = ["text", "html", "card", "list"];
12997
13150
  * @returns
12998
13151
  */
12999
13152
  var ChatMessage = function (props) {
13000
- var _a, _b, _c;
13153
+ var _a, _b, _c, _d;
13001
13154
  var middleware = props.messageMiddleware || StandardMiddlewares;
13002
13155
  var chatConfig = React.useContext(ChatConfigContext);
13003
13156
  // console.log(`########### chatConfig: ${JSON.stringify(chatConfig, null, 2)}`);
13004
13157
  var ctx = props.middlewareContext;
13005
13158
  var user = props.agent;
13159
+ var chatDispatch = useChatDispatch();
13160
+ var visuals = reactRedux.useSelector(function (state) { return state.visuals; });
13161
+ // Text links follow the tenant's URL policy when it set one (utm_source, same-tab
13162
+ // continuation, ...). Tenants without one keep the plain new-tab anchor.
13163
+ var handleTextLinkClick = ((_a = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _a === void 0 ? void 0 : _a.urls) && (ctx === null || ctx === void 0 ? void 0 : ctx.openUrl)
13164
+ ? function (href) { return ctx.openUrl(href, chatDispatch, visuals); }
13165
+ : undefined;
13006
13166
  // Get hideUserInfo from the agent info based on the message sender's nick
13007
- var agentInfo = (_a = props.agents) === null || _a === void 0 ? void 0 : _a[props.message.user.nick];
13167
+ var agentInfo = (_b = props.agents) === null || _b === void 0 ? void 0 : _b[props.message.user.nick];
13008
13168
  var hideUserInfo = (agentInfo === null || agentInfo === void 0 ? void 0 : agentInfo.hideUserInfo) || false;
13009
13169
  function renderByType() {
13010
13170
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
@@ -13023,7 +13183,7 @@ var ChatMessage = function (props) {
13023
13183
  var debugMode = (_d = (_c = (_b = (_a = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _a === void 0 ? void 0 : _a.connection) === null || _b === void 0 ? void 0 : _b.mcp) === null || _c === void 0 ? void 0 : _c.debugMode) !== null && _d !== void 0 ? _d : false;
13024
13184
  // Show tool progress with avatar when no text yet, or above text when text exists
13025
13185
  var showToolProgressWithAvatar = hasToolCalls && !msg.text;
13026
- 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 &&
13186
+ 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, onLinkClick: handleTextLinkClick }) })), msg.html &&
13027
13187
  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) {
13028
13188
  if (display.type === "ScheduleButton") {
13029
13189
  return (require$$0.jsx(ChatScheduleWidget, { minimizeOnClick: props.minimizeOnClick, display: display }, index));
@@ -13047,7 +13207,7 @@ var ChatMessage = function (props) {
13047
13207
  }
13048
13208
  // Don't show timestamp/sources while streaming or if message has no content yet
13049
13209
  var isStreaming = props.message.isStreaming;
13050
- var hasContent = !!(((_b = props.message.msg) === null || _b === void 0 ? void 0 : _b.text) || ((_c = props.message.msg) === null || _c === void 0 ? void 0 : _c.html));
13210
+ var hasContent = !!(((_c = props.message.msg) === null || _c === void 0 ? void 0 : _c.text) || ((_d = props.message.msg) === null || _d === void 0 ? void 0 : _d.html));
13051
13211
  var showFooter = !isStreaming && hasContent;
13052
13212
  // empty
13053
13213
  return (require$$0.jsx("div", { className: "chat-msg-container-wrapper ".concat(isAgent(props.message.user.nick) ? "agent" : "visitor", " ").concat(props.sibling ? "sibling" : ""), children: require$$0.jsxs("div", { className: "chat-msg-container ".concat(getClassName(props.message)), children: [renderByType(), showFooter && renderTimestampAndSources()] }) }));
@@ -13057,9 +13217,6 @@ var ChatMessage = function (props) {
13057
13217
  * value must be like "60m", "2h" etc.
13058
13218
  */
13059
13219
  function convertToSeconds(s) {
13060
- if (!s) {
13061
- return 0;
13062
- }
13063
13220
  var groups = s.match(/(\d+)([dhms]?)/);
13064
13221
  if (!groups) {
13065
13222
  err("Invalid time string: ".concat(s));
@@ -13082,15 +13239,50 @@ function convertToSeconds(s) {
13082
13239
  }
13083
13240
  }
13084
13241
 
13085
- function checkSessionExpiration(duration, lastMessageTimestamp, _lastTimestamp) {
13086
- var durationMs = convertToSeconds(duration) * 1000;
13087
- // never if no session duration in config
13088
- if (!duration) {
13242
+ /**
13243
+ * How long a conversation must sit idle, on top of the calendar day changing, before it
13244
+ * expires when the widget config does not say.
13245
+ */
13246
+ var DEFAULT_SESSION_EXPIRATION = "4h";
13247
+ function isSameLocalDay(a, b) {
13248
+ var dayA = new Date(a);
13249
+ var dayB = new Date(b);
13250
+ return dayA.getFullYear() === dayB.getFullYear()
13251
+ && dayA.getMonth() === dayB.getMonth()
13252
+ && dayA.getDate() === dayB.getDate();
13253
+ }
13254
+ /**
13255
+ * A conversation expires only when BOTH the visitor's local calendar day has changed since
13256
+ * the last message AND at least `duration` has passed. So a visitor who browses the site
13257
+ * over an afternoon keeps one conversation, and one chatting at 11pm is not greeted afresh
13258
+ * at 1am - but tomorrow's visit starts clean.
13259
+ */
13260
+ function checkSessionExpiration(duration, lastMessageTimestamp, _lastTimestamp, now) {
13261
+ if (now === void 0) { now = Date.now(); }
13262
+ if (!lastMessageTimestamp) {
13089
13263
  return false;
13090
13264
  }
13091
- // TODO: Why? Let's use the time passed since the last message timestamp
13092
- // return lastTimestamp - lastMessageTimestamp > durationMs;
13093
- return +new Date() - lastMessageTimestamp > durationMs;
13265
+ var durationMs = convertToSeconds(duration || DEFAULT_SESSION_EXPIRATION) * 1000;
13266
+ return now - lastMessageTimestamp >= durationMs && !isSameLocalDay(lastMessageTimestamp, now);
13267
+ }
13268
+
13269
+ /**
13270
+ * Whether this page was reached through a same-tab link in the chat, read once on mount.
13271
+ *
13272
+ * The read is pure and the strip runs in an effect. StrictMode calls state initializers
13273
+ * twice in development, and React 18 (still allowed by our peer range) keeps the second
13274
+ * result - which an initializer that also stripped the param would get wrong. React 19
13275
+ * reuses the first result, so the test suite here cannot show that failure.
13276
+ */
13277
+ function useArrivedViaChatLink(disabled) {
13278
+ if (disabled === void 0) { disabled = false; }
13279
+ var arrived = React.useState(function () { return !disabled && hasChatContinueParam(); })[0];
13280
+ React.useEffect(function () {
13281
+ if (arrived) {
13282
+ stripChatContinueParam();
13283
+ }
13284
+ }, [arrived]);
13285
+ return arrived;
13094
13286
  }
13095
13287
 
13096
13288
  function useExternalScript(url) {
@@ -13211,6 +13403,7 @@ function useGreeting(active) {
13211
13403
  // Handle null context (e.g., in preview mode)
13212
13404
  var isAdmin = (_b = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.env) === null || _a === void 0 ? void 0 : _a.isAdmin) !== null && _b !== void 0 ? _b : false;
13213
13405
  var sessionId = reactRedux.useSelector(function (state) { return state.sessionId; });
13406
+ var conversationOnScreen = reactRedux.useSelector(function (state) { return hasConversation(state.chats); });
13214
13407
  React.useEffect(function () {
13215
13408
  if (active) {
13216
13409
  if (snapshotRef.current !== curr) {
@@ -13221,7 +13414,8 @@ function useGreeting(active) {
13221
13414
  visitorId: curr.visitorId,
13222
13415
  userId: curr.userId,
13223
13416
  accessToken: curr.accessToken,
13224
- attributes: __assign$1(__assign$1({}, curr.attributes), { currentUrl: window.location.href })
13417
+ attributes: __assign$1(__assign$1({}, curr.attributes), { currentUrl: window.location.href }),
13418
+ hasConversation: conversationOnScreen
13225
13419
  }, sessionId));
13226
13420
  if (!isAdmin) {
13227
13421
  var timeoutId_1 = setTimeout(function () {
@@ -37077,7 +37271,7 @@ var ChatWidgetConnected = function (props) {
37077
37271
  * Exported for use by StaticChatWidgetContainer to avoid infinite loops.
37078
37272
  */
37079
37273
  var ChatWidgetUI = function (props) {
37080
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13;
37274
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14;
37081
37275
  var innerDispatch = useChatDispatch();
37082
37276
  var dispatch = useChatServerDispatch();
37083
37277
  // Publish the visual viewport's geometry so the panel can size around the soft keyboard.
@@ -37098,14 +37292,18 @@ var ChatWidgetUI = function (props) {
37098
37292
  // Preview mode can be enabled via `preview` prop (recommended) or legacy `mode="preview"`
37099
37293
  var previewMode = props.preview === true || mode === "preview";
37100
37294
  var modeClass = "widget-container--".concat(mode);
37101
- var canRefresh = (_c = (_b = props.config.header) === null || _b === void 0 ? void 0 : _b.actions) === null || _c === void 0 ? void 0 : _c.refresh;
37295
+ // Reached this page through a link in the chat.
37296
+ var arrivedViaChatLink = useArrivedViaChatLink(previewMode);
37297
+ var conversationOnScreen = hasConversation(chatState.chats);
37298
+ var chatButtonCta = React.useMemo(function () { var _a; return resolveCta((_a = props.config) === null || _a === void 0 ? void 0 : _a.cta, { hasConversation: conversationOnScreen, arrivedViaChatLink: arrivedViaChatLink }); }, [(_b = props.config) === null || _b === void 0 ? void 0 : _b.cta, conversationOnScreen, arrivedViaChatLink]);
37299
+ var canRefresh = (_d = (_c = props.config.header) === null || _c === void 0 ? void 0 : _c.actions) === null || _d === void 0 ? void 0 : _d.refresh;
37102
37300
  // can't minimize in docked mode, static mode, or preview mode.
37103
- var canMinimize = !dockedMode && !staticMode && !previewMode && ((_e = (_d = props.config.header) === null || _d === void 0 ? void 0 : _d.actions) === null || _e === void 0 ? void 0 : _e.minimize);
37104
- log("docked: ".concat(dockedMode, " static: ").concat(staticMode, " preview: ").concat(previewMode, " minimized: ").concat((_g = (_f = props.config.header) === null || _f === void 0 ? void 0 : _f.actions) === null || _g === void 0 ? void 0 : _g.minimize));
37301
+ var canMinimize = !dockedMode && !staticMode && !previewMode && ((_f = (_e = props.config.header) === null || _e === void 0 ? void 0 : _e.actions) === null || _f === void 0 ? void 0 : _f.minimize);
37302
+ log("docked: ".concat(dockedMode, " static: ").concat(staticMode, " preview: ").concat(previewMode, " minimized: ").concat((_h = (_g = props.config.header) === null || _g === void 0 ? void 0 : _g.actions) === null || _h === void 0 ? void 0 : _h.minimize));
37105
37303
  var canCancel;
37106
37304
  // To preserve legacy behavior, cancel needs a little more checks
37107
- if (typeof ((_j = (_h = props.config.header) === null || _h === void 0 ? void 0 : _h.actions) === null || _j === void 0 ? void 0 : _j.cancel) === "boolean") {
37108
- canCancel = !dockedMode && !staticMode && !previewMode && ((_l = (_k = props.config.header) === null || _k === void 0 ? void 0 : _k.actions) === null || _l === void 0 ? void 0 : _l.cancel);
37305
+ if (typeof ((_k = (_j = props.config.header) === null || _j === void 0 ? void 0 : _j.actions) === null || _k === void 0 ? void 0 : _k.cancel) === "boolean") {
37306
+ canCancel = !dockedMode && !staticMode && !previewMode && ((_m = (_l = props.config.header) === null || _l === void 0 ? void 0 : _l.actions) === null || _m === void 0 ? void 0 : _m.cancel);
37109
37307
  }
37110
37308
  else {
37111
37309
  canCancel = !dockedMode && !staticMode && !previewMode;
@@ -37116,16 +37314,16 @@ var ChatWidgetUI = function (props) {
37116
37314
  }
37117
37315
  // Our state - pull from storage
37118
37316
  // Parse autoOpenOnWidth once for use in both initial state and useEffect
37119
- var autoOpenOnWidthValue = ((_m = props.config) === null || _m === void 0 ? void 0 : _m.autoOpenOnWidth)
37317
+ var autoOpenOnWidthValue = ((_o = props.config) === null || _o === void 0 ? void 0 : _o.autoOpenOnWidth)
37120
37318
  ? parseInt(props.config.autoOpenOnWidth, 10)
37121
37319
  : NaN;
37122
37320
  var autoOpenOnWidthMatch = !isNaN(autoOpenOnWidthValue) && window.innerWidth >= autoOpenOnWidthValue;
37123
37321
  // When autoOpenOnWidth is configured, use width check instead of persisted visible state
37124
37322
  // to prevent greeting requests on viewports below the threshold
37125
- var _14 = React.useState((!canMinimize && !canCancel) ||
37323
+ var _15 = React.useState((!canMinimize && !canCancel) ||
37126
37324
  // !!get("visible") ||
37127
- (!isNaN(autoOpenOnWidthValue) ? autoOpenOnWidthMatch : chatState.visuals.visible)), visible = _14[0], setVisibleState = _14[1];
37128
- var _15 = React.useState(false); _15[0]; var setTypingState = _15[1]; // false initially - state kept for potential external observers
37325
+ (!isNaN(autoOpenOnWidthValue) ? autoOpenOnWidthMatch : chatState.visuals.visible)), visible = _15[0], setVisibleState = _15[1];
37326
+ var _16 = React.useState(false); _16[0]; var setTypingState = _16[1]; // false initially - state kept for potential external observers
37129
37327
  // Ref to track typing state for use in timeout callbacks
37130
37328
  var typingRef = React.useRef(false);
37131
37329
  // Timeout ref for debouncing "stop typing" events
@@ -37133,11 +37331,11 @@ var ChatWidgetUI = function (props) {
37133
37331
  // Track whether autoOpenOnWidth has already fired so it doesn't re-open after user closes
37134
37332
  var autoOpenFiredRef = React.useRef(autoOpenOnWidthMatch);
37135
37333
  var chatServer = React.useContext(ChatServerContext);
37136
- var patternsConfig = (_p = (_o = props.config) === null || _o === void 0 ? void 0 : _o.autoOpenOnPattern) === null || _p === void 0 ? void 0 : _p.patterns;
37334
+ var patternsConfig = (_q = (_p = props.config) === null || _p === void 0 ? void 0 : _p.autoOpenOnPattern) === null || _q === void 0 ? void 0 : _q.patterns;
37137
37335
  var currentUrl = window.location.href;
37138
37336
  var patternExist = patternsConfig && patternsConfig.length > 0;
37139
37337
  var patternMatches = patternsConfig === null || patternsConfig === void 0 ? void 0 : patternsConfig.some(function (pattern) { return currentUrl.includes(pattern); });
37140
- var configWidth = (_r = (_q = props.config) === null || _q === void 0 ? void 0 : _q.autoOpenOnPattern) === null || _r === void 0 ? void 0 : _r.minimumWidth;
37338
+ var configWidth = (_s = (_r = props.config) === null || _r === void 0 ? void 0 : _r.autoOpenOnPattern) === null || _s === void 0 ? void 0 : _s.minimumWidth;
37141
37339
  var currentWidth = window.innerWidth;
37142
37340
  var setVisible = React.useCallback(function (newVisible) {
37143
37341
  if (staticMode || previewMode) {
@@ -37172,7 +37370,7 @@ var ChatWidgetUI = function (props) {
37172
37370
  };
37173
37371
  // eslint-disable-next-line react-hooks/exhaustive-deps
37174
37372
  }, []);
37175
- var _16 = React.useState(!document.hidden), isTabVisible = _16[0], setIsTabVisible = _16[1];
37373
+ var _17 = React.useState(!document.hidden), isTabVisible = _17[0], setIsTabVisible = _17[1];
37176
37374
  React.useEffect(function () {
37177
37375
  var handleVisibilityChange = function () {
37178
37376
  setIsTabVisible(!document.hidden);
@@ -37396,8 +37594,8 @@ var ChatWidgetUI = function (props) {
37396
37594
  }));
37397
37595
  }
37398
37596
  // Action Bar state and handlers
37399
- var _17 = React.useState(false), formModalOpen = _17[0], setFormModalOpen = _17[1];
37400
- var _18 = React.useState(null), activeFormConfig = _18[0], setActiveFormConfig = _18[1];
37597
+ var _18 = React.useState(false), formModalOpen = _18[0], setFormModalOpen = _18[1];
37598
+ var _19 = React.useState(null), activeFormConfig = _19[0], setActiveFormConfig = _19[1];
37401
37599
  var handleFormButtonClick = React.useCallback(function (formButton) {
37402
37600
  // If form-widget is configured, use its openForm API
37403
37601
  // The form-widget script should already be pre-loaded
@@ -37464,7 +37662,7 @@ var ChatWidgetUI = function (props) {
37464
37662
  var messages = chatState && chatState.chats;
37465
37663
  log("Rendering - accountStatus: \"".concat(chatState.accountStatus, "\", connectionStatus: \"").concat(chatState.connection.connectionStatus, "\""));
37466
37664
  var config = props.config, onConnectionStatusChange = props.onConnectionStatusChange;
37467
- var actionBarEnabled = ((_s = config === null || config === void 0 ? void 0 : config.actionBar) === null || _s === void 0 ? void 0 : _s.enabled) === true;
37665
+ var actionBarEnabled = ((_t = config === null || config === void 0 ? void 0 : config.actionBar) === null || _t === void 0 ? void 0 : _t.enabled) === true;
37468
37666
  // The host page declares this; the widget cannot work it out. On the standalone
37469
37667
  // contact/booking page the form already *is* the page, which makes every button that
37470
37668
  // only opens one redundant. XappMedia/chat-widget#1626.
@@ -37488,7 +37686,7 @@ var ChatWidgetUI = function (props) {
37488
37686
  onConnectionStatusChange(connectionStatus);
37489
37687
  }
37490
37688
  }, [connectionStatus, onConnectionStatusChange]);
37491
- useExternalScript((_t = props.config) === null || _t === void 0 ? void 0 : _t.middlewareUrl);
37689
+ useExternalScript((_u = props.config) === null || _u === void 0 ? void 0 : _u.middlewareUrl);
37492
37690
  // Only reach for form.xapp.ai when something on this page can still open the form.
37493
37691
  //
37494
37692
  // Loading it is not free: the script mounts a second form-widget instance whose wrapper
@@ -37528,15 +37726,18 @@ var ChatWidgetUI = function (props) {
37528
37726
  };
37529
37727
  }, [actionBarVisible]);
37530
37728
  // This is a pseudo agent. It represent's the widget (shown in the header avatar for instance)
37531
- var widgetAgent = ((_u = chatState.agents["agent:robot"]) === null || _u === void 0 ? void 0 : _u.user) ||
37729
+ var widgetAgent = ((_v = chatState.agents["agent:robot"]) === null || _v === void 0 ? void 0 : _v.user) ||
37532
37730
  (config === null || config === void 0 ? void 0 : config.agent) || {
37533
37731
  nick: "agent:robot",
37534
37732
  avatarPath: config.avatarUrl,
37535
37733
  display_name: "Agent",
37536
37734
  };
37537
- return (require$$0.jsxs(require$$0.Fragment, { children: [require$$0.jsxs("div", { className: "widget-container ".concat(modeClass, " ").concat(getVisibilityClass()).concat(actionBarVisible ? " widget-container--with-action-bar" : ""), children: [require$$0.jsx(WidgetStylesheet, { theme: config === null || config === void 0 ? void 0 : config.theme }), require$$0.jsx(ChatHeader, { accountStatus: chatState.accountStatus, refreshOnClick: handleRestartClick, minimizeOnClick: handleMinimizeClick, cancelOnClick: handleCancelClick, agent: widgetAgent, canRefresh: canRefresh, canMinimize: canMinimize, canCancel: canCancel, config: config === null || config === void 0 ? void 0 : config.header, menuConfig: config.menu, debugMode: (_x = (_w = (_v = props.config) === null || _v === void 0 ? void 0 : _v.connection) === null || _w === void 0 ? void 0 : _w.mcp) === null || _x === void 0 ? void 0 : _x.debugMode, onSubmit: handleOnSubmit }), require$$0.jsx(MessageList, { visible: visible, queuePosition: chatState.queuePosition, isChatting: chatState.isChatting, isOffline: isOffline, messages: messages, agents: chatState.agents, agent: config === null || config === void 0 ? void 0 : config.agent, lastRatingRequestTimestamp: chatState.lastRatingRequestTimestamp, hasRating: chatState.hasRating, visitorId: chatState.visitorId, hasWsButton: !!chatState.wsButton, messageMiddleware: props.messageMiddleware, textTypingStatusEnabled: (_z = (_y = props.config) === null || _y === void 0 ? void 0 : _y.typingStatus) === null || _z === void 0 ? void 0 : _z.textTypingStatusEnabled, disableAutoScroll: props.disableAutoScroll, onSend: handleSendMessage, onWrite: handleWriteMessage, onOpenUrl: handleOpenUrl, minimizeOnClick: handleMinimizeClick }), require$$0.jsx("div", { className: "xa-spinner-container ".concat(visible && connectionStatus === "pending" ? "visible" : ""), children: require$$0.jsx("div", { className: "xa-spinner" }) }), connectionStatus === "offline" && require$$0.jsx(ServerOffline, {}), chatState.wsButton && visible && (require$$0.jsx(WsButton, { button: chatState.wsButton, onPress: handleWsButtonPress })), require$$0.jsx(ChatFooter, { isAdmin: config === null || config === void 0 ? void 0 : config.isAdmin, isChatting: chatState.isChatting, placeholder: (_0 = config === null || config === void 0 ? void 0 : config.input) === null || _0 === void 0 ? void 0 : _0.placeholder, sendButtonIcon: (_2 = (_1 = config === null || config === void 0 ? void 0 : config.footer) === null || _1 === void 0 ? void 0 : _1.sendButton) === null || _2 === void 0 ? void 0 : _2.icon, sendButtonIconHover: (_4 = (_3 = config === null || config === void 0 ? void 0 : config.footer) === null || _3 === void 0 ? void 0 : _3.sendButton) === null || _4 === void 0 ? void 0 : _4.iconHover, sendButtonIconDisabled: (_6 = (_5 = config === null || config === void 0 ? void 0 : config.footer) === null || _5 === void 0 ? void 0 : _5.sendButton) === null || _6 === void 0 ? void 0 : _6.iconDisabled, visible: visible, hasWsButton: !!chatState.wsButton, menuConfig: (_7 = props.config) === null || _7 === void 0 ? void 0 : _7.menu, footerConfig: (_8 = props.config) === null || _8 === void 0 ? void 0 : _8.footer, inputConfig: (_9 = props.config) === null || _9 === void 0 ? void 0 : _9.input, disabled: previewMode, onChange: handleOnChange, onSubmit: handleOnSubmit, onFileUpload: handleFileUpload }), require$$0.jsx("div", { className: "restartModal", ref: modalRef, onClick: handleRestartModalCloseClick, children: require$$0.jsx(ModalContent, { onClose: handleRestartModalCloseClick, onReset: handleReset }) })] }), actionBarVisible && config.actionBar ? (require$$0.jsxs(require$$0.Fragment, { children: [require$$0.jsx(ActionBar, { config: __assign$1(__assign$1({}, config.actionBar), {
37735
+ return (require$$0.jsxs(require$$0.Fragment, { children: [require$$0.jsxs("div", { className: "widget-container ".concat(modeClass, " ").concat(getVisibilityClass()).concat(actionBarVisible ? " widget-container--with-action-bar" : ""), children: [require$$0.jsx(WidgetStylesheet, { theme: config === null || config === void 0 ? void 0 : config.theme }), require$$0.jsx(ChatHeader, { accountStatus: chatState.accountStatus, refreshOnClick: handleRestartClick, minimizeOnClick: handleMinimizeClick, cancelOnClick: handleCancelClick, agent: widgetAgent, canRefresh: canRefresh, canMinimize: canMinimize, canCancel: canCancel, config: config === null || config === void 0 ? void 0 : config.header, menuConfig: config.menu, debugMode: (_y = (_x = (_w = props.config) === null || _w === void 0 ? void 0 : _w.connection) === null || _x === void 0 ? void 0 : _x.mcp) === null || _y === void 0 ? void 0 : _y.debugMode, onSubmit: handleOnSubmit }), require$$0.jsx(MessageList, { visible: visible, queuePosition: chatState.queuePosition, isChatting: chatState.isChatting, isOffline: isOffline, messages: messages, agents: chatState.agents, agent: config === null || config === void 0 ? void 0 : config.agent, lastRatingRequestTimestamp: chatState.lastRatingRequestTimestamp, hasRating: chatState.hasRating, visitorId: chatState.visitorId, hasWsButton: !!chatState.wsButton, messageMiddleware: props.messageMiddleware, textTypingStatusEnabled: (_0 = (_z = props.config) === null || _z === void 0 ? void 0 : _z.typingStatus) === null || _0 === void 0 ? void 0 : _0.textTypingStatusEnabled, disableAutoScroll: props.disableAutoScroll, onSend: handleSendMessage, onWrite: handleWriteMessage, onOpenUrl: handleOpenUrl, minimizeOnClick: handleMinimizeClick }), require$$0.jsx("div", { className: "xa-spinner-container ".concat(visible && connectionStatus === "pending" ? "visible" : ""), children: require$$0.jsx("div", { className: "xa-spinner" }) }), connectionStatus === "offline" && require$$0.jsx(ServerOffline, {}), chatState.wsButton && visible && (require$$0.jsx(WsButton, { button: chatState.wsButton, onPress: handleWsButtonPress })), require$$0.jsx(ChatFooter, { isAdmin: config === null || config === void 0 ? void 0 : config.isAdmin, isChatting: chatState.isChatting, placeholder: (_1 = config === null || config === void 0 ? void 0 : config.input) === null || _1 === void 0 ? void 0 : _1.placeholder, sendButtonIcon: (_3 = (_2 = config === null || config === void 0 ? void 0 : config.footer) === null || _2 === void 0 ? void 0 : _2.sendButton) === null || _3 === void 0 ? void 0 : _3.icon, sendButtonIconHover: (_5 = (_4 = config === null || config === void 0 ? void 0 : config.footer) === null || _4 === void 0 ? void 0 : _4.sendButton) === null || _5 === void 0 ? void 0 : _5.iconHover, sendButtonIconDisabled: (_7 = (_6 = config === null || config === void 0 ? void 0 : config.footer) === null || _6 === void 0 ? void 0 : _6.sendButton) === null || _7 === void 0 ? void 0 : _7.iconDisabled, visible: visible, hasWsButton: !!chatState.wsButton, menuConfig: (_8 = props.config) === null || _8 === void 0 ? void 0 : _8.menu, footerConfig: (_9 = props.config) === null || _9 === void 0 ? void 0 : _9.footer, inputConfig: (_10 = props.config) === null || _10 === void 0 ? void 0 : _10.input, disabled: previewMode, onChange: handleOnChange, onSubmit: handleOnSubmit, onFileUpload: handleFileUpload }), require$$0.jsx("div", { className: "restartModal", ref: modalRef, onClick: handleRestartModalCloseClick, children: require$$0.jsx(ModalContent, { onClose: handleRestartModalCloseClick, onReset: handleReset }) })] }), actionBarVisible && config.actionBar ? (require$$0.jsxs(require$$0.Fragment, { children: [require$$0.jsx(ActionBar, { config: __assign$1(__assign$1({}, config.actionBar), {
37538
37736
  // Use actionBar.cta if set, otherwise fall back to widget-level cta
37539
- cta: (_10 = config.actionBar.cta) !== null && _10 !== void 0 ? _10 : config.cta }), visible: visible, chatDisabled: config.disabled, formDisabled: isFormPage, hasUserInteracted: (_11 = chatState.visuals) === null || _11 === void 0 ? void 0 : _11.hasInteracted, onChatClick: chatButtonOnClick, onChatMinimize: handleMinimizeClick, onFormClick: handleFormButtonClick, onCtaDismiss: handleCtaDismiss }), formModalOpen && activeFormConfig && (require$$0.jsx(FormModal, { config: activeFormConfig, widgetEnv: config, onClose: handleFormModalClose }))] })) : (require$$0.jsx(ChatButton, { addClass: visible ? "visible" : "", onClick: chatButtonOnClick, config: config === null || config === void 0 ? void 0 : config.cta, imageUrl: (_12 = config === null || config === void 0 ? void 0 : config.chatButton) === null || _12 === void 0 ? void 0 : _12.imageUrl, visible: visible, hasInteracted: (_13 = chatState.visuals) === null || _13 === void 0 ? void 0 : _13.hasInteracted, onCtaDismiss: handleCtaDismiss })), require$$0.jsx(ErrorOverlay, { enableErrorOverlay: config === null || config === void 0 ? void 0 : config.enableErrorOverlay })] }));
37737
+ cta: resolveCta((_11 = config.actionBar.cta) !== null && _11 !== void 0 ? _11 : config.cta, {
37738
+ hasConversation: conversationOnScreen,
37739
+ arrivedViaChatLink: arrivedViaChatLink,
37740
+ }) }), visible: visible, chatDisabled: config.disabled, formDisabled: isFormPage, hasUserInteracted: (_12 = chatState.visuals) === null || _12 === void 0 ? void 0 : _12.hasInteracted, onChatClick: chatButtonOnClick, onChatMinimize: handleMinimizeClick, onFormClick: handleFormButtonClick, onCtaDismiss: handleCtaDismiss }), formModalOpen && activeFormConfig && (require$$0.jsx(FormModal, { config: activeFormConfig, widgetEnv: config, onClose: handleFormModalClose }))] })) : (require$$0.jsx(ChatButton, { addClass: visible ? "visible" : "", onClick: chatButtonOnClick, config: chatButtonCta, imageUrl: (_13 = config === null || config === void 0 ? void 0 : config.chatButton) === null || _13 === void 0 ? void 0 : _13.imageUrl, visible: visible, hasInteracted: (_14 = chatState.visuals) === null || _14 === void 0 ? void 0 : _14.hasInteracted, onCtaDismiss: handleCtaDismiss })), require$$0.jsx(ErrorOverlay, { enableErrorOverlay: config === null || config === void 0 ? void 0 : config.enableErrorOverlay })] }));
37540
37741
  };
37541
37742
  /**
37542
37743
  * Top-level wrapper that dispatches between preview mode and connected mode.