@xapp/chat-widget 1.100.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/components/ChatTextMessage/ChatTextMessage.d.ts +0 -0
- package/dist/hooks/useArrivedViaChatLink.d.ts +0 -0
- package/dist/hooks/useOpenUrlCallback.d.ts +0 -0
- package/dist/index.css +1 -1
- package/dist/index.es.js +324 -72
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +324 -72
- package/dist/index.js.map +1 -1
- package/dist/store/ChatAction.d.ts +0 -0
- package/dist/utils/chatContinuation.d.ts +0 -0
- package/dist/utils/checkSessionExpiration.d.ts +0 -0
- package/dist/utils/isMobileViewport.d.ts +0 -0
- package/dist/xapp/ChatServer.d.ts +0 -0
- package/dist/xapp/MCPChat.d.ts +0 -0
- package/dist/xapp-chat-widget.css +1 -1
- package/dist/xapp-chat-widget.js +5 -5
- package/dist/xapp-chat-widget.js.map +1 -1
- package/package.json +1 -1
package/dist/index.es.js
CHANGED
|
@@ -919,6 +919,15 @@ var ActionBarCta = function (_a) {
|
|
|
919
919
|
* in `_mixins.scss`. Keep this in step with the `mobile()` mixin. XappMedia/chat-widget#1594.
|
|
920
920
|
*/
|
|
921
921
|
var MOBILE_MEDIA_QUERY = "(max-width: 640px), (max-height: 480px) and (pointer: coarse)";
|
|
922
|
+
/**
|
|
923
|
+
* Whether the chat panel renders full-screen right now. False where matchMedia is missing
|
|
924
|
+
* (jsdom), matching ActionBar's assume-desktop fallback.
|
|
925
|
+
*/
|
|
926
|
+
function isMobileViewport(win) {
|
|
927
|
+
if (win === void 0) { win = window; }
|
|
928
|
+
return typeof win.matchMedia === "function" && win.matchMedia(MOBILE_MEDIA_QUERY).matches;
|
|
929
|
+
}
|
|
930
|
+
|
|
922
931
|
/**
|
|
923
932
|
* ChatWidget.scss sizes the chat panel against this variable. Nothing used to set it, so it always
|
|
924
933
|
* fell back to the hardcoded 80px in the SCSS - and the real mobile bar is taller than that once
|
|
@@ -2322,8 +2331,100 @@ function setVisualStatus(status) {
|
|
|
2322
2331
|
};
|
|
2323
2332
|
}
|
|
2324
2333
|
|
|
2325
|
-
|
|
2334
|
+
/**
|
|
2335
|
+
* Carries "this visitor just clicked a link in the chat" across a same-tab navigation.
|
|
2336
|
+
*
|
|
2337
|
+
* Persisted widget state already survives the page load, but it cannot tell a chat link
|
|
2338
|
+
* click from a visitor coming back to the site later. The param can; it is read on arrival
|
|
2339
|
+
* and stripped from the address bar so it is never bookmarked or shared.
|
|
2340
|
+
*/
|
|
2341
|
+
var CHAT_CONTINUE_PARAM = "xa_chat";
|
|
2342
|
+
var CHAT_CONTINUE_VALUE = "resume";
|
|
2343
|
+
var CONTINUE_CTA_MESSAGE = "Continue your conversation";
|
|
2344
|
+
/**
|
|
2345
|
+
* Marks a link the widget is about to follow in this tab. Only same-origin links: widget
|
|
2346
|
+
* state lives in this origin's localStorage, so the widget on any other origin - a
|
|
2347
|
+
* subdomain included - has no conversation to continue.
|
|
2348
|
+
*/
|
|
2349
|
+
function withChatContinueParam(url, currentOrigin) {
|
|
2350
|
+
try {
|
|
2351
|
+
var urlObj = new URL(url);
|
|
2352
|
+
if (urlObj.origin !== currentOrigin || urlObj.searchParams.get(CHAT_CONTINUE_PARAM) === CHAT_CONTINUE_VALUE) {
|
|
2353
|
+
return url;
|
|
2354
|
+
}
|
|
2355
|
+
urlObj.searchParams.set(CHAT_CONTINUE_PARAM, CHAT_CONTINUE_VALUE);
|
|
2356
|
+
return urlObj.toString();
|
|
2357
|
+
}
|
|
2358
|
+
catch (_) {
|
|
2359
|
+
return url;
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
/**
|
|
2363
|
+
* True when this page was reached through a chat link. Pure, so it is safe inside a
|
|
2364
|
+
* render-time initializer that React may call twice (StrictMode); strip the param
|
|
2365
|
+
* separately with `stripChatContinueParam`, from an effect.
|
|
2366
|
+
*/
|
|
2367
|
+
function hasChatContinueParam(win) {
|
|
2368
|
+
if (win === void 0) { win = window; }
|
|
2369
|
+
try {
|
|
2370
|
+
return new URL(win.location.href).searchParams.get(CHAT_CONTINUE_PARAM) === CHAT_CONTINUE_VALUE;
|
|
2371
|
+
}
|
|
2372
|
+
catch (_) {
|
|
2373
|
+
return false;
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
/**
|
|
2377
|
+
* Removes the param from the address bar with replaceState, keeping the rest of the URL
|
|
2378
|
+
* and any SPA history state. A no-op when it is absent, so repeating it is harmless.
|
|
2379
|
+
*/
|
|
2380
|
+
function stripChatContinueParam(win) {
|
|
2381
|
+
if (win === void 0) { win = window; }
|
|
2382
|
+
try {
|
|
2383
|
+
var url = new URL(win.location.href);
|
|
2384
|
+
if (!url.searchParams.has(CHAT_CONTINUE_PARAM)) {
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
url.searchParams.delete(CHAT_CONTINUE_PARAM);
|
|
2388
|
+
win.history.replaceState(win.history.state, "", url.toString());
|
|
2389
|
+
}
|
|
2390
|
+
catch (_) {
|
|
2391
|
+
// An address bar we cannot rewrite only leaves the param visible.
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
/**
|
|
2395
|
+
* A conversation worth continuing: any message on screen. Join notices do not count - the
|
|
2396
|
+
* MCP connection posts one before the greeting, so a brand-new session has one too.
|
|
2397
|
+
*/
|
|
2398
|
+
function hasConversation(chats) {
|
|
2399
|
+
return !!(chats === null || chats === void 0 ? void 0 : chats.some(function (chat) { return chat.type === "chat.msg"; }));
|
|
2400
|
+
}
|
|
2401
|
+
/**
|
|
2402
|
+
* The CTA to show, given whether there is a conversation to go back to.
|
|
2403
|
+
*
|
|
2404
|
+
* With one, the first-visit copy ("Need Help? Click Here") becomes a prompt to continue,
|
|
2405
|
+
* keeping the tenant's timing and styling. A tenant with no CTA gets none on an ordinary
|
|
2406
|
+
* page load; right after a chat link it does, immediately - on mobile that prompt is the
|
|
2407
|
+
* only sign the conversation is still there, because the panel stays closed over the page
|
|
2408
|
+
* they asked for.
|
|
2409
|
+
*/
|
|
2410
|
+
function resolveCta(cta, options) {
|
|
2411
|
+
var arrivedViaChatLink = options.arrivedViaChatLink;
|
|
2412
|
+
if (!options.hasConversation || (!(cta === null || cta === void 0 ? void 0 : cta.message) && !arrivedViaChatLink)) {
|
|
2413
|
+
return cta;
|
|
2414
|
+
}
|
|
2415
|
+
var immediately = arrivedViaChatLink ? { delay: 0 } : {};
|
|
2416
|
+
var mobile = cta === null || cta === void 0 ? void 0 : cta.mobile;
|
|
2417
|
+
return __assign$1(__assign$1(__assign$1(__assign$1({}, cta), { message: CONTINUE_CTA_MESSAGE }), immediately), (mobile && {
|
|
2418
|
+
mobile: __assign$1(__assign$1(__assign$1({}, mobile), ((typeof mobile.message === "string" || arrivedViaChatLink) && { message: CONTINUE_CTA_MESSAGE })), immediately),
|
|
2419
|
+
}));
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
/**
|
|
2423
|
+
* @internal Exported for unit testing; use `useOpenUrlCallback`.
|
|
2424
|
+
*/
|
|
2425
|
+
function executeOpenUrl(url, dispatch, visuals, behavior, isMobile) {
|
|
2326
2426
|
var _a, _b;
|
|
2427
|
+
if (isMobile === void 0) { isMobile = isMobileViewport(); }
|
|
2327
2428
|
if (!dispatch || !visuals) {
|
|
2328
2429
|
throw new Error("OpenUrl: No dispatch and/or visual.");
|
|
2329
2430
|
}
|
|
@@ -2390,12 +2491,23 @@ function execute(url, dispatch, visuals, behavior) {
|
|
|
2390
2491
|
window.open(url, "_blank");
|
|
2391
2492
|
break;
|
|
2392
2493
|
case "sameWindow":
|
|
2393
|
-
|
|
2494
|
+
// Desktop: the next page reopens the chat beside the content, as before.
|
|
2495
|
+
// Mobile: the panel is full-screen, so reopening it would cover the page they
|
|
2496
|
+
// just asked for. Leave it closed; the param lets that page offer to continue.
|
|
2497
|
+
// Unconditional: callers can hand in a `visuals` captured before the chat opened
|
|
2498
|
+
// (ChatMarkdownMessage's click handler does), and skipping on a stale `opened`
|
|
2499
|
+
// leaves the persisted state open.
|
|
2500
|
+
if (isMobile) {
|
|
2501
|
+
dispatch(setVisualStatus({
|
|
2502
|
+
opened: false
|
|
2503
|
+
}));
|
|
2504
|
+
}
|
|
2505
|
+
else if (!visuals.opened) {
|
|
2394
2506
|
dispatch(setVisualStatus({
|
|
2395
2507
|
opened: true
|
|
2396
2508
|
}));
|
|
2397
2509
|
}
|
|
2398
|
-
window.open(url, "_self");
|
|
2510
|
+
window.open(withChatContinueParam(url, window.location.origin), "_self");
|
|
2399
2511
|
break;
|
|
2400
2512
|
default:
|
|
2401
2513
|
throwBadKind$1(type);
|
|
@@ -2407,7 +2519,7 @@ function useOpenUrlCallback() {
|
|
|
2407
2519
|
if (!behavior) {
|
|
2408
2520
|
behavior = (env === null || env === void 0 ? void 0 : env.urls) ? resolveUrlPolicy(url, env.urls) : defaultBehavior;
|
|
2409
2521
|
}
|
|
2410
|
-
|
|
2522
|
+
executeOpenUrl(url, dispatch, visuals, behavior);
|
|
2411
2523
|
}, [env]);
|
|
2412
2524
|
}
|
|
2413
2525
|
|
|
@@ -4300,6 +4412,10 @@ var MCPChat = /** @class */ (function () {
|
|
|
4300
4412
|
this.isInitializingSession = false;
|
|
4301
4413
|
// Session validation state (silent handshake - no stream dispatch)
|
|
4302
4414
|
this.isValidatingSession = false;
|
|
4415
|
+
// A conversation is on screen that the server should keep: restored from a previous page,
|
|
4416
|
+
// or confirmed by the server on this one. Sent as `continueConversation`, which also lets
|
|
4417
|
+
// the server resume a session idle longer than its default window.
|
|
4418
|
+
this.conversationOnScreen = false;
|
|
4303
4419
|
// Tool call tracking for debug mode
|
|
4304
4420
|
this.currentToolCalls = [];
|
|
4305
4421
|
this.isReplaying = false;
|
|
@@ -4387,8 +4503,12 @@ var MCPChat = /** @class */ (function () {
|
|
|
4387
4503
|
_a.label = 1;
|
|
4388
4504
|
case 1:
|
|
4389
4505
|
_a.trys.push([1, 3, 4, 5]);
|
|
4390
|
-
|
|
4506
|
+
// With a conversation on screen (e.g. after a same-tab link click), ask the
|
|
4507
|
+
// server to continue it: without the flag it clears the history and greets.
|
|
4508
|
+
return [4 /*yield*/, this.performSSERequest(this.buildRequestPayload(__assign$1({ resumeSession: true }, (this.conversationOnScreen && { continueConversation: true }))))];
|
|
4391
4509
|
case 2:
|
|
4510
|
+
// With a conversation on screen (e.g. after a same-tab link click), ask the
|
|
4511
|
+
// server to continue it: without the flag it clears the history and greets.
|
|
4392
4512
|
_a.sent();
|
|
4393
4513
|
log("Session validation complete");
|
|
4394
4514
|
return [3 /*break*/, 5];
|
|
@@ -4643,8 +4763,8 @@ var MCPChat = /** @class */ (function () {
|
|
|
4643
4763
|
// Generic fallback
|
|
4644
4764
|
return "Something went wrong. Please try again.";
|
|
4645
4765
|
};
|
|
4646
|
-
MCPChat.prototype.dispatchStreamStart = function (messageId) {
|
|
4647
|
-
var bot = this.getBot(
|
|
4766
|
+
MCPChat.prototype.dispatchStreamStart = function (messageId, user) {
|
|
4767
|
+
var bot = this.getBot(user);
|
|
4648
4768
|
var streamTimestamp = +new Date();
|
|
4649
4769
|
log("[dispatchStreamStart] Stream started: ".concat(messageId, ", timestamp: ").concat(streamTimestamp));
|
|
4650
4770
|
this.dispatch({
|
|
@@ -4657,30 +4777,25 @@ var MCPChat = /** @class */ (function () {
|
|
|
4657
4777
|
},
|
|
4658
4778
|
});
|
|
4659
4779
|
};
|
|
4660
|
-
MCPChat.prototype.dispatchStreamChunk = function (messageId, text) {
|
|
4780
|
+
MCPChat.prototype.dispatchStreamChunk = function (messageId, text, user) {
|
|
4661
4781
|
this.dispatch({
|
|
4662
4782
|
type: "chat",
|
|
4663
4783
|
detail: {
|
|
4664
4784
|
type: "chat.stream.chunk",
|
|
4665
|
-
user: this.getBot(
|
|
4785
|
+
user: this.getBot(user),
|
|
4666
4786
|
messageId: messageId,
|
|
4667
4787
|
text: text,
|
|
4668
4788
|
timestamp: +new Date(),
|
|
4669
4789
|
},
|
|
4670
4790
|
});
|
|
4671
4791
|
};
|
|
4672
|
-
MCPChat.prototype.dispatchStreamEnd = function (messageId, fullMessage,
|
|
4792
|
+
MCPChat.prototype.dispatchStreamEnd = function (messageId, fullMessage, options) {
|
|
4793
|
+
if (options === void 0) { options = {}; }
|
|
4794
|
+
var toolCalls = options.toolCalls, user = options.user, preserveChips = options.preserveChips;
|
|
4673
4795
|
log("Stream ended: ".concat(messageId));
|
|
4674
4796
|
this.dispatch({
|
|
4675
4797
|
type: "chat",
|
|
4676
|
-
detail: {
|
|
4677
|
-
type: "chat.stream.end",
|
|
4678
|
-
user: this.getBot(undefined),
|
|
4679
|
-
messageId: messageId,
|
|
4680
|
-
msg: fullMessage,
|
|
4681
|
-
toolCalls: toolCalls,
|
|
4682
|
-
timestamp: +new Date(),
|
|
4683
|
-
},
|
|
4798
|
+
detail: __assign$1(__assign$1({ type: "chat.stream.end", user: this.getBot(user), messageId: messageId, msg: fullMessage, toolCalls: toolCalls }, (preserveChips && { preserveChips: preserveChips })), { timestamp: +new Date() }),
|
|
4684
4799
|
});
|
|
4685
4800
|
};
|
|
4686
4801
|
MCPChat.prototype.dispatchStreamAbort = function (messageId) {
|
|
@@ -4790,6 +4905,19 @@ var MCPChat = /** @class */ (function () {
|
|
|
4790
4905
|
MCPChat.prototype.bufferStreamText = function (text) {
|
|
4791
4906
|
this.streamBuffer += text;
|
|
4792
4907
|
};
|
|
4908
|
+
/**
|
|
4909
|
+
* Dispatches whatever is buffered right now, without waiting on the throttle timer.
|
|
4910
|
+
*
|
|
4911
|
+
* Used when a bubble has to be closed mid-turn (`message_start`): the split has to be
|
|
4912
|
+
* synchronous, because frames arrive in order on one `onmessage` callback and deferring it
|
|
4913
|
+
* to a microtask would let the next `text` frame land in the bubble we are closing.
|
|
4914
|
+
*/
|
|
4915
|
+
MCPChat.prototype.flushStreamBufferSync = function () {
|
|
4916
|
+
if (this.streamBuffer.length > 0 && this.streamingMessageIdForThrottle) {
|
|
4917
|
+
this.dispatchStreamChunk(this.streamingMessageIdForThrottle, this.streamBuffer);
|
|
4918
|
+
this.streamBuffer = "";
|
|
4919
|
+
}
|
|
4920
|
+
};
|
|
4793
4921
|
/**
|
|
4794
4922
|
* Flushes any remaining buffered text and stops the throttle timer.
|
|
4795
4923
|
* Returns a promise that resolves when all buffered text has been dispatched.
|
|
@@ -5060,7 +5188,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5060
5188
|
return [2 /*return*/];
|
|
5061
5189
|
}
|
|
5062
5190
|
log("[attemptSSERequest] Sending message: \"".concat(messageText, "\", userId: ").concat(this._userId, ", sessionId: ").concat(this._sessionId));
|
|
5063
|
-
return [2 /*return*/, this.performSSERequest(this.buildRequestPayload({ message: messageText }))];
|
|
5191
|
+
return [2 /*return*/, this.performSSERequest(this.buildRequestPayload(__assign$1({ message: messageText }, (this.conversationOnScreen && { continueConversation: true }))))];
|
|
5064
5192
|
});
|
|
5065
5193
|
});
|
|
5066
5194
|
};
|
|
@@ -5070,7 +5198,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5070
5198
|
*/
|
|
5071
5199
|
MCPChat.prototype.performSSERequest = function (body) {
|
|
5072
5200
|
return __awaiter$1(this, void 0, void 0, function () {
|
|
5073
|
-
var endpoint, messageId,
|
|
5201
|
+
var endpoint, messageId, bubbleId, bubbleUser, bubbleChunks, hasError, httpStatusCode, sseError;
|
|
5074
5202
|
var _this = this;
|
|
5075
5203
|
return __generator$1(this, function (_a) {
|
|
5076
5204
|
endpoint = this.getEndpoint();
|
|
@@ -5083,7 +5211,8 @@ var MCPChat = /** @class */ (function () {
|
|
|
5083
5211
|
this.abortController = new AbortController();
|
|
5084
5212
|
messageId = uuid_1();
|
|
5085
5213
|
this.currentMessageId = messageId;
|
|
5086
|
-
|
|
5214
|
+
bubbleId = messageId;
|
|
5215
|
+
bubbleChunks = [];
|
|
5087
5216
|
hasError = false;
|
|
5088
5217
|
return [2 /*return*/, new Promise(function (resolve, reject) {
|
|
5089
5218
|
// WARNING: Do not log headers object - it contains sensitive auth tokens
|
|
@@ -5114,6 +5243,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5114
5243
|
});
|
|
5115
5244
|
}); },
|
|
5116
5245
|
onmessage: function (event) {
|
|
5246
|
+
var _a;
|
|
5117
5247
|
// Guard: ignore events from stale/aborted requests
|
|
5118
5248
|
if (_this.currentMessageId !== messageId) {
|
|
5119
5249
|
log("[SSE] Ignoring stale event for messageId: ".concat(messageId, ", current: ").concat(_this.currentMessageId));
|
|
@@ -5140,10 +5270,15 @@ var MCPChat = /** @class */ (function () {
|
|
|
5140
5270
|
case "connected":
|
|
5141
5271
|
// Connection established
|
|
5142
5272
|
log("[SSE] Connected event received, messageId: ".concat(messageId, ", isReturningUser: ").concat(data.isReturningUser, ", isReplay: ").concat(data.isReplay));
|
|
5273
|
+
_this.conversationOnScreen = true;
|
|
5143
5274
|
// Track if this is a replay connection
|
|
5144
5275
|
if (data.isReplay) {
|
|
5145
5276
|
_this.isReplaying = true;
|
|
5146
5277
|
}
|
|
5278
|
+
// A server may name the participant up front.
|
|
5279
|
+
if (data.user) {
|
|
5280
|
+
bubbleUser = data.user;
|
|
5281
|
+
}
|
|
5147
5282
|
// Store identity from server response
|
|
5148
5283
|
if (data.userId || data.sessionId) {
|
|
5149
5284
|
if (data.userId) {
|
|
@@ -5163,18 +5298,35 @@ var MCPChat = /** @class */ (function () {
|
|
|
5163
5298
|
if (data.isReplay) {
|
|
5164
5299
|
// Replay connection — resume into the existing partial
|
|
5165
5300
|
// message instead of creating a duplicate placeholder
|
|
5166
|
-
_this.dispatchStreamResume(
|
|
5301
|
+
_this.dispatchStreamResume(bubbleId);
|
|
5167
5302
|
}
|
|
5168
5303
|
else {
|
|
5169
5304
|
// Normal connection — create new streaming placeholder
|
|
5170
|
-
_this.dispatchStreamStart(
|
|
5305
|
+
_this.dispatchStreamStart(bubbleId, bubbleUser);
|
|
5306
|
+
}
|
|
5307
|
+
_this.startStreamThrottle(bubbleId);
|
|
5308
|
+
break;
|
|
5309
|
+
case "session_resumed":
|
|
5310
|
+
// The server continued the conversation: no greeting follows,
|
|
5311
|
+
// so adopt the identity without opening a stream bubble.
|
|
5312
|
+
log("[SSE] Session resumed, sessionId: ".concat(data.sessionId, ", hasHistory: ").concat(data.hasHistory));
|
|
5313
|
+
_this.conversationOnScreen = true;
|
|
5314
|
+
if (data.userId) {
|
|
5315
|
+
_this._userId = data.userId;
|
|
5316
|
+
_this.userIdIsServerAssigned = true;
|
|
5317
|
+
}
|
|
5318
|
+
if (data.sessionId) {
|
|
5319
|
+
_this._sessionId = data.sessionId;
|
|
5320
|
+
_this.dispatch(setSessionId(_this._sessionId));
|
|
5171
5321
|
}
|
|
5172
|
-
|
|
5322
|
+
storeIdentity(data.userId, data.sessionId);
|
|
5323
|
+
_this.stopTyping();
|
|
5173
5324
|
break;
|
|
5174
5325
|
case "session_expired":
|
|
5175
5326
|
// Session no longer exists on server - need to start fresh
|
|
5176
5327
|
log("[SSE] Session expired: ".concat(data.reason || "Session not found"));
|
|
5177
5328
|
// Clear stored session (and lastSeq) but keep userId for returning user identification
|
|
5329
|
+
_this.conversationOnScreen = false;
|
|
5178
5330
|
clearStoredSession(_this._sessionId);
|
|
5179
5331
|
_this._sessionId = "";
|
|
5180
5332
|
_this.lastSeq = undefined;
|
|
@@ -5187,14 +5339,40 @@ var MCPChat = /** @class */ (function () {
|
|
|
5187
5339
|
_this.initializeNewSession();
|
|
5188
5340
|
resolve();
|
|
5189
5341
|
break;
|
|
5342
|
+
case "message_start": {
|
|
5343
|
+
// Close the bubble being streamed and open a new one, optionally
|
|
5344
|
+
// under a different participant. This is the only way a turn can
|
|
5345
|
+
// render as more than one message: the reducer creates a fresh
|
|
5346
|
+
// bubble per `chat.stream.start` and finalizes any still-streaming
|
|
5347
|
+
// one, so a server can make a handoff look like someone joining.
|
|
5348
|
+
// Servers that never send this are unaffected.
|
|
5349
|
+
log("[SSE] message_start \u2014 splitting bubble, user: ".concat((_a = data.user) === null || _a === void 0 ? void 0 : _a.displayName));
|
|
5350
|
+
_this.flushStreamBufferSync();
|
|
5351
|
+
_this.dispatchStreamEnd(bubbleId, { text: bubbleChunks.join("") }, {
|
|
5352
|
+
// Same debug-mode capture as `done`, scoped to this bubble:
|
|
5353
|
+
// stream.end replaces a bubble's toolCalls wholesale, so any
|
|
5354
|
+
// left in currentToolCalls would reappear on the next one.
|
|
5355
|
+
toolCalls: (_this.config.debugMode || _this._debugMode) ? __spreadArray$1([], _this.currentToolCalls, true) : undefined,
|
|
5356
|
+
user: bubbleUser,
|
|
5357
|
+
// Chips belong to the turn; its final end sets them.
|
|
5358
|
+
preserveChips: true,
|
|
5359
|
+
});
|
|
5360
|
+
_this.currentToolCalls = [];
|
|
5361
|
+
bubbleId = uuid_1();
|
|
5362
|
+
bubbleChunks = [];
|
|
5363
|
+
bubbleUser = data.user;
|
|
5364
|
+
_this.dispatchStreamStart(bubbleId, bubbleUser);
|
|
5365
|
+
_this.startStreamThrottle(bubbleId);
|
|
5366
|
+
break;
|
|
5367
|
+
}
|
|
5190
5368
|
case "text":
|
|
5191
5369
|
if (data.text) {
|
|
5192
|
-
log("[SSE] Text chunk received (".concat(data.text.length, " chars),
|
|
5193
|
-
|
|
5370
|
+
log("[SSE] Text chunk received (".concat(data.text.length, " chars), chunks in this bubble: ").concat(bubbleChunks.length + 1, ", isReplay: ").concat(data.isReplay));
|
|
5371
|
+
bubbleChunks.push(data.text);
|
|
5194
5372
|
if (data.isReplay || _this.isSleeping) {
|
|
5195
5373
|
// Replayed text or tab hidden — dispatch immediately
|
|
5196
5374
|
// (no throttle timer running in either case)
|
|
5197
|
-
_this.dispatchStreamChunk(
|
|
5375
|
+
_this.dispatchStreamChunk(bubbleId, data.text, bubbleUser);
|
|
5198
5376
|
}
|
|
5199
5377
|
else {
|
|
5200
5378
|
// Live text — buffer for throttled dispatch
|
|
@@ -5218,7 +5396,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5218
5396
|
_this.currentToolCalls.push(toolCall);
|
|
5219
5397
|
// Always dispatch tool start for user-friendly progress display
|
|
5220
5398
|
_this.dispatchToolStart({
|
|
5221
|
-
messageId:
|
|
5399
|
+
messageId: bubbleId,
|
|
5222
5400
|
toolName: data.toolName,
|
|
5223
5401
|
toolCallId: data.toolCallId,
|
|
5224
5402
|
displayName: data.displayName,
|
|
@@ -5259,7 +5437,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5259
5437
|
}
|
|
5260
5438
|
// Always dispatch tool end for user-friendly progress display
|
|
5261
5439
|
_this.dispatchToolEnd({
|
|
5262
|
-
messageId:
|
|
5440
|
+
messageId: bubbleId,
|
|
5263
5441
|
toolName: data.toolName,
|
|
5264
5442
|
toolCallId: data.toolCallId,
|
|
5265
5443
|
displayName: data.displayName,
|
|
@@ -5281,8 +5459,10 @@ var MCPChat = /** @class */ (function () {
|
|
|
5281
5459
|
storeIdentity(undefined, data.response.sessionId);
|
|
5282
5460
|
}
|
|
5283
5461
|
// Wait for buffer to flush, then finalize
|
|
5284
|
-
|
|
5285
|
-
|
|
5462
|
+
// The bubble's own text, not the whole turn's — a turn split by
|
|
5463
|
+
// `message_start` has already committed its earlier bubbles.
|
|
5464
|
+
var accumulatedText = bubbleChunks.join("");
|
|
5465
|
+
log("[SSE] Done event received. Chunks in this bubble: ".concat(bubbleChunks.length, ", accumulated text length: ").concat(accumulatedText.length));
|
|
5286
5466
|
log("[SSE] First 100 chars of accumulated: ".concat(accumulatedText.substring(0, 100)));
|
|
5287
5467
|
// Map suggestions to options (chips) format
|
|
5288
5468
|
var suggestions = data.response.suggestions;
|
|
@@ -5298,8 +5478,14 @@ var MCPChat = /** @class */ (function () {
|
|
|
5298
5478
|
if (sources === null || sources === void 0 ? void 0 : sources.length) {
|
|
5299
5479
|
log("[SSE] Sources: ".concat(sources.length, " items"));
|
|
5300
5480
|
}
|
|
5481
|
+
// `response.response` is request-scoped. For an unsplit turn it is
|
|
5482
|
+
// the whole answer, and the fallback serves servers that deliver it
|
|
5483
|
+
// only via `done`. Once `message_start` has fired it would copy every
|
|
5484
|
+
// earlier bubble's text into this one, so a split turn's bubbles
|
|
5485
|
+
// must stream their text as `text` frames.
|
|
5486
|
+
var isSplitTurn = bubbleId !== messageId;
|
|
5301
5487
|
var finalMessage_1 = {
|
|
5302
|
-
text: accumulatedText || data.response.response || "",
|
|
5488
|
+
text: accumulatedText || (isSplitTurn ? "" : data.response.response) || "",
|
|
5303
5489
|
options: options,
|
|
5304
5490
|
sources: sources,
|
|
5305
5491
|
displays: data.response.displays,
|
|
@@ -5307,7 +5493,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5307
5493
|
// Capture tool calls before flushing (debug mode — config or runtime toggle)
|
|
5308
5494
|
var toolCalls_1 = (_this.config.debugMode || _this._debugMode) ? __spreadArray$1([], _this.currentToolCalls, true) : undefined;
|
|
5309
5495
|
_this.flushStreamBuffer().then(function () {
|
|
5310
|
-
_this.dispatchStreamEnd(
|
|
5496
|
+
_this.dispatchStreamEnd(bubbleId, finalMessage_1, { toolCalls: toolCalls_1, user: bubbleUser });
|
|
5311
5497
|
_this.currentToolCalls = []; // Clear after dispatch
|
|
5312
5498
|
resolve();
|
|
5313
5499
|
});
|
|
@@ -5347,7 +5533,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5347
5533
|
log("[MCPChat] SSE error during sleep - will request replay on wakeup");
|
|
5348
5534
|
_this.needsReplayOnWakeup = true;
|
|
5349
5535
|
if (_this.streamingMessageIdForThrottle) {
|
|
5350
|
-
_this.dispatchStreamAbort(
|
|
5536
|
+
_this.dispatchStreamAbort(bubbleId);
|
|
5351
5537
|
}
|
|
5352
5538
|
_this.stopStreamThrottle();
|
|
5353
5539
|
}
|
|
@@ -5391,6 +5577,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5391
5577
|
MCPChat.prototype.setVisitorInfo = function (visitorInfo, sessionId, cb) {
|
|
5392
5578
|
this.visitorInfo = visitorInfo;
|
|
5393
5579
|
this._attributes = __assign$1({}, this.visitorInfo.attributes);
|
|
5580
|
+
this.conversationOnScreen = visitorInfo.hasConversation === true;
|
|
5394
5581
|
// do not set currentUrl if localhost
|
|
5395
5582
|
var href = new URL(window.location.href);
|
|
5396
5583
|
if (href === null || href === void 0 ? void 0 : href.host.toLowerCase().startsWith("localhost")) {
|
|
@@ -12905,7 +13092,21 @@ var ChatTextMessage = function (props) {
|
|
|
12905
13092
|
var wasStreamed = message.wasStreamed;
|
|
12906
13093
|
var showTail = agentMessage && !props.sibling && !isStreaming && !wasStreamed;
|
|
12907
13094
|
var hasToolCalls = props.toolCalls && props.toolCalls.length > 0;
|
|
12908
|
-
|
|
13095
|
+
// Delegated, because the links are inside dangerouslySetInnerHTML. Modified clicks stay
|
|
13096
|
+
// with the browser so ctrl/cmd-click still opens a background tab.
|
|
13097
|
+
var onLinkClick = props.onLinkClick;
|
|
13098
|
+
var handleContentClick = function (ev) {
|
|
13099
|
+
var _a, _b;
|
|
13100
|
+
if (!onLinkClick || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) {
|
|
13101
|
+
return;
|
|
13102
|
+
}
|
|
13103
|
+
var link = (_b = (_a = ev.target).closest) === null || _b === void 0 ? void 0 : _b.call(_a, "a");
|
|
13104
|
+
if (link && ev.currentTarget.contains(link)) {
|
|
13105
|
+
ev.preventDefault();
|
|
13106
|
+
onLinkClick(link.href);
|
|
13107
|
+
}
|
|
13108
|
+
};
|
|
13109
|
+
return (jsxs("div", { className: "chat-msg".concat(isStreaming ? " chat-msg--streaming" : ""), children: [hasToolCalls && (jsx(ToolCallDisplay, { toolCalls: props.toolCalls, debugMode: props.debugMode })), jsx("div", { className: "chat-text-container", children: jsxs(ChatMessageBubble, { owner: agentMessage ? "others" : "mine", hasTail: showTail, children: [jsx("span", { className: "message-sr-only", children: "at " + time + (agentMessage ? (isStreaming ? " the bot is typing" : " the bot said") : " the user said") }), parsedHtml ? (jsx("span", { className: "chat-msg__content", onClick: handleContentClick, dangerouslySetInnerHTML: { __html: parsedHtml } })) : (jsx("span", { className: "chat-msg__content", children: message.msg.text })), isStreaming && jsx("span", { className: "streaming-cursor", "aria-hidden": "true" })] }) })] }));
|
|
12909
13110
|
};
|
|
12910
13111
|
|
|
12911
13112
|
var ChatScheduleWidget = function (props) {
|
|
@@ -12948,14 +13149,21 @@ var avaKeys = ["text", "html", "card", "list"];
|
|
|
12948
13149
|
* @returns
|
|
12949
13150
|
*/
|
|
12950
13151
|
var ChatMessage = function (props) {
|
|
12951
|
-
var _a, _b, _c;
|
|
13152
|
+
var _a, _b, _c, _d;
|
|
12952
13153
|
var middleware = props.messageMiddleware || StandardMiddlewares;
|
|
12953
13154
|
var chatConfig = useContext(ChatConfigContext);
|
|
12954
13155
|
// console.log(`########### chatConfig: ${JSON.stringify(chatConfig, null, 2)}`);
|
|
12955
13156
|
var ctx = props.middlewareContext;
|
|
12956
13157
|
var user = props.agent;
|
|
13158
|
+
var chatDispatch = useChatDispatch();
|
|
13159
|
+
var visuals = useSelector(function (state) { return state.visuals; });
|
|
13160
|
+
// Text links follow the tenant's URL policy when it set one (utm_source, same-tab
|
|
13161
|
+
// continuation, ...). Tenants without one keep the plain new-tab anchor.
|
|
13162
|
+
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)
|
|
13163
|
+
? function (href) { return ctx.openUrl(href, chatDispatch, visuals); }
|
|
13164
|
+
: undefined;
|
|
12957
13165
|
// Get hideUserInfo from the agent info based on the message sender's nick
|
|
12958
|
-
var agentInfo = (
|
|
13166
|
+
var agentInfo = (_b = props.agents) === null || _b === void 0 ? void 0 : _b[props.message.user.nick];
|
|
12959
13167
|
var hideUserInfo = (agentInfo === null || agentInfo === void 0 ? void 0 : agentInfo.hideUserInfo) || false;
|
|
12960
13168
|
function renderByType() {
|
|
12961
13169
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
|
|
@@ -12974,7 +13182,7 @@ var ChatMessage = function (props) {
|
|
|
12974
13182
|
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;
|
|
12975
13183
|
// Show tool progress with avatar when no text yet, or above text when text exists
|
|
12976
13184
|
var showToolProgressWithAvatar = hasToolCalls && !msg.text;
|
|
12977
|
-
return (jsxs(Fragment, { children: [showToolProgressWithAvatar && (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: jsx("div", { className: "chat-msg", children: jsx(ToolCallDisplay, { toolCalls: toolCalls, debugMode: debugMode }) }) })), msg.text && (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: jsx(ChatTextMessage, { message: props.message, sibling: props.sibling, user: user, toolCalls: hasToolCalls ? toolCalls : undefined, debugMode: debugMode }) })), msg.html &&
|
|
13185
|
+
return (jsxs(Fragment, { children: [showToolProgressWithAvatar && (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: jsx("div", { className: "chat-msg", children: jsx(ToolCallDisplay, { toolCalls: toolCalls, debugMode: debugMode }) }) })), msg.text && (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: jsx(ChatTextMessage, { message: props.message, sibling: props.sibling, user: user, toolCalls: hasToolCalls ? toolCalls : undefined, debugMode: debugMode, onLinkClick: handleTextLinkClick }) })), msg.html &&
|
|
12978
13186
|
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: 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) {
|
|
12979
13187
|
if (display.type === "ScheduleButton") {
|
|
12980
13188
|
return (jsx(ChatScheduleWidget, { minimizeOnClick: props.minimizeOnClick, display: display }, index));
|
|
@@ -12998,7 +13206,7 @@ var ChatMessage = function (props) {
|
|
|
12998
13206
|
}
|
|
12999
13207
|
// Don't show timestamp/sources while streaming or if message has no content yet
|
|
13000
13208
|
var isStreaming = props.message.isStreaming;
|
|
13001
|
-
var hasContent = !!(((
|
|
13209
|
+
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));
|
|
13002
13210
|
var showFooter = !isStreaming && hasContent;
|
|
13003
13211
|
// empty
|
|
13004
13212
|
return (jsx("div", { className: "chat-msg-container-wrapper ".concat(isAgent(props.message.user.nick) ? "agent" : "visitor", " ").concat(props.sibling ? "sibling" : ""), children: jsxs("div", { className: "chat-msg-container ".concat(getClassName(props.message)), children: [renderByType(), showFooter && renderTimestampAndSources()] }) }));
|
|
@@ -13008,9 +13216,6 @@ var ChatMessage = function (props) {
|
|
|
13008
13216
|
* value must be like "60m", "2h" etc.
|
|
13009
13217
|
*/
|
|
13010
13218
|
function convertToSeconds(s) {
|
|
13011
|
-
if (!s) {
|
|
13012
|
-
return 0;
|
|
13013
|
-
}
|
|
13014
13219
|
var groups = s.match(/(\d+)([dhms]?)/);
|
|
13015
13220
|
if (!groups) {
|
|
13016
13221
|
err("Invalid time string: ".concat(s));
|
|
@@ -13033,15 +13238,50 @@ function convertToSeconds(s) {
|
|
|
13033
13238
|
}
|
|
13034
13239
|
}
|
|
13035
13240
|
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13241
|
+
/**
|
|
13242
|
+
* How long a conversation must sit idle, on top of the calendar day changing, before it
|
|
13243
|
+
* expires when the widget config does not say.
|
|
13244
|
+
*/
|
|
13245
|
+
var DEFAULT_SESSION_EXPIRATION = "4h";
|
|
13246
|
+
function isSameLocalDay(a, b) {
|
|
13247
|
+
var dayA = new Date(a);
|
|
13248
|
+
var dayB = new Date(b);
|
|
13249
|
+
return dayA.getFullYear() === dayB.getFullYear()
|
|
13250
|
+
&& dayA.getMonth() === dayB.getMonth()
|
|
13251
|
+
&& dayA.getDate() === dayB.getDate();
|
|
13252
|
+
}
|
|
13253
|
+
/**
|
|
13254
|
+
* A conversation expires only when BOTH the visitor's local calendar day has changed since
|
|
13255
|
+
* the last message AND at least `duration` has passed. So a visitor who browses the site
|
|
13256
|
+
* over an afternoon keeps one conversation, and one chatting at 11pm is not greeted afresh
|
|
13257
|
+
* at 1am - but tomorrow's visit starts clean.
|
|
13258
|
+
*/
|
|
13259
|
+
function checkSessionExpiration(duration, lastMessageTimestamp, _lastTimestamp, now) {
|
|
13260
|
+
if (now === void 0) { now = Date.now(); }
|
|
13261
|
+
if (!lastMessageTimestamp) {
|
|
13040
13262
|
return false;
|
|
13041
13263
|
}
|
|
13042
|
-
|
|
13043
|
-
|
|
13044
|
-
|
|
13264
|
+
var durationMs = convertToSeconds(duration || DEFAULT_SESSION_EXPIRATION) * 1000;
|
|
13265
|
+
return now - lastMessageTimestamp >= durationMs && !isSameLocalDay(lastMessageTimestamp, now);
|
|
13266
|
+
}
|
|
13267
|
+
|
|
13268
|
+
/**
|
|
13269
|
+
* Whether this page was reached through a same-tab link in the chat, read once on mount.
|
|
13270
|
+
*
|
|
13271
|
+
* The read is pure and the strip runs in an effect. StrictMode calls state initializers
|
|
13272
|
+
* twice in development, and React 18 (still allowed by our peer range) keeps the second
|
|
13273
|
+
* result - which an initializer that also stripped the param would get wrong. React 19
|
|
13274
|
+
* reuses the first result, so the test suite here cannot show that failure.
|
|
13275
|
+
*/
|
|
13276
|
+
function useArrivedViaChatLink(disabled) {
|
|
13277
|
+
if (disabled === void 0) { disabled = false; }
|
|
13278
|
+
var arrived = useState(function () { return !disabled && hasChatContinueParam(); })[0];
|
|
13279
|
+
useEffect(function () {
|
|
13280
|
+
if (arrived) {
|
|
13281
|
+
stripChatContinueParam();
|
|
13282
|
+
}
|
|
13283
|
+
}, [arrived]);
|
|
13284
|
+
return arrived;
|
|
13045
13285
|
}
|
|
13046
13286
|
|
|
13047
13287
|
function useExternalScript(url) {
|
|
@@ -13162,6 +13402,7 @@ function useGreeting(active) {
|
|
|
13162
13402
|
// Handle null context (e.g., in preview mode)
|
|
13163
13403
|
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;
|
|
13164
13404
|
var sessionId = useSelector(function (state) { return state.sessionId; });
|
|
13405
|
+
var conversationOnScreen = useSelector(function (state) { return hasConversation(state.chats); });
|
|
13165
13406
|
useEffect(function () {
|
|
13166
13407
|
if (active) {
|
|
13167
13408
|
if (snapshotRef.current !== curr) {
|
|
@@ -13172,7 +13413,8 @@ function useGreeting(active) {
|
|
|
13172
13413
|
visitorId: curr.visitorId,
|
|
13173
13414
|
userId: curr.userId,
|
|
13174
13415
|
accessToken: curr.accessToken,
|
|
13175
|
-
attributes: __assign$1(__assign$1({}, curr.attributes), { currentUrl: window.location.href })
|
|
13416
|
+
attributes: __assign$1(__assign$1({}, curr.attributes), { currentUrl: window.location.href }),
|
|
13417
|
+
hasConversation: conversationOnScreen
|
|
13176
13418
|
}, sessionId));
|
|
13177
13419
|
if (!isAdmin) {
|
|
13178
13420
|
var timeoutId_1 = setTimeout(function () {
|
|
@@ -37028,7 +37270,7 @@ var ChatWidgetConnected = function (props) {
|
|
|
37028
37270
|
* Exported for use by StaticChatWidgetContainer to avoid infinite loops.
|
|
37029
37271
|
*/
|
|
37030
37272
|
var ChatWidgetUI = function (props) {
|
|
37031
|
-
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;
|
|
37273
|
+
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;
|
|
37032
37274
|
var innerDispatch = useChatDispatch();
|
|
37033
37275
|
var dispatch = useChatServerDispatch();
|
|
37034
37276
|
// Publish the visual viewport's geometry so the panel can size around the soft keyboard.
|
|
@@ -37049,14 +37291,18 @@ var ChatWidgetUI = function (props) {
|
|
|
37049
37291
|
// Preview mode can be enabled via `preview` prop (recommended) or legacy `mode="preview"`
|
|
37050
37292
|
var previewMode = props.preview === true || mode === "preview";
|
|
37051
37293
|
var modeClass = "widget-container--".concat(mode);
|
|
37052
|
-
|
|
37294
|
+
// Reached this page through a link in the chat.
|
|
37295
|
+
var arrivedViaChatLink = useArrivedViaChatLink(previewMode);
|
|
37296
|
+
var conversationOnScreen = hasConversation(chatState.chats);
|
|
37297
|
+
var chatButtonCta = 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]);
|
|
37298
|
+
var canRefresh = (_d = (_c = props.config.header) === null || _c === void 0 ? void 0 : _c.actions) === null || _d === void 0 ? void 0 : _d.refresh;
|
|
37053
37299
|
// can't minimize in docked mode, static mode, or preview mode.
|
|
37054
|
-
var canMinimize = !dockedMode && !staticMode && !previewMode && ((
|
|
37055
|
-
log("docked: ".concat(dockedMode, " static: ").concat(staticMode, " preview: ").concat(previewMode, " minimized: ").concat((
|
|
37300
|
+
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);
|
|
37301
|
+
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));
|
|
37056
37302
|
var canCancel;
|
|
37057
37303
|
// To preserve legacy behavior, cancel needs a little more checks
|
|
37058
|
-
if (typeof ((
|
|
37059
|
-
canCancel = !dockedMode && !staticMode && !previewMode && ((
|
|
37304
|
+
if (typeof ((_k = (_j = props.config.header) === null || _j === void 0 ? void 0 : _j.actions) === null || _k === void 0 ? void 0 : _k.cancel) === "boolean") {
|
|
37305
|
+
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);
|
|
37060
37306
|
}
|
|
37061
37307
|
else {
|
|
37062
37308
|
canCancel = !dockedMode && !staticMode && !previewMode;
|
|
@@ -37067,16 +37313,16 @@ var ChatWidgetUI = function (props) {
|
|
|
37067
37313
|
}
|
|
37068
37314
|
// Our state - pull from storage
|
|
37069
37315
|
// Parse autoOpenOnWidth once for use in both initial state and useEffect
|
|
37070
|
-
var autoOpenOnWidthValue = ((
|
|
37316
|
+
var autoOpenOnWidthValue = ((_o = props.config) === null || _o === void 0 ? void 0 : _o.autoOpenOnWidth)
|
|
37071
37317
|
? parseInt(props.config.autoOpenOnWidth, 10)
|
|
37072
37318
|
: NaN;
|
|
37073
37319
|
var autoOpenOnWidthMatch = !isNaN(autoOpenOnWidthValue) && window.innerWidth >= autoOpenOnWidthValue;
|
|
37074
37320
|
// When autoOpenOnWidth is configured, use width check instead of persisted visible state
|
|
37075
37321
|
// to prevent greeting requests on viewports below the threshold
|
|
37076
|
-
var
|
|
37322
|
+
var _15 = useState((!canMinimize && !canCancel) ||
|
|
37077
37323
|
// !!get("visible") ||
|
|
37078
|
-
(!isNaN(autoOpenOnWidthValue) ? autoOpenOnWidthMatch : chatState.visuals.visible)), visible =
|
|
37079
|
-
var
|
|
37324
|
+
(!isNaN(autoOpenOnWidthValue) ? autoOpenOnWidthMatch : chatState.visuals.visible)), visible = _15[0], setVisibleState = _15[1];
|
|
37325
|
+
var _16 = useState(false); _16[0]; var setTypingState = _16[1]; // false initially - state kept for potential external observers
|
|
37080
37326
|
// Ref to track typing state for use in timeout callbacks
|
|
37081
37327
|
var typingRef = useRef(false);
|
|
37082
37328
|
// Timeout ref for debouncing "stop typing" events
|
|
@@ -37084,11 +37330,11 @@ var ChatWidgetUI = function (props) {
|
|
|
37084
37330
|
// Track whether autoOpenOnWidth has already fired so it doesn't re-open after user closes
|
|
37085
37331
|
var autoOpenFiredRef = useRef(autoOpenOnWidthMatch);
|
|
37086
37332
|
var chatServer = useContext(ChatServerContext);
|
|
37087
|
-
var patternsConfig = (
|
|
37333
|
+
var patternsConfig = (_q = (_p = props.config) === null || _p === void 0 ? void 0 : _p.autoOpenOnPattern) === null || _q === void 0 ? void 0 : _q.patterns;
|
|
37088
37334
|
var currentUrl = window.location.href;
|
|
37089
37335
|
var patternExist = patternsConfig && patternsConfig.length > 0;
|
|
37090
37336
|
var patternMatches = patternsConfig === null || patternsConfig === void 0 ? void 0 : patternsConfig.some(function (pattern) { return currentUrl.includes(pattern); });
|
|
37091
|
-
var configWidth = (
|
|
37337
|
+
var configWidth = (_s = (_r = props.config) === null || _r === void 0 ? void 0 : _r.autoOpenOnPattern) === null || _s === void 0 ? void 0 : _s.minimumWidth;
|
|
37092
37338
|
var currentWidth = window.innerWidth;
|
|
37093
37339
|
var setVisible = useCallback(function (newVisible) {
|
|
37094
37340
|
if (staticMode || previewMode) {
|
|
@@ -37123,7 +37369,7 @@ var ChatWidgetUI = function (props) {
|
|
|
37123
37369
|
};
|
|
37124
37370
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
37125
37371
|
}, []);
|
|
37126
|
-
var
|
|
37372
|
+
var _17 = useState(!document.hidden), isTabVisible = _17[0], setIsTabVisible = _17[1];
|
|
37127
37373
|
useEffect(function () {
|
|
37128
37374
|
var handleVisibilityChange = function () {
|
|
37129
37375
|
setIsTabVisible(!document.hidden);
|
|
@@ -37347,8 +37593,8 @@ var ChatWidgetUI = function (props) {
|
|
|
37347
37593
|
}));
|
|
37348
37594
|
}
|
|
37349
37595
|
// Action Bar state and handlers
|
|
37350
|
-
var
|
|
37351
|
-
var
|
|
37596
|
+
var _18 = useState(false), formModalOpen = _18[0], setFormModalOpen = _18[1];
|
|
37597
|
+
var _19 = useState(null), activeFormConfig = _19[0], setActiveFormConfig = _19[1];
|
|
37352
37598
|
var handleFormButtonClick = useCallback(function (formButton) {
|
|
37353
37599
|
// If form-widget is configured, use its openForm API
|
|
37354
37600
|
// The form-widget script should already be pre-loaded
|
|
@@ -37415,7 +37661,7 @@ var ChatWidgetUI = function (props) {
|
|
|
37415
37661
|
var messages = chatState && chatState.chats;
|
|
37416
37662
|
log("Rendering - accountStatus: \"".concat(chatState.accountStatus, "\", connectionStatus: \"").concat(chatState.connection.connectionStatus, "\""));
|
|
37417
37663
|
var config = props.config, onConnectionStatusChange = props.onConnectionStatusChange;
|
|
37418
|
-
var actionBarEnabled = ((
|
|
37664
|
+
var actionBarEnabled = ((_t = config === null || config === void 0 ? void 0 : config.actionBar) === null || _t === void 0 ? void 0 : _t.enabled) === true;
|
|
37419
37665
|
// The host page declares this; the widget cannot work it out. On the standalone
|
|
37420
37666
|
// contact/booking page the form already *is* the page, which makes every button that
|
|
37421
37667
|
// only opens one redundant. XappMedia/chat-widget#1626.
|
|
@@ -37439,7 +37685,7 @@ var ChatWidgetUI = function (props) {
|
|
|
37439
37685
|
onConnectionStatusChange(connectionStatus);
|
|
37440
37686
|
}
|
|
37441
37687
|
}, [connectionStatus, onConnectionStatusChange]);
|
|
37442
|
-
useExternalScript((
|
|
37688
|
+
useExternalScript((_u = props.config) === null || _u === void 0 ? void 0 : _u.middlewareUrl);
|
|
37443
37689
|
// Only reach for form.xapp.ai when something on this page can still open the form.
|
|
37444
37690
|
//
|
|
37445
37691
|
// Loading it is not free: the script mounts a second form-widget instance whose wrapper
|
|
@@ -37479,15 +37725,18 @@ var ChatWidgetUI = function (props) {
|
|
|
37479
37725
|
};
|
|
37480
37726
|
}, [actionBarVisible]);
|
|
37481
37727
|
// This is a pseudo agent. It represent's the widget (shown in the header avatar for instance)
|
|
37482
|
-
var widgetAgent = ((
|
|
37728
|
+
var widgetAgent = ((_v = chatState.agents["agent:robot"]) === null || _v === void 0 ? void 0 : _v.user) ||
|
|
37483
37729
|
(config === null || config === void 0 ? void 0 : config.agent) || {
|
|
37484
37730
|
nick: "agent:robot",
|
|
37485
37731
|
avatarPath: config.avatarUrl,
|
|
37486
37732
|
display_name: "Agent",
|
|
37487
37733
|
};
|
|
37488
|
-
return (jsxs(Fragment, { children: [jsxs("div", { className: "widget-container ".concat(modeClass, " ").concat(getVisibilityClass()).concat(actionBarVisible ? " widget-container--with-action-bar" : ""), children: [jsx(WidgetStylesheet, { theme: config === null || config === void 0 ? void 0 : config.theme }), 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: (
|
|
37734
|
+
return (jsxs(Fragment, { children: [jsxs("div", { className: "widget-container ".concat(modeClass, " ").concat(getVisibilityClass()).concat(actionBarVisible ? " widget-container--with-action-bar" : ""), children: [jsx(WidgetStylesheet, { theme: config === null || config === void 0 ? void 0 : config.theme }), 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 }), 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 }), jsx("div", { className: "xa-spinner-container ".concat(visible && connectionStatus === "pending" ? "visible" : ""), children: jsx("div", { className: "xa-spinner" }) }), connectionStatus === "offline" && jsx(ServerOffline, {}), chatState.wsButton && visible && (jsx(WsButton, { button: chatState.wsButton, onPress: handleWsButtonPress })), 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 }), jsx("div", { className: "restartModal", ref: modalRef, onClick: handleRestartModalCloseClick, children: jsx(ModalContent, { onClose: handleRestartModalCloseClick, onReset: handleReset }) })] }), actionBarVisible && config.actionBar ? (jsxs(Fragment, { children: [jsx(ActionBar, { config: __assign$1(__assign$1({}, config.actionBar), {
|
|
37489
37735
|
// Use actionBar.cta if set, otherwise fall back to widget-level cta
|
|
37490
|
-
cta: (
|
|
37736
|
+
cta: resolveCta((_11 = config.actionBar.cta) !== null && _11 !== void 0 ? _11 : config.cta, {
|
|
37737
|
+
hasConversation: conversationOnScreen,
|
|
37738
|
+
arrivedViaChatLink: arrivedViaChatLink,
|
|
37739
|
+
}) }), 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 && (jsx(FormModal, { config: activeFormConfig, widgetEnv: config, onClose: handleFormModalClose }))] })) : (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 })), jsx(ErrorOverlay, { enableErrorOverlay: config === null || config === void 0 ? void 0 : config.enableErrorOverlay })] }));
|
|
37491
37740
|
};
|
|
37492
37741
|
/**
|
|
37493
37742
|
* Top-level wrapper that dispatches between preview mode and connected mode.
|
|
@@ -37947,8 +38196,11 @@ function update(state, action) {
|
|
|
37947
38196
|
var finalMsg_1 = endDetail.msg;
|
|
37948
38197
|
// Preserve tool calls from either the action or the existing message
|
|
37949
38198
|
var toolCalls_1 = endDetail.toolCalls || endStreamingChat.toolCalls;
|
|
37950
|
-
// Update chips from message options (same as chat.msg handler)
|
|
37951
|
-
|
|
38199
|
+
// Update chips from message options (same as chat.msg handler) — unless this closes
|
|
38200
|
+
// an intermediate bubble of a split turn, whose chips arrive with the final end.
|
|
38201
|
+
var chips = endDetail.preserveChips
|
|
38202
|
+
? state.chips
|
|
38203
|
+
: ((_w = finalMsg_1 === null || finalMsg_1 === void 0 ? void 0 : finalMsg_1.options) === null || _w === void 0 ? void 0 : _w.length) ? finalMsg_1.options : [];
|
|
37952
38204
|
return __assign$1(__assign$1({}, state), { lastTimestamp: endDetail === null || endDetail === void 0 ? void 0 : endDetail.timestamp, streamingMessageId: undefined, chips: chips, chats: state.chats.map(function (chat, index) {
|
|
37953
38205
|
if (index === streamingIndex_4 && isChatMsgDetail(chat)) {
|
|
37954
38206
|
return __assign$1(__assign$1({}, chat), { msg: finalMsg_1, isStreaming: false, wasStreamed: true, toolCalls: toolCalls_1 });
|