@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.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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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];
|
|
@@ -4644,8 +4764,8 @@ var MCPChat = /** @class */ (function () {
|
|
|
4644
4764
|
// Generic fallback
|
|
4645
4765
|
return "Something went wrong. Please try again.";
|
|
4646
4766
|
};
|
|
4647
|
-
MCPChat.prototype.dispatchStreamStart = function (messageId) {
|
|
4648
|
-
var bot = this.getBot(
|
|
4767
|
+
MCPChat.prototype.dispatchStreamStart = function (messageId, user) {
|
|
4768
|
+
var bot = this.getBot(user);
|
|
4649
4769
|
var streamTimestamp = +new Date();
|
|
4650
4770
|
log("[dispatchStreamStart] Stream started: ".concat(messageId, ", timestamp: ").concat(streamTimestamp));
|
|
4651
4771
|
this.dispatch({
|
|
@@ -4658,30 +4778,25 @@ var MCPChat = /** @class */ (function () {
|
|
|
4658
4778
|
},
|
|
4659
4779
|
});
|
|
4660
4780
|
};
|
|
4661
|
-
MCPChat.prototype.dispatchStreamChunk = function (messageId, text) {
|
|
4781
|
+
MCPChat.prototype.dispatchStreamChunk = function (messageId, text, user) {
|
|
4662
4782
|
this.dispatch({
|
|
4663
4783
|
type: "chat",
|
|
4664
4784
|
detail: {
|
|
4665
4785
|
type: "chat.stream.chunk",
|
|
4666
|
-
user: this.getBot(
|
|
4786
|
+
user: this.getBot(user),
|
|
4667
4787
|
messageId: messageId,
|
|
4668
4788
|
text: text,
|
|
4669
4789
|
timestamp: +new Date(),
|
|
4670
4790
|
},
|
|
4671
4791
|
});
|
|
4672
4792
|
};
|
|
4673
|
-
MCPChat.prototype.dispatchStreamEnd = function (messageId, fullMessage,
|
|
4793
|
+
MCPChat.prototype.dispatchStreamEnd = function (messageId, fullMessage, options) {
|
|
4794
|
+
if (options === void 0) { options = {}; }
|
|
4795
|
+
var toolCalls = options.toolCalls, user = options.user, preserveChips = options.preserveChips;
|
|
4674
4796
|
log("Stream ended: ".concat(messageId));
|
|
4675
4797
|
this.dispatch({
|
|
4676
4798
|
type: "chat",
|
|
4677
|
-
detail: {
|
|
4678
|
-
type: "chat.stream.end",
|
|
4679
|
-
user: this.getBot(undefined),
|
|
4680
|
-
messageId: messageId,
|
|
4681
|
-
msg: fullMessage,
|
|
4682
|
-
toolCalls: toolCalls,
|
|
4683
|
-
timestamp: +new Date(),
|
|
4684
|
-
},
|
|
4799
|
+
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() }),
|
|
4685
4800
|
});
|
|
4686
4801
|
};
|
|
4687
4802
|
MCPChat.prototype.dispatchStreamAbort = function (messageId) {
|
|
@@ -4791,6 +4906,19 @@ var MCPChat = /** @class */ (function () {
|
|
|
4791
4906
|
MCPChat.prototype.bufferStreamText = function (text) {
|
|
4792
4907
|
this.streamBuffer += text;
|
|
4793
4908
|
};
|
|
4909
|
+
/**
|
|
4910
|
+
* Dispatches whatever is buffered right now, without waiting on the throttle timer.
|
|
4911
|
+
*
|
|
4912
|
+
* Used when a bubble has to be closed mid-turn (`message_start`): the split has to be
|
|
4913
|
+
* synchronous, because frames arrive in order on one `onmessage` callback and deferring it
|
|
4914
|
+
* to a microtask would let the next `text` frame land in the bubble we are closing.
|
|
4915
|
+
*/
|
|
4916
|
+
MCPChat.prototype.flushStreamBufferSync = function () {
|
|
4917
|
+
if (this.streamBuffer.length > 0 && this.streamingMessageIdForThrottle) {
|
|
4918
|
+
this.dispatchStreamChunk(this.streamingMessageIdForThrottle, this.streamBuffer);
|
|
4919
|
+
this.streamBuffer = "";
|
|
4920
|
+
}
|
|
4921
|
+
};
|
|
4794
4922
|
/**
|
|
4795
4923
|
* Flushes any remaining buffered text and stops the throttle timer.
|
|
4796
4924
|
* Returns a promise that resolves when all buffered text has been dispatched.
|
|
@@ -5061,7 +5189,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5061
5189
|
return [2 /*return*/];
|
|
5062
5190
|
}
|
|
5063
5191
|
log("[attemptSSERequest] Sending message: \"".concat(messageText, "\", userId: ").concat(this._userId, ", sessionId: ").concat(this._sessionId));
|
|
5064
|
-
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 }))))];
|
|
5065
5193
|
});
|
|
5066
5194
|
});
|
|
5067
5195
|
};
|
|
@@ -5071,7 +5199,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5071
5199
|
*/
|
|
5072
5200
|
MCPChat.prototype.performSSERequest = function (body) {
|
|
5073
5201
|
return __awaiter$1(this, void 0, void 0, function () {
|
|
5074
|
-
var endpoint, messageId,
|
|
5202
|
+
var endpoint, messageId, bubbleId, bubbleUser, bubbleChunks, hasError, httpStatusCode, sseError;
|
|
5075
5203
|
var _this = this;
|
|
5076
5204
|
return __generator$1(this, function (_a) {
|
|
5077
5205
|
endpoint = this.getEndpoint();
|
|
@@ -5084,7 +5212,8 @@ var MCPChat = /** @class */ (function () {
|
|
|
5084
5212
|
this.abortController = new AbortController();
|
|
5085
5213
|
messageId = uuid_1();
|
|
5086
5214
|
this.currentMessageId = messageId;
|
|
5087
|
-
|
|
5215
|
+
bubbleId = messageId;
|
|
5216
|
+
bubbleChunks = [];
|
|
5088
5217
|
hasError = false;
|
|
5089
5218
|
return [2 /*return*/, new Promise(function (resolve, reject) {
|
|
5090
5219
|
// WARNING: Do not log headers object - it contains sensitive auth tokens
|
|
@@ -5115,6 +5244,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5115
5244
|
});
|
|
5116
5245
|
}); },
|
|
5117
5246
|
onmessage: function (event) {
|
|
5247
|
+
var _a;
|
|
5118
5248
|
// Guard: ignore events from stale/aborted requests
|
|
5119
5249
|
if (_this.currentMessageId !== messageId) {
|
|
5120
5250
|
log("[SSE] Ignoring stale event for messageId: ".concat(messageId, ", current: ").concat(_this.currentMessageId));
|
|
@@ -5141,10 +5271,15 @@ var MCPChat = /** @class */ (function () {
|
|
|
5141
5271
|
case "connected":
|
|
5142
5272
|
// Connection established
|
|
5143
5273
|
log("[SSE] Connected event received, messageId: ".concat(messageId, ", isReturningUser: ").concat(data.isReturningUser, ", isReplay: ").concat(data.isReplay));
|
|
5274
|
+
_this.conversationOnScreen = true;
|
|
5144
5275
|
// Track if this is a replay connection
|
|
5145
5276
|
if (data.isReplay) {
|
|
5146
5277
|
_this.isReplaying = true;
|
|
5147
5278
|
}
|
|
5279
|
+
// A server may name the participant up front.
|
|
5280
|
+
if (data.user) {
|
|
5281
|
+
bubbleUser = data.user;
|
|
5282
|
+
}
|
|
5148
5283
|
// Store identity from server response
|
|
5149
5284
|
if (data.userId || data.sessionId) {
|
|
5150
5285
|
if (data.userId) {
|
|
@@ -5164,18 +5299,35 @@ var MCPChat = /** @class */ (function () {
|
|
|
5164
5299
|
if (data.isReplay) {
|
|
5165
5300
|
// Replay connection — resume into the existing partial
|
|
5166
5301
|
// message instead of creating a duplicate placeholder
|
|
5167
|
-
_this.dispatchStreamResume(
|
|
5302
|
+
_this.dispatchStreamResume(bubbleId);
|
|
5168
5303
|
}
|
|
5169
5304
|
else {
|
|
5170
5305
|
// Normal connection — create new streaming placeholder
|
|
5171
|
-
_this.dispatchStreamStart(
|
|
5306
|
+
_this.dispatchStreamStart(bubbleId, bubbleUser);
|
|
5307
|
+
}
|
|
5308
|
+
_this.startStreamThrottle(bubbleId);
|
|
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));
|
|
5172
5322
|
}
|
|
5173
|
-
|
|
5323
|
+
storeIdentity(data.userId, data.sessionId);
|
|
5324
|
+
_this.stopTyping();
|
|
5174
5325
|
break;
|
|
5175
5326
|
case "session_expired":
|
|
5176
5327
|
// Session no longer exists on server - need to start fresh
|
|
5177
5328
|
log("[SSE] Session expired: ".concat(data.reason || "Session not found"));
|
|
5178
5329
|
// Clear stored session (and lastSeq) but keep userId for returning user identification
|
|
5330
|
+
_this.conversationOnScreen = false;
|
|
5179
5331
|
clearStoredSession(_this._sessionId);
|
|
5180
5332
|
_this._sessionId = "";
|
|
5181
5333
|
_this.lastSeq = undefined;
|
|
@@ -5188,14 +5340,40 @@ var MCPChat = /** @class */ (function () {
|
|
|
5188
5340
|
_this.initializeNewSession();
|
|
5189
5341
|
resolve();
|
|
5190
5342
|
break;
|
|
5343
|
+
case "message_start": {
|
|
5344
|
+
// Close the bubble being streamed and open a new one, optionally
|
|
5345
|
+
// under a different participant. This is the only way a turn can
|
|
5346
|
+
// render as more than one message: the reducer creates a fresh
|
|
5347
|
+
// bubble per `chat.stream.start` and finalizes any still-streaming
|
|
5348
|
+
// one, so a server can make a handoff look like someone joining.
|
|
5349
|
+
// Servers that never send this are unaffected.
|
|
5350
|
+
log("[SSE] message_start \u2014 splitting bubble, user: ".concat((_a = data.user) === null || _a === void 0 ? void 0 : _a.displayName));
|
|
5351
|
+
_this.flushStreamBufferSync();
|
|
5352
|
+
_this.dispatchStreamEnd(bubbleId, { text: bubbleChunks.join("") }, {
|
|
5353
|
+
// Same debug-mode capture as `done`, scoped to this bubble:
|
|
5354
|
+
// stream.end replaces a bubble's toolCalls wholesale, so any
|
|
5355
|
+
// left in currentToolCalls would reappear on the next one.
|
|
5356
|
+
toolCalls: (_this.config.debugMode || _this._debugMode) ? __spreadArray$1([], _this.currentToolCalls, true) : undefined,
|
|
5357
|
+
user: bubbleUser,
|
|
5358
|
+
// Chips belong to the turn; its final end sets them.
|
|
5359
|
+
preserveChips: true,
|
|
5360
|
+
});
|
|
5361
|
+
_this.currentToolCalls = [];
|
|
5362
|
+
bubbleId = uuid_1();
|
|
5363
|
+
bubbleChunks = [];
|
|
5364
|
+
bubbleUser = data.user;
|
|
5365
|
+
_this.dispatchStreamStart(bubbleId, bubbleUser);
|
|
5366
|
+
_this.startStreamThrottle(bubbleId);
|
|
5367
|
+
break;
|
|
5368
|
+
}
|
|
5191
5369
|
case "text":
|
|
5192
5370
|
if (data.text) {
|
|
5193
|
-
log("[SSE] Text chunk received (".concat(data.text.length, " chars),
|
|
5194
|
-
|
|
5371
|
+
log("[SSE] Text chunk received (".concat(data.text.length, " chars), chunks in this bubble: ").concat(bubbleChunks.length + 1, ", isReplay: ").concat(data.isReplay));
|
|
5372
|
+
bubbleChunks.push(data.text);
|
|
5195
5373
|
if (data.isReplay || _this.isSleeping) {
|
|
5196
5374
|
// Replayed text or tab hidden — dispatch immediately
|
|
5197
5375
|
// (no throttle timer running in either case)
|
|
5198
|
-
_this.dispatchStreamChunk(
|
|
5376
|
+
_this.dispatchStreamChunk(bubbleId, data.text, bubbleUser);
|
|
5199
5377
|
}
|
|
5200
5378
|
else {
|
|
5201
5379
|
// Live text — buffer for throttled dispatch
|
|
@@ -5219,7 +5397,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5219
5397
|
_this.currentToolCalls.push(toolCall);
|
|
5220
5398
|
// Always dispatch tool start for user-friendly progress display
|
|
5221
5399
|
_this.dispatchToolStart({
|
|
5222
|
-
messageId:
|
|
5400
|
+
messageId: bubbleId,
|
|
5223
5401
|
toolName: data.toolName,
|
|
5224
5402
|
toolCallId: data.toolCallId,
|
|
5225
5403
|
displayName: data.displayName,
|
|
@@ -5260,7 +5438,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5260
5438
|
}
|
|
5261
5439
|
// Always dispatch tool end for user-friendly progress display
|
|
5262
5440
|
_this.dispatchToolEnd({
|
|
5263
|
-
messageId:
|
|
5441
|
+
messageId: bubbleId,
|
|
5264
5442
|
toolName: data.toolName,
|
|
5265
5443
|
toolCallId: data.toolCallId,
|
|
5266
5444
|
displayName: data.displayName,
|
|
@@ -5282,8 +5460,10 @@ var MCPChat = /** @class */ (function () {
|
|
|
5282
5460
|
storeIdentity(undefined, data.response.sessionId);
|
|
5283
5461
|
}
|
|
5284
5462
|
// Wait for buffer to flush, then finalize
|
|
5285
|
-
|
|
5286
|
-
|
|
5463
|
+
// The bubble's own text, not the whole turn's — a turn split by
|
|
5464
|
+
// `message_start` has already committed its earlier bubbles.
|
|
5465
|
+
var accumulatedText = bubbleChunks.join("");
|
|
5466
|
+
log("[SSE] Done event received. Chunks in this bubble: ".concat(bubbleChunks.length, ", accumulated text length: ").concat(accumulatedText.length));
|
|
5287
5467
|
log("[SSE] First 100 chars of accumulated: ".concat(accumulatedText.substring(0, 100)));
|
|
5288
5468
|
// Map suggestions to options (chips) format
|
|
5289
5469
|
var suggestions = data.response.suggestions;
|
|
@@ -5299,8 +5479,14 @@ var MCPChat = /** @class */ (function () {
|
|
|
5299
5479
|
if (sources === null || sources === void 0 ? void 0 : sources.length) {
|
|
5300
5480
|
log("[SSE] Sources: ".concat(sources.length, " items"));
|
|
5301
5481
|
}
|
|
5482
|
+
// `response.response` is request-scoped. For an unsplit turn it is
|
|
5483
|
+
// the whole answer, and the fallback serves servers that deliver it
|
|
5484
|
+
// only via `done`. Once `message_start` has fired it would copy every
|
|
5485
|
+
// earlier bubble's text into this one, so a split turn's bubbles
|
|
5486
|
+
// must stream their text as `text` frames.
|
|
5487
|
+
var isSplitTurn = bubbleId !== messageId;
|
|
5302
5488
|
var finalMessage_1 = {
|
|
5303
|
-
text: accumulatedText || data.response.response || "",
|
|
5489
|
+
text: accumulatedText || (isSplitTurn ? "" : data.response.response) || "",
|
|
5304
5490
|
options: options,
|
|
5305
5491
|
sources: sources,
|
|
5306
5492
|
displays: data.response.displays,
|
|
@@ -5308,7 +5494,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5308
5494
|
// Capture tool calls before flushing (debug mode — config or runtime toggle)
|
|
5309
5495
|
var toolCalls_1 = (_this.config.debugMode || _this._debugMode) ? __spreadArray$1([], _this.currentToolCalls, true) : undefined;
|
|
5310
5496
|
_this.flushStreamBuffer().then(function () {
|
|
5311
|
-
_this.dispatchStreamEnd(
|
|
5497
|
+
_this.dispatchStreamEnd(bubbleId, finalMessage_1, { toolCalls: toolCalls_1, user: bubbleUser });
|
|
5312
5498
|
_this.currentToolCalls = []; // Clear after dispatch
|
|
5313
5499
|
resolve();
|
|
5314
5500
|
});
|
|
@@ -5348,7 +5534,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5348
5534
|
log("[MCPChat] SSE error during sleep - will request replay on wakeup");
|
|
5349
5535
|
_this.needsReplayOnWakeup = true;
|
|
5350
5536
|
if (_this.streamingMessageIdForThrottle) {
|
|
5351
|
-
_this.dispatchStreamAbort(
|
|
5537
|
+
_this.dispatchStreamAbort(bubbleId);
|
|
5352
5538
|
}
|
|
5353
5539
|
_this.stopStreamThrottle();
|
|
5354
5540
|
}
|
|
@@ -5392,6 +5578,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
5392
5578
|
MCPChat.prototype.setVisitorInfo = function (visitorInfo, sessionId, cb) {
|
|
5393
5579
|
this.visitorInfo = visitorInfo;
|
|
5394
5580
|
this._attributes = __assign$1({}, this.visitorInfo.attributes);
|
|
5581
|
+
this.conversationOnScreen = visitorInfo.hasConversation === true;
|
|
5395
5582
|
// do not set currentUrl if localhost
|
|
5396
5583
|
var href = new URL(window.location.href);
|
|
5397
5584
|
if (href === null || href === void 0 ? void 0 : href.host.toLowerCase().startsWith("localhost")) {
|
|
@@ -12906,7 +13093,21 @@ var ChatTextMessage = function (props) {
|
|
|
12906
13093
|
var wasStreamed = message.wasStreamed;
|
|
12907
13094
|
var showTail = agentMessage && !props.sibling && !isStreaming && !wasStreamed;
|
|
12908
13095
|
var hasToolCalls = props.toolCalls && props.toolCalls.length > 0;
|
|
12909
|
-
|
|
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" })] }) })] }));
|
|
12910
13111
|
};
|
|
12911
13112
|
|
|
12912
13113
|
var ChatScheduleWidget = function (props) {
|
|
@@ -12949,14 +13150,21 @@ var avaKeys = ["text", "html", "card", "list"];
|
|
|
12949
13150
|
* @returns
|
|
12950
13151
|
*/
|
|
12951
13152
|
var ChatMessage = function (props) {
|
|
12952
|
-
var _a, _b, _c;
|
|
13153
|
+
var _a, _b, _c, _d;
|
|
12953
13154
|
var middleware = props.messageMiddleware || StandardMiddlewares;
|
|
12954
13155
|
var chatConfig = React.useContext(ChatConfigContext);
|
|
12955
13156
|
// console.log(`########### chatConfig: ${JSON.stringify(chatConfig, null, 2)}`);
|
|
12956
13157
|
var ctx = props.middlewareContext;
|
|
12957
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;
|
|
12958
13166
|
// Get hideUserInfo from the agent info based on the message sender's nick
|
|
12959
|
-
var agentInfo = (
|
|
13167
|
+
var agentInfo = (_b = props.agents) === null || _b === void 0 ? void 0 : _b[props.message.user.nick];
|
|
12960
13168
|
var hideUserInfo = (agentInfo === null || agentInfo === void 0 ? void 0 : agentInfo.hideUserInfo) || false;
|
|
12961
13169
|
function renderByType() {
|
|
12962
13170
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
|
|
@@ -12975,7 +13183,7 @@ var ChatMessage = function (props) {
|
|
|
12975
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;
|
|
12976
13184
|
// Show tool progress with avatar when no text yet, or above text when text exists
|
|
12977
13185
|
var showToolProgressWithAvatar = hasToolCalls && !msg.text;
|
|
12978
|
-
return (require$$0.jsxs(require$$0.Fragment, { children: [showToolProgressWithAvatar && (require$$0.jsx(ChatMessagePart, { showAvatar: true, user: user, avatarPosition: (_g = (_f = (_e = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _e === void 0 ? void 0 : _e.theme) === null || _f === void 0 ? void 0 : _f.messages) === null || _g === void 0 ? void 0 : _g.avatarPosition, hideUserInfo: hideUserInfo, isStreaming: true, children: require$$0.jsx("div", { className: "chat-msg", children: require$$0.jsx(ToolCallDisplay, { toolCalls: toolCalls, debugMode: debugMode }) }) })), msg.text && (require$$0.jsx(ChatMessagePart, { showAvatar: avaKey === "text", user: user, avatarPosition: (_k = (_j = (_h = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.env) === null || _h === void 0 ? void 0 : _h.theme) === null || _j === void 0 ? void 0 : _j.messages) === null || _k === void 0 ? void 0 : _k.avatarPosition, hideUserInfo: hideUserInfo, isStreaming: isStreaming_1 || wasStreamed, children: require$$0.jsx(ChatTextMessage, { message: props.message, sibling: props.sibling, user: user, toolCalls: hasToolCalls ? toolCalls : undefined, debugMode: debugMode }) })), msg.html &&
|
|
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 &&
|
|
12979
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) {
|
|
12980
13188
|
if (display.type === "ScheduleButton") {
|
|
12981
13189
|
return (require$$0.jsx(ChatScheduleWidget, { minimizeOnClick: props.minimizeOnClick, display: display }, index));
|
|
@@ -12999,7 +13207,7 @@ var ChatMessage = function (props) {
|
|
|
12999
13207
|
}
|
|
13000
13208
|
// Don't show timestamp/sources while streaming or if message has no content yet
|
|
13001
13209
|
var isStreaming = props.message.isStreaming;
|
|
13002
|
-
var hasContent = !!(((
|
|
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));
|
|
13003
13211
|
var showFooter = !isStreaming && hasContent;
|
|
13004
13212
|
// empty
|
|
13005
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()] }) }));
|
|
@@ -13009,9 +13217,6 @@ var ChatMessage = function (props) {
|
|
|
13009
13217
|
* value must be like "60m", "2h" etc.
|
|
13010
13218
|
*/
|
|
13011
13219
|
function convertToSeconds(s) {
|
|
13012
|
-
if (!s) {
|
|
13013
|
-
return 0;
|
|
13014
|
-
}
|
|
13015
13220
|
var groups = s.match(/(\d+)([dhms]?)/);
|
|
13016
13221
|
if (!groups) {
|
|
13017
13222
|
err("Invalid time string: ".concat(s));
|
|
@@ -13034,15 +13239,50 @@ function convertToSeconds(s) {
|
|
|
13034
13239
|
}
|
|
13035
13240
|
}
|
|
13036
13241
|
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
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) {
|
|
13041
13263
|
return false;
|
|
13042
13264
|
}
|
|
13043
|
-
|
|
13044
|
-
|
|
13045
|
-
|
|
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;
|
|
13046
13286
|
}
|
|
13047
13287
|
|
|
13048
13288
|
function useExternalScript(url) {
|
|
@@ -13163,6 +13403,7 @@ function useGreeting(active) {
|
|
|
13163
13403
|
// Handle null context (e.g., in preview mode)
|
|
13164
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;
|
|
13165
13405
|
var sessionId = reactRedux.useSelector(function (state) { return state.sessionId; });
|
|
13406
|
+
var conversationOnScreen = reactRedux.useSelector(function (state) { return hasConversation(state.chats); });
|
|
13166
13407
|
React.useEffect(function () {
|
|
13167
13408
|
if (active) {
|
|
13168
13409
|
if (snapshotRef.current !== curr) {
|
|
@@ -13173,7 +13414,8 @@ function useGreeting(active) {
|
|
|
13173
13414
|
visitorId: curr.visitorId,
|
|
13174
13415
|
userId: curr.userId,
|
|
13175
13416
|
accessToken: curr.accessToken,
|
|
13176
|
-
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
|
|
13177
13419
|
}, sessionId));
|
|
13178
13420
|
if (!isAdmin) {
|
|
13179
13421
|
var timeoutId_1 = setTimeout(function () {
|
|
@@ -37029,7 +37271,7 @@ var ChatWidgetConnected = function (props) {
|
|
|
37029
37271
|
* Exported for use by StaticChatWidgetContainer to avoid infinite loops.
|
|
37030
37272
|
*/
|
|
37031
37273
|
var ChatWidgetUI = function (props) {
|
|
37032
|
-
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;
|
|
37033
37275
|
var innerDispatch = useChatDispatch();
|
|
37034
37276
|
var dispatch = useChatServerDispatch();
|
|
37035
37277
|
// Publish the visual viewport's geometry so the panel can size around the soft keyboard.
|
|
@@ -37050,14 +37292,18 @@ var ChatWidgetUI = function (props) {
|
|
|
37050
37292
|
// Preview mode can be enabled via `preview` prop (recommended) or legacy `mode="preview"`
|
|
37051
37293
|
var previewMode = props.preview === true || mode === "preview";
|
|
37052
37294
|
var modeClass = "widget-container--".concat(mode);
|
|
37053
|
-
|
|
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;
|
|
37054
37300
|
// can't minimize in docked mode, static mode, or preview mode.
|
|
37055
|
-
var canMinimize = !dockedMode && !staticMode && !previewMode && ((
|
|
37056
|
-
log("docked: ".concat(dockedMode, " static: ").concat(staticMode, " preview: ").concat(previewMode, " minimized: ").concat((
|
|
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));
|
|
37057
37303
|
var canCancel;
|
|
37058
37304
|
// To preserve legacy behavior, cancel needs a little more checks
|
|
37059
|
-
if (typeof ((
|
|
37060
|
-
canCancel = !dockedMode && !staticMode && !previewMode && ((
|
|
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);
|
|
37061
37307
|
}
|
|
37062
37308
|
else {
|
|
37063
37309
|
canCancel = !dockedMode && !staticMode && !previewMode;
|
|
@@ -37068,16 +37314,16 @@ var ChatWidgetUI = function (props) {
|
|
|
37068
37314
|
}
|
|
37069
37315
|
// Our state - pull from storage
|
|
37070
37316
|
// Parse autoOpenOnWidth once for use in both initial state and useEffect
|
|
37071
|
-
var autoOpenOnWidthValue = ((
|
|
37317
|
+
var autoOpenOnWidthValue = ((_o = props.config) === null || _o === void 0 ? void 0 : _o.autoOpenOnWidth)
|
|
37072
37318
|
? parseInt(props.config.autoOpenOnWidth, 10)
|
|
37073
37319
|
: NaN;
|
|
37074
37320
|
var autoOpenOnWidthMatch = !isNaN(autoOpenOnWidthValue) && window.innerWidth >= autoOpenOnWidthValue;
|
|
37075
37321
|
// When autoOpenOnWidth is configured, use width check instead of persisted visible state
|
|
37076
37322
|
// to prevent greeting requests on viewports below the threshold
|
|
37077
|
-
var
|
|
37323
|
+
var _15 = React.useState((!canMinimize && !canCancel) ||
|
|
37078
37324
|
// !!get("visible") ||
|
|
37079
|
-
(!isNaN(autoOpenOnWidthValue) ? autoOpenOnWidthMatch : chatState.visuals.visible)), visible =
|
|
37080
|
-
var
|
|
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
|
|
37081
37327
|
// Ref to track typing state for use in timeout callbacks
|
|
37082
37328
|
var typingRef = React.useRef(false);
|
|
37083
37329
|
// Timeout ref for debouncing "stop typing" events
|
|
@@ -37085,11 +37331,11 @@ var ChatWidgetUI = function (props) {
|
|
|
37085
37331
|
// Track whether autoOpenOnWidth has already fired so it doesn't re-open after user closes
|
|
37086
37332
|
var autoOpenFiredRef = React.useRef(autoOpenOnWidthMatch);
|
|
37087
37333
|
var chatServer = React.useContext(ChatServerContext);
|
|
37088
|
-
var patternsConfig = (
|
|
37334
|
+
var patternsConfig = (_q = (_p = props.config) === null || _p === void 0 ? void 0 : _p.autoOpenOnPattern) === null || _q === void 0 ? void 0 : _q.patterns;
|
|
37089
37335
|
var currentUrl = window.location.href;
|
|
37090
37336
|
var patternExist = patternsConfig && patternsConfig.length > 0;
|
|
37091
37337
|
var patternMatches = patternsConfig === null || patternsConfig === void 0 ? void 0 : patternsConfig.some(function (pattern) { return currentUrl.includes(pattern); });
|
|
37092
|
-
var configWidth = (
|
|
37338
|
+
var configWidth = (_s = (_r = props.config) === null || _r === void 0 ? void 0 : _r.autoOpenOnPattern) === null || _s === void 0 ? void 0 : _s.minimumWidth;
|
|
37093
37339
|
var currentWidth = window.innerWidth;
|
|
37094
37340
|
var setVisible = React.useCallback(function (newVisible) {
|
|
37095
37341
|
if (staticMode || previewMode) {
|
|
@@ -37124,7 +37370,7 @@ var ChatWidgetUI = function (props) {
|
|
|
37124
37370
|
};
|
|
37125
37371
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
37126
37372
|
}, []);
|
|
37127
|
-
var
|
|
37373
|
+
var _17 = React.useState(!document.hidden), isTabVisible = _17[0], setIsTabVisible = _17[1];
|
|
37128
37374
|
React.useEffect(function () {
|
|
37129
37375
|
var handleVisibilityChange = function () {
|
|
37130
37376
|
setIsTabVisible(!document.hidden);
|
|
@@ -37348,8 +37594,8 @@ var ChatWidgetUI = function (props) {
|
|
|
37348
37594
|
}));
|
|
37349
37595
|
}
|
|
37350
37596
|
// Action Bar state and handlers
|
|
37351
|
-
var
|
|
37352
|
-
var
|
|
37597
|
+
var _18 = React.useState(false), formModalOpen = _18[0], setFormModalOpen = _18[1];
|
|
37598
|
+
var _19 = React.useState(null), activeFormConfig = _19[0], setActiveFormConfig = _19[1];
|
|
37353
37599
|
var handleFormButtonClick = React.useCallback(function (formButton) {
|
|
37354
37600
|
// If form-widget is configured, use its openForm API
|
|
37355
37601
|
// The form-widget script should already be pre-loaded
|
|
@@ -37416,7 +37662,7 @@ var ChatWidgetUI = function (props) {
|
|
|
37416
37662
|
var messages = chatState && chatState.chats;
|
|
37417
37663
|
log("Rendering - accountStatus: \"".concat(chatState.accountStatus, "\", connectionStatus: \"").concat(chatState.connection.connectionStatus, "\""));
|
|
37418
37664
|
var config = props.config, onConnectionStatusChange = props.onConnectionStatusChange;
|
|
37419
|
-
var actionBarEnabled = ((
|
|
37665
|
+
var actionBarEnabled = ((_t = config === null || config === void 0 ? void 0 : config.actionBar) === null || _t === void 0 ? void 0 : _t.enabled) === true;
|
|
37420
37666
|
// The host page declares this; the widget cannot work it out. On the standalone
|
|
37421
37667
|
// contact/booking page the form already *is* the page, which makes every button that
|
|
37422
37668
|
// only opens one redundant. XappMedia/chat-widget#1626.
|
|
@@ -37440,7 +37686,7 @@ var ChatWidgetUI = function (props) {
|
|
|
37440
37686
|
onConnectionStatusChange(connectionStatus);
|
|
37441
37687
|
}
|
|
37442
37688
|
}, [connectionStatus, onConnectionStatusChange]);
|
|
37443
|
-
useExternalScript((
|
|
37689
|
+
useExternalScript((_u = props.config) === null || _u === void 0 ? void 0 : _u.middlewareUrl);
|
|
37444
37690
|
// Only reach for form.xapp.ai when something on this page can still open the form.
|
|
37445
37691
|
//
|
|
37446
37692
|
// Loading it is not free: the script mounts a second form-widget instance whose wrapper
|
|
@@ -37480,15 +37726,18 @@ var ChatWidgetUI = function (props) {
|
|
|
37480
37726
|
};
|
|
37481
37727
|
}, [actionBarVisible]);
|
|
37482
37728
|
// This is a pseudo agent. It represent's the widget (shown in the header avatar for instance)
|
|
37483
|
-
var widgetAgent = ((
|
|
37729
|
+
var widgetAgent = ((_v = chatState.agents["agent:robot"]) === null || _v === void 0 ? void 0 : _v.user) ||
|
|
37484
37730
|
(config === null || config === void 0 ? void 0 : config.agent) || {
|
|
37485
37731
|
nick: "agent:robot",
|
|
37486
37732
|
avatarPath: config.avatarUrl,
|
|
37487
37733
|
display_name: "Agent",
|
|
37488
37734
|
};
|
|
37489
|
-
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: (
|
|
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), {
|
|
37490
37736
|
// Use actionBar.cta if set, otherwise fall back to widget-level cta
|
|
37491
|
-
cta: (
|
|
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 })] }));
|
|
37492
37741
|
};
|
|
37493
37742
|
/**
|
|
37494
37743
|
* Top-level wrapper that dispatches between preview mode and connected mode.
|
|
@@ -37948,8 +38197,11 @@ function update(state, action) {
|
|
|
37948
38197
|
var finalMsg_1 = endDetail.msg;
|
|
37949
38198
|
// Preserve tool calls from either the action or the existing message
|
|
37950
38199
|
var toolCalls_1 = endDetail.toolCalls || endStreamingChat.toolCalls;
|
|
37951
|
-
// Update chips from message options (same as chat.msg handler)
|
|
37952
|
-
|
|
38200
|
+
// Update chips from message options (same as chat.msg handler) — unless this closes
|
|
38201
|
+
// an intermediate bubble of a split turn, whose chips arrive with the final end.
|
|
38202
|
+
var chips = endDetail.preserveChips
|
|
38203
|
+
? state.chips
|
|
38204
|
+
: ((_w = finalMsg_1 === null || finalMsg_1 === void 0 ? void 0 : finalMsg_1.options) === null || _w === void 0 ? void 0 : _w.length) ? finalMsg_1.options : [];
|
|
37953
38205
|
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) {
|
|
37954
38206
|
if (index === streamingIndex_4 && isChatMsgDetail(chat)) {
|
|
37955
38207
|
return __assign$1(__assign$1({}, chat), { msg: finalMsg_1, isStreaming: false, wasStreamed: true, toolCalls: toolCalls_1 });
|