@xapp/chat-widget 1.90.0 → 1.92.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/components/Table/Table.d.ts +0 -0
- package/dist/components/Table/index.d.ts +0 -0
- package/dist/index.css +1 -1
- package/dist/index.es.js +485 -84
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +485 -84
- package/dist/index.js.map +1 -1
- package/dist/middlewares/table-middleware.d.ts +0 -0
- package/dist/store/ChatAction.d.ts +0 -0
- package/dist/utils/guards.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 +4 -4
- package/dist/xapp-chat-widget.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -2993,7 +2993,9 @@ function setSessionId(sessionId) {
|
|
|
2993
2993
|
|
|
2994
2994
|
// Endpoint path constants
|
|
2995
2995
|
var HOMEOWNER_ENDPOINT_PATH = "/chat/stream";
|
|
2996
|
+
var HOMEOWNER_CLOSE_ENDPOINT_PATH = "/chat/close";
|
|
2996
2997
|
var BUSINESS_OWNER_ENDPOINT_PATH = "/api/chat/stream";
|
|
2998
|
+
var BUSINESS_OWNER_CLOSE_ENDPOINT_PATH = "/api/chat/close";
|
|
2997
2999
|
// Default retry configuration for MCP connections
|
|
2998
3000
|
var DEFAULT_MCP_RETRY_CONFIG = [
|
|
2999
3001
|
{ retry: 0, delay: 0, text: "" }, // Initial attempt (silent)
|
|
@@ -3147,6 +3149,22 @@ function clearStoredLastSeq(sessionId) {
|
|
|
3147
3149
|
// sessionStorage may be unavailable
|
|
3148
3150
|
}
|
|
3149
3151
|
}
|
|
3152
|
+
/**
|
|
3153
|
+
* Returns the current local time as an ISO 8601 string with the local timezone offset.
|
|
3154
|
+
* Unlike toISOString(), this preserves the user's local time zone (e.g. "2024-01-15T14:30:00-04:00")
|
|
3155
|
+
* so the agent server can correctly determine time-of-day context (morning greetings, etc.).
|
|
3156
|
+
*/
|
|
3157
|
+
function getLocalISOString(date) {
|
|
3158
|
+
if (date === void 0) { date = new Date(); }
|
|
3159
|
+
var offsetMinutes = -date.getTimezoneOffset();
|
|
3160
|
+
var sign = offsetMinutes >= 0 ? "+" : "-";
|
|
3161
|
+
var pad = function (n) { return String(Math.abs(n)).padStart(2, "0"); };
|
|
3162
|
+
var offsetHours = Math.floor(Math.abs(offsetMinutes) / 60);
|
|
3163
|
+
var offsetMins = Math.abs(offsetMinutes) % 60;
|
|
3164
|
+
return ("".concat(date.getFullYear(), "-").concat(pad(date.getMonth() + 1), "-").concat(pad(date.getDate())) +
|
|
3165
|
+
"T".concat(pad(date.getHours()), ":").concat(pad(date.getMinutes()), ":").concat(pad(date.getSeconds())) +
|
|
3166
|
+
"".concat(sign).concat(pad(offsetHours), ":").concat(pad(offsetMins)));
|
|
3167
|
+
}
|
|
3150
3168
|
/**
|
|
3151
3169
|
* Gets the current page context from browser globals.
|
|
3152
3170
|
* If an override is provided, uses that instead (useful for testing).
|
|
@@ -3155,7 +3173,7 @@ function clearStoredLastSeq(sessionId) {
|
|
|
3155
3173
|
* may contain sensitive data (tokens, PII). Only origin + pathname are sent.
|
|
3156
3174
|
*/
|
|
3157
3175
|
function getPageContext(override) {
|
|
3158
|
-
var localTime =
|
|
3176
|
+
var localTime = getLocalISOString();
|
|
3159
3177
|
// If override provided, merge with defaults (override wins)
|
|
3160
3178
|
// Overrides are typically for testing and may include full URLs
|
|
3161
3179
|
if (override && Object.keys(override).length > 0) {
|
|
@@ -3195,9 +3213,11 @@ function getPageContext(override) {
|
|
|
3195
3213
|
}
|
|
3196
3214
|
var MCPChat = /** @class */ (function () {
|
|
3197
3215
|
function MCPChat(config, options) {
|
|
3216
|
+
var _a;
|
|
3198
3217
|
this._userId = "";
|
|
3199
3218
|
this._sessionId = "";
|
|
3200
3219
|
this._accessToken = "";
|
|
3220
|
+
this._debugMode = false;
|
|
3201
3221
|
this._attributes = {};
|
|
3202
3222
|
// Streaming throttle state
|
|
3203
3223
|
this.streamBuffer = "";
|
|
@@ -3207,16 +3227,20 @@ var MCPChat = /** @class */ (function () {
|
|
|
3207
3227
|
this.isValidatingSession = false;
|
|
3208
3228
|
// Tool call tracking for debug mode
|
|
3209
3229
|
this.currentToolCalls = [];
|
|
3210
|
-
// TODO: isReplaying and lastSeq are tracked for future replay support.
|
|
3211
|
-
// Wire up replay requests in wakeup() once the server endpoint supports it.
|
|
3212
3230
|
this.isReplaying = false;
|
|
3231
|
+
// Sleep/wakeup state for tab backgrounding recovery
|
|
3232
|
+
this.isSleeping = false;
|
|
3233
|
+
this.isRequestingReplay = false;
|
|
3234
|
+
this.needsReplayOnWakeup = false;
|
|
3213
3235
|
// True when userId was assigned by the server (via connected SSE event),
|
|
3214
3236
|
// meaning it should not be overwritten by the default client fingerprint.
|
|
3215
3237
|
this.userIdIsServerAssigned = false;
|
|
3238
|
+
this.sentMessageCount = 0;
|
|
3216
3239
|
this.config = config;
|
|
3217
3240
|
this.options = options;
|
|
3218
3241
|
this.configurableMessages = options === null || options === void 0 ? void 0 : options.configurableMessages;
|
|
3219
3242
|
this.isNewSession = false;
|
|
3243
|
+
this._debugMode = (_a = config.debugMode) !== null && _a !== void 0 ? _a : false;
|
|
3220
3244
|
this.hooks = options === null || options === void 0 ? void 0 : options.hooks;
|
|
3221
3245
|
// Validate business_owner mode requires authToken
|
|
3222
3246
|
var mode = config.mode || "homeowner";
|
|
@@ -3248,6 +3272,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
3248
3272
|
}
|
|
3249
3273
|
MCPChat.prototype.init = function (dispatch) {
|
|
3250
3274
|
this.dispatch = dispatch;
|
|
3275
|
+
this.sessionStartTime = Date.now();
|
|
3251
3276
|
var mode = this.config.mode || "homeowner";
|
|
3252
3277
|
log("MCPChat initialized with mode: ".concat(mode));
|
|
3253
3278
|
// Warn if not using HTTPS with authentication
|
|
@@ -3259,14 +3284,8 @@ var MCPChat = /** @class */ (function () {
|
|
|
3259
3284
|
// Dispatch bot joined early so it appears before the greeting.
|
|
3260
3285
|
// getBot() is config-derived (uses options.bot), so it's safe to call before setVisitorInfo.
|
|
3261
3286
|
this.userJoined(this.getBot(undefined));
|
|
3262
|
-
//
|
|
3263
|
-
//
|
|
3264
|
-
if (this._sessionId) {
|
|
3265
|
-
this.validateSession();
|
|
3266
|
-
}
|
|
3267
|
-
else {
|
|
3268
|
-
this.initializeNewSession();
|
|
3269
|
-
}
|
|
3287
|
+
// Session initialization is deferred to setVisitorInfo() to avoid
|
|
3288
|
+
// making network requests before the widget is visible.
|
|
3270
3289
|
};
|
|
3271
3290
|
/**
|
|
3272
3291
|
* Validates an existing session with the server ("Silent Handshake").
|
|
@@ -3320,7 +3339,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
3320
3339
|
*/
|
|
3321
3340
|
MCPChat.prototype.buildRequestPayload = function (additionalData) {
|
|
3322
3341
|
if (additionalData === void 0) { additionalData = {}; }
|
|
3323
|
-
return __assign({
|
|
3342
|
+
return __assign(__assign({
|
|
3324
3343
|
// Identity - server generates these, we just send back what we have
|
|
3325
3344
|
userId: this._userId || undefined, sessionId: this._sessionId || undefined,
|
|
3326
3345
|
// Fingerprint for user identification (can be disabled for privacy)
|
|
@@ -3328,7 +3347,9 @@ var MCPChat = /** @class */ (function () {
|
|
|
3328
3347
|
// Page context
|
|
3329
3348
|
pageContext: getPageContext(this.config.pageContextOverride),
|
|
3330
3349
|
// Client timestamp for latency measurement and clock drift detection
|
|
3331
|
-
clientTime: new Date().toISOString()
|
|
3350
|
+
clientTime: new Date().toISOString(),
|
|
3351
|
+
// Last sequence number for replay on reconnect
|
|
3352
|
+
lastSeq: this.lastSeq }, (this._debugMode && { debug: true })), additionalData);
|
|
3332
3353
|
};
|
|
3333
3354
|
/**
|
|
3334
3355
|
* Initializes a new session by calling the endpoint with { newSession: true }.
|
|
@@ -3338,9 +3359,12 @@ var MCPChat = /** @class */ (function () {
|
|
|
3338
3359
|
return __awaiter$1(this, void 0, void 0, function () {
|
|
3339
3360
|
var endpoint, error_2;
|
|
3340
3361
|
var _this = this;
|
|
3341
|
-
|
|
3342
|
-
|
|
3362
|
+
var _a;
|
|
3363
|
+
return __generator$1(this, function (_b) {
|
|
3364
|
+
switch (_b.label) {
|
|
3343
3365
|
case 0:
|
|
3366
|
+
// Reset debug mode on new session (respects config default)
|
|
3367
|
+
this._debugMode = (_a = this.config.debugMode) !== null && _a !== void 0 ? _a : false;
|
|
3344
3368
|
// Prevent multiple concurrent initialization requests
|
|
3345
3369
|
if (this.isInitializingSession) {
|
|
3346
3370
|
log("New session initialization already in progress, skipping");
|
|
@@ -3354,15 +3378,15 @@ var MCPChat = /** @class */ (function () {
|
|
|
3354
3378
|
_this.resolveSessionInit = resolve;
|
|
3355
3379
|
});
|
|
3356
3380
|
this.typing();
|
|
3357
|
-
|
|
3381
|
+
_b.label = 1;
|
|
3358
3382
|
case 1:
|
|
3359
|
-
|
|
3383
|
+
_b.trys.push([1, 3, 4, 5]);
|
|
3360
3384
|
return [4 /*yield*/, this.performSSERequest(this.buildRequestPayload({ newSession: true, generateGreeting: true }))];
|
|
3361
3385
|
case 2:
|
|
3362
|
-
|
|
3386
|
+
_b.sent();
|
|
3363
3387
|
return [3 /*break*/, 5];
|
|
3364
3388
|
case 3:
|
|
3365
|
-
error_2 =
|
|
3389
|
+
error_2 = _b.sent();
|
|
3366
3390
|
err("Failed to initialize new session: ".concat(error_2));
|
|
3367
3391
|
this.stopTyping();
|
|
3368
3392
|
this.setConnectionStatus("offline");
|
|
@@ -3401,6 +3425,14 @@ var MCPChat = /** @class */ (function () {
|
|
|
3401
3425
|
// homeowner mode - serverUrl should already include appId
|
|
3402
3426
|
return "".concat(serverUrl).concat(HOMEOWNER_ENDPOINT_PATH);
|
|
3403
3427
|
};
|
|
3428
|
+
MCPChat.prototype.getCloseEndpoint = function () {
|
|
3429
|
+
var serverUrl = this.config.serverUrl.replace(/\/+$/, "");
|
|
3430
|
+
var mode = this.config.mode || "homeowner";
|
|
3431
|
+
if (mode === "business_owner") {
|
|
3432
|
+
return "".concat(serverUrl).concat(BUSINESS_OWNER_CLOSE_ENDPOINT_PATH);
|
|
3433
|
+
}
|
|
3434
|
+
return "".concat(serverUrl).concat(HOMEOWNER_CLOSE_ENDPOINT_PATH);
|
|
3435
|
+
};
|
|
3404
3436
|
MCPChat.prototype.sendNewMessage = function (msg, user) {
|
|
3405
3437
|
log("SERVER: new message: ".concat(JSON.stringify(msg)));
|
|
3406
3438
|
this.dispatch({
|
|
@@ -3551,6 +3583,30 @@ var MCPChat = /** @class */ (function () {
|
|
|
3551
3583
|
},
|
|
3552
3584
|
});
|
|
3553
3585
|
};
|
|
3586
|
+
MCPChat.prototype.dispatchStreamAbort = function (messageId) {
|
|
3587
|
+
log("[dispatchStreamAbort] Stream aborted: ".concat(messageId));
|
|
3588
|
+
this.dispatch({
|
|
3589
|
+
type: "chat",
|
|
3590
|
+
detail: {
|
|
3591
|
+
type: "chat.stream.abort",
|
|
3592
|
+
user: this.getBot(undefined),
|
|
3593
|
+
messageId: messageId,
|
|
3594
|
+
timestamp: Date.now(),
|
|
3595
|
+
},
|
|
3596
|
+
});
|
|
3597
|
+
};
|
|
3598
|
+
MCPChat.prototype.dispatchStreamResume = function (messageId) {
|
|
3599
|
+
log("[dispatchStreamResume] Stream resumed: ".concat(messageId));
|
|
3600
|
+
this.dispatch({
|
|
3601
|
+
type: "chat",
|
|
3602
|
+
detail: {
|
|
3603
|
+
type: "chat.stream.resume",
|
|
3604
|
+
user: this.getBot(undefined),
|
|
3605
|
+
messageId: messageId,
|
|
3606
|
+
timestamp: Date.now(),
|
|
3607
|
+
},
|
|
3608
|
+
});
|
|
3609
|
+
};
|
|
3554
3610
|
MCPChat.prototype.dispatchToolStart = function (params) {
|
|
3555
3611
|
this.dispatch({
|
|
3556
3612
|
type: "chat",
|
|
@@ -3864,7 +3920,10 @@ var MCPChat = /** @class */ (function () {
|
|
|
3864
3920
|
attempt++;
|
|
3865
3921
|
return [3 /*break*/, 3];
|
|
3866
3922
|
case 14:
|
|
3867
|
-
if (
|
|
3923
|
+
if (success) {
|
|
3924
|
+
this.sentMessageCount++;
|
|
3925
|
+
}
|
|
3926
|
+
else {
|
|
3868
3927
|
log("All MCP retry attempts exhausted");
|
|
3869
3928
|
}
|
|
3870
3929
|
return [2 /*return*/];
|
|
@@ -3877,10 +3936,28 @@ var MCPChat = /** @class */ (function () {
|
|
|
3877
3936
|
*/
|
|
3878
3937
|
MCPChat.prototype.attemptSSERequest = function (message) {
|
|
3879
3938
|
return __awaiter$1(this, void 0, void 0, function () {
|
|
3880
|
-
var messageText;
|
|
3939
|
+
var messageText, statusMsg, msgId;
|
|
3881
3940
|
var _a;
|
|
3882
3941
|
return __generator$1(this, function (_b) {
|
|
3883
3942
|
messageText = ((_a = message.msg) === null || _a === void 0 ? void 0 : _a.text) || "";
|
|
3943
|
+
// Intercept /debug command — toggle debug mode client-side without sending to LLM
|
|
3944
|
+
if (messageText.trim().toLowerCase() === '/debug') {
|
|
3945
|
+
this._debugMode = !this._debugMode;
|
|
3946
|
+
statusMsg = this._debugMode ? 'Debug mode enabled' : 'Debug mode disabled';
|
|
3947
|
+
log("[attemptSSERequest] ".concat(statusMsg));
|
|
3948
|
+
msgId = "debug-".concat(Date.now());
|
|
3949
|
+
this.dispatch({
|
|
3950
|
+
type: "chat",
|
|
3951
|
+
detail: {
|
|
3952
|
+
type: "chat.stream.end",
|
|
3953
|
+
user: this.getBot(undefined),
|
|
3954
|
+
messageId: msgId,
|
|
3955
|
+
msg: { text: statusMsg },
|
|
3956
|
+
timestamp: +new Date(),
|
|
3957
|
+
},
|
|
3958
|
+
});
|
|
3959
|
+
return [2 /*return*/];
|
|
3960
|
+
}
|
|
3884
3961
|
log("[attemptSSERequest] Sending message: \"".concat(messageText, "\", userId: ").concat(this._userId, ", sessionId: ").concat(this._sessionId));
|
|
3885
3962
|
return [2 /*return*/, this.performSSERequest(this.buildRequestPayload({ message: messageText }))];
|
|
3886
3963
|
});
|
|
@@ -3916,6 +3993,10 @@ var MCPChat = /** @class */ (function () {
|
|
|
3916
3993
|
: {})),
|
|
3917
3994
|
body: JSON.stringify(body),
|
|
3918
3995
|
signal: _this.abortController.signal,
|
|
3996
|
+
// Keep the connection open when the tab is hidden. Without this,
|
|
3997
|
+
// fetchEventSource closes and re-POSTs on visibility change, which
|
|
3998
|
+
// creates duplicate streaming messages and out-of-order responses.
|
|
3999
|
+
openWhenHidden: true,
|
|
3919
4000
|
onopen: function (response) { return __awaiter$1(_this, void 0, void 0, function () {
|
|
3920
4001
|
var error;
|
|
3921
4002
|
return __generator$1(this, function (_a) {
|
|
@@ -3932,6 +4013,11 @@ var MCPChat = /** @class */ (function () {
|
|
|
3932
4013
|
});
|
|
3933
4014
|
}); },
|
|
3934
4015
|
onmessage: function (event) {
|
|
4016
|
+
// Guard: ignore events from stale/aborted requests
|
|
4017
|
+
if (_this.currentMessageId !== messageId) {
|
|
4018
|
+
log("[SSE] Ignoring stale event for messageId: ".concat(messageId, ", current: ").concat(_this.currentMessageId));
|
|
4019
|
+
return;
|
|
4020
|
+
}
|
|
3935
4021
|
log("SSE event: ".concat(event.event, ", data: ").concat(event.data));
|
|
3936
4022
|
var eventType = event.event || "message";
|
|
3937
4023
|
var data = {};
|
|
@@ -3971,11 +4057,17 @@ var MCPChat = /** @class */ (function () {
|
|
|
3971
4057
|
storeIdentity(data.userId, data.sessionId);
|
|
3972
4058
|
log("[SSE] Identity from connected - userId: ".concat(data.userId, ", sessionId: ").concat(data.sessionId));
|
|
3973
4059
|
}
|
|
3974
|
-
// Always start streaming - server sends greeting for both new and returning users
|
|
3975
|
-
// (resumeSession requests always get a personalized greeting)
|
|
3976
4060
|
_this.currentToolCalls = [];
|
|
3977
4061
|
_this.stopTyping();
|
|
3978
|
-
|
|
4062
|
+
if (data.isReplay) {
|
|
4063
|
+
// Replay connection — resume into the existing partial
|
|
4064
|
+
// message instead of creating a duplicate placeholder
|
|
4065
|
+
_this.dispatchStreamResume(messageId);
|
|
4066
|
+
}
|
|
4067
|
+
else {
|
|
4068
|
+
// Normal connection — create new streaming placeholder
|
|
4069
|
+
_this.dispatchStreamStart(messageId);
|
|
4070
|
+
}
|
|
3979
4071
|
_this.startStreamThrottle(messageId);
|
|
3980
4072
|
break;
|
|
3981
4073
|
case "session_expired":
|
|
@@ -3998,9 +4090,9 @@ var MCPChat = /** @class */ (function () {
|
|
|
3998
4090
|
if (data.text) {
|
|
3999
4091
|
log("[SSE] Text chunk received (".concat(data.text.length, " chars), total chunks: ").concat(textChunks.length + 1, ", isReplay: ").concat(data.isReplay));
|
|
4000
4092
|
textChunks.push(data.text);
|
|
4001
|
-
if (data.isReplay) {
|
|
4002
|
-
// Replayed text — dispatch immediately
|
|
4003
|
-
//
|
|
4093
|
+
if (data.isReplay || _this.isSleeping) {
|
|
4094
|
+
// Replayed text or tab hidden — dispatch immediately
|
|
4095
|
+
// (no throttle timer running in either case)
|
|
4004
4096
|
_this.dispatchStreamChunk(messageId, data.text);
|
|
4005
4097
|
}
|
|
4006
4098
|
else {
|
|
@@ -4050,8 +4142,19 @@ var MCPChat = /** @class */ (function () {
|
|
|
4050
4142
|
toolCall.error = data.toolError;
|
|
4051
4143
|
toolCall.label = data.label; // Update with completion label
|
|
4052
4144
|
toolCall.hidden = data.hidden; // Update hidden status
|
|
4053
|
-
|
|
4145
|
+
// Use server-side batch duration if available (debug mode), otherwise client-side
|
|
4146
|
+
toolCall.endTime = (data.toolBatchDurationMs && toolCall.startTime != null)
|
|
4147
|
+
? toolCall.startTime + data.toolBatchDurationMs
|
|
4148
|
+
: Date.now();
|
|
4054
4149
|
toolCall.isExecuting = false;
|
|
4150
|
+
// Capture tool input from debug-enriched events
|
|
4151
|
+
if (data.toolInput) {
|
|
4152
|
+
toolCall.input = data.toolInput;
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
// Toggle debug mode when toggle_debug tool is called by the LLM
|
|
4156
|
+
if (data.toolName === 'toggle_debug') {
|
|
4157
|
+
_this._debugMode = !_this._debugMode;
|
|
4055
4158
|
}
|
|
4056
4159
|
// Always dispatch tool end for user-friendly progress display
|
|
4057
4160
|
_this.dispatchToolEnd({
|
|
@@ -4099,8 +4202,8 @@ var MCPChat = /** @class */ (function () {
|
|
|
4099
4202
|
sources: sources,
|
|
4100
4203
|
displays: data.response.displays,
|
|
4101
4204
|
};
|
|
4102
|
-
// Capture tool calls before flushing (debug mode)
|
|
4103
|
-
var toolCalls_1 = _this.config.debugMode ? __spreadArray$1([], _this.currentToolCalls, true) : undefined;
|
|
4205
|
+
// Capture tool calls before flushing (debug mode — config or runtime toggle)
|
|
4206
|
+
var toolCalls_1 = (_this.config.debugMode || _this._debugMode) ? __spreadArray$1([], _this.currentToolCalls, true) : undefined;
|
|
4104
4207
|
_this.flushStreamBuffer().then(function () {
|
|
4105
4208
|
_this.dispatchStreamEnd(messageId, finalMessage_1, toolCalls_1);
|
|
4106
4209
|
_this.currentToolCalls = []; // Clear after dispatch
|
|
@@ -4135,7 +4238,20 @@ var MCPChat = /** @class */ (function () {
|
|
|
4135
4238
|
if (_this.currentMessageId === messageId && !hasError) {
|
|
4136
4239
|
hasError = true;
|
|
4137
4240
|
_this.isReplaying = false;
|
|
4138
|
-
_this.
|
|
4241
|
+
if (_this.isSleeping) {
|
|
4242
|
+
// Connection dropped while tab is hidden — don't treat as
|
|
4243
|
+
// normal error. Finalize partial message and request replay
|
|
4244
|
+
// when the user returns.
|
|
4245
|
+
log("[MCPChat] SSE error during sleep - will request replay on wakeup");
|
|
4246
|
+
_this.needsReplayOnWakeup = true;
|
|
4247
|
+
if (_this.streamingMessageIdForThrottle) {
|
|
4248
|
+
_this.dispatchStreamAbort(messageId);
|
|
4249
|
+
}
|
|
4250
|
+
_this.stopStreamThrottle();
|
|
4251
|
+
}
|
|
4252
|
+
else {
|
|
4253
|
+
_this.stopStreamThrottle();
|
|
4254
|
+
}
|
|
4139
4255
|
sseError = error instanceof Error ? error : new Error(String(error));
|
|
4140
4256
|
// Get status code if available
|
|
4141
4257
|
var statusCode = (error === null || error === void 0 ? void 0 : error.statusCode) || httpStatusCode;
|
|
@@ -4182,12 +4298,20 @@ var MCPChat = /** @class */ (function () {
|
|
|
4182
4298
|
this._attributes.currentUrl = window.location.href;
|
|
4183
4299
|
}
|
|
4184
4300
|
this._accessToken = this.visitorInfo.accessToken;
|
|
4185
|
-
|
|
4186
|
-
//
|
|
4301
|
+
this.startSession(sessionId);
|
|
4302
|
+
// Initialize session now that the widget is visible and visitor info is set.
|
|
4303
|
+
// Returning user with existing session - validate session silently
|
|
4304
|
+
// New user without session - initialize new session to get greeting
|
|
4305
|
+
if (this._sessionId) {
|
|
4306
|
+
this.validateSession();
|
|
4307
|
+
}
|
|
4308
|
+
else {
|
|
4309
|
+
this.initializeNewSession();
|
|
4310
|
+
}
|
|
4311
|
+
// Show typing indicator while waiting for session init response
|
|
4187
4312
|
if (this.isInitializingSession) {
|
|
4188
4313
|
this.typing();
|
|
4189
4314
|
}
|
|
4190
|
-
this.startSession(sessionId);
|
|
4191
4315
|
cb();
|
|
4192
4316
|
};
|
|
4193
4317
|
MCPChat.prototype.sendChatRating = function () { };
|
|
@@ -4204,23 +4328,138 @@ var MCPChat = /** @class */ (function () {
|
|
|
4204
4328
|
}
|
|
4205
4329
|
};
|
|
4206
4330
|
MCPChat.prototype.sleep = function () {
|
|
4207
|
-
this.
|
|
4208
|
-
this.
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
this.
|
|
4331
|
+
this.isSleeping = true;
|
|
4332
|
+
if (this.streamingMessageIdForThrottle) {
|
|
4333
|
+
// Active stream in progress — keep SSE connection alive so the
|
|
4334
|
+
// response completes naturally while the tab is hidden.
|
|
4335
|
+
this.needsReplayOnWakeup = false;
|
|
4336
|
+
log("[MCPChat] sleep() - stream in progress, keeping SSE connection alive");
|
|
4337
|
+
// Flush any buffered text to Redux immediately
|
|
4338
|
+
if (this.streamBuffer.length > 0) {
|
|
4339
|
+
this.dispatchStreamChunk(this.streamingMessageIdForThrottle, this.streamBuffer);
|
|
4340
|
+
this.streamBuffer = "";
|
|
4341
|
+
}
|
|
4342
|
+
// Stop the visual throttle timer (browsers throttle timers anyway)
|
|
4343
|
+
// but keep streamingMessageIdForThrottle so incoming chunks can be dispatched
|
|
4344
|
+
if (this.streamThrottleTimer) {
|
|
4345
|
+
clearInterval(this.streamThrottleTimer);
|
|
4346
|
+
this.streamThrottleTimer = undefined;
|
|
4347
|
+
}
|
|
4348
|
+
if (this.visibilityHandler) {
|
|
4349
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
4350
|
+
this.visibilityHandler = undefined;
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
else {
|
|
4354
|
+
// No active stream — safe to abort the idle connection
|
|
4355
|
+
this.stopStreamThrottle();
|
|
4356
|
+
this.isReplaying = false;
|
|
4357
|
+
if (this.abortController) {
|
|
4358
|
+
this.abortController.abort();
|
|
4359
|
+
this.abortController = undefined;
|
|
4360
|
+
}
|
|
4212
4361
|
}
|
|
4213
4362
|
};
|
|
4214
4363
|
MCPChat.prototype.wakeup = function () {
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4364
|
+
this.isSleeping = false;
|
|
4365
|
+
if (this.streamingMessageIdForThrottle && this.abortController) {
|
|
4366
|
+
// Stream is still in progress — restart the visual throttle
|
|
4367
|
+
log("[MCPChat] wakeup() - stream still in progress, restarting throttle");
|
|
4368
|
+
// Flush accumulated buffer before startStreamThrottle (which resets it)
|
|
4369
|
+
if (this.streamBuffer.length > 0) {
|
|
4370
|
+
this.dispatchStreamChunk(this.streamingMessageIdForThrottle, this.streamBuffer);
|
|
4371
|
+
this.streamBuffer = "";
|
|
4372
|
+
}
|
|
4373
|
+
this.startStreamThrottle(this.streamingMessageIdForThrottle);
|
|
4374
|
+
}
|
|
4375
|
+
else if (this.needsReplayOnWakeup) {
|
|
4376
|
+
this.needsReplayOnWakeup = false;
|
|
4377
|
+
if (this.lastSeq !== undefined && this._sessionId) {
|
|
4378
|
+
// Connection dropped during sleep — request replay of missed events.
|
|
4379
|
+
// Don't set "online" yet — requestReplay is fire-and-forget and the
|
|
4380
|
+
// connection status will be updated by the SSE onopen handler.
|
|
4381
|
+
log("[MCPChat] wakeup() - connection lost during sleep, requesting replay");
|
|
4382
|
+
this.requestReplay();
|
|
4383
|
+
return;
|
|
4384
|
+
}
|
|
4385
|
+
// Cannot replay — missing lastSeq or sessionId
|
|
4386
|
+
err("[MCPChat] wakeup() - cannot replay, missing lastSeq (".concat(this.lastSeq, ") or sessionId (").concat(this._sessionId, ")"));
|
|
4387
|
+
}
|
|
4219
4388
|
if (this._sessionId) {
|
|
4220
4389
|
log("[MCPChat] Wakeup - session exists, lastSeq: ".concat(this.lastSeq));
|
|
4221
4390
|
this.setConnectionStatus("online");
|
|
4222
4391
|
}
|
|
4223
4392
|
};
|
|
4393
|
+
MCPChat.prototype.requestReplay = function () {
|
|
4394
|
+
return __awaiter$1(this, void 0, void 0, function () {
|
|
4395
|
+
var error_5;
|
|
4396
|
+
return __generator$1(this, function (_a) {
|
|
4397
|
+
switch (_a.label) {
|
|
4398
|
+
case 0:
|
|
4399
|
+
if (this.isRequestingReplay) {
|
|
4400
|
+
log("[MCPChat] Replay already in progress, skipping");
|
|
4401
|
+
return [2 /*return*/];
|
|
4402
|
+
}
|
|
4403
|
+
this.isRequestingReplay = true;
|
|
4404
|
+
this.typing();
|
|
4405
|
+
_a.label = 1;
|
|
4406
|
+
case 1:
|
|
4407
|
+
_a.trys.push([1, 3, 4, 5]);
|
|
4408
|
+
// lastSeq is already included by buildRequestPayload
|
|
4409
|
+
return [4 /*yield*/, this.performSSERequest(this.buildRequestPayload())];
|
|
4410
|
+
case 2:
|
|
4411
|
+
// lastSeq is already included by buildRequestPayload
|
|
4412
|
+
_a.sent();
|
|
4413
|
+
log("[MCPChat] Replay request completed");
|
|
4414
|
+
return [3 /*break*/, 5];
|
|
4415
|
+
case 3:
|
|
4416
|
+
error_5 = _a.sent();
|
|
4417
|
+
err("[MCPChat] Replay request failed: ".concat(error_5));
|
|
4418
|
+
this.setConnectionStatus("offline");
|
|
4419
|
+
return [3 /*break*/, 5];
|
|
4420
|
+
case 4:
|
|
4421
|
+
this.stopTyping();
|
|
4422
|
+
this.isRequestingReplay = false;
|
|
4423
|
+
return [7 /*endfinally*/];
|
|
4424
|
+
case 5: return [2 /*return*/];
|
|
4425
|
+
}
|
|
4426
|
+
});
|
|
4427
|
+
});
|
|
4428
|
+
};
|
|
4429
|
+
/**
|
|
4430
|
+
* Sends a best-effort close notification to the agent server.
|
|
4431
|
+
* Uses navigator.sendBeacon() so the request survives page unload.
|
|
4432
|
+
* Called from the beforeunload event handler and the widget close button.
|
|
4433
|
+
*/
|
|
4434
|
+
MCPChat.prototype.notifyDisconnecting = function (reason) {
|
|
4435
|
+
if (reason === void 0) { reason = "close_tab"; }
|
|
4436
|
+
if (!this._sessionId) {
|
|
4437
|
+
return;
|
|
4438
|
+
}
|
|
4439
|
+
var endpoint = this.getCloseEndpoint();
|
|
4440
|
+
var payload = JSON.stringify({
|
|
4441
|
+
sessionId: this._sessionId,
|
|
4442
|
+
userId: this._userId || undefined,
|
|
4443
|
+
reason: reason,
|
|
4444
|
+
messageCount: this.sentMessageCount,
|
|
4445
|
+
durationMs: this.sessionStartTime !== undefined ? Date.now() - this.sessionStartTime : 0,
|
|
4446
|
+
});
|
|
4447
|
+
log("[MCPChat] notifyDisconnecting: reason=".concat(reason, ", endpoint=").concat(endpoint));
|
|
4448
|
+
// For widget_close, the page is not unloading so fetch() can be used — it supports
|
|
4449
|
+
// auth headers (needed for business_owner mode) unlike sendBeacon.
|
|
4450
|
+
if (reason === "widget_close" && typeof fetch !== "undefined") {
|
|
4451
|
+
var headers = { "Content-Type": "application/json" };
|
|
4452
|
+
if (this.config.authToken) {
|
|
4453
|
+
headers["Authorization"] = "Bearer ".concat(this.config.authToken);
|
|
4454
|
+
}
|
|
4455
|
+
fetch(endpoint, { method: "POST", headers: headers, body: payload, keepalive: true });
|
|
4456
|
+
return;
|
|
4457
|
+
}
|
|
4458
|
+
if (typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
4459
|
+
var blob = new Blob([payload], { type: "application/json" });
|
|
4460
|
+
navigator.sendBeacon(endpoint, blob);
|
|
4461
|
+
}
|
|
4462
|
+
};
|
|
4224
4463
|
MCPChat.prototype.startSession = function (sessionId) {
|
|
4225
4464
|
// userId priority:
|
|
4226
4465
|
// 1. Existing server-assigned userId (from localStorage or previous connected event)
|
|
@@ -10795,6 +11034,14 @@ function isCard(display) {
|
|
|
10795
11034
|
function isList(display) {
|
|
10796
11035
|
return !!display && (display.type === "LIST" || display.type === "CAROUSEL");
|
|
10797
11036
|
}
|
|
11037
|
+
/**
|
|
11038
|
+
* Is the Display a Table
|
|
11039
|
+
*/
|
|
11040
|
+
function isTable(display) {
|
|
11041
|
+
return !!display && display.type === "TABLE"
|
|
11042
|
+
&& Array.isArray(display.headers)
|
|
11043
|
+
&& Array.isArray(display.rows);
|
|
11044
|
+
}
|
|
10798
11045
|
|
|
10799
11046
|
var CardMiddlewareWidget = function (props) {
|
|
10800
11047
|
var msg = props.msg, ctx = props.ctx;
|
|
@@ -11095,10 +11342,31 @@ var MultiSelectMiddleware = function (next) { return function (props) {
|
|
|
11095
11342
|
return next(props);
|
|
11096
11343
|
}; };
|
|
11097
11344
|
|
|
11345
|
+
var Table = function (_a) {
|
|
11346
|
+
var table = _a.table;
|
|
11347
|
+
return (require$$0.jsxs("div", { className: "xappw-table", children: [table.title && require$$0.jsx("div", { className: "xappw-table__header", children: table.title }), require$$0.jsx("div", { className: "xappw-table__container", children: require$$0.jsxs("table", { children: [require$$0.jsx("thead", { children: require$$0.jsx("tr", { children: table.headers.map(function (header, i) { return (require$$0.jsx("th", { scope: "col", children: header }, "".concat(header, "-").concat(i))); }) }) }), require$$0.jsx("tbody", { children: table.rows.map(function (row, rowIndex) { return (require$$0.jsx("tr", { children: row.map(function (cell, cellIndex) { return (require$$0.jsx("td", { children: cell }, cellIndex)); }) }, rowIndex)); }) })] }) })] }));
|
|
11348
|
+
};
|
|
11349
|
+
|
|
11350
|
+
var TableMiddlewareWidget = function (props) {
|
|
11351
|
+
var msg = props.msg, ctx = props.ctx;
|
|
11352
|
+
if (!ctx) {
|
|
11353
|
+
throw new TypeError("ctx not provided to ".concat(TableMiddlewareWidget.name));
|
|
11354
|
+
}
|
|
11355
|
+
return (require$$0.jsx(ChatMessagePart, { showAvatar: false, user: ctx.user, fullWidth: true, children: require$$0.jsx("div", { className: "chat-msg chat-msg--expand", children: require$$0.jsx(Table, { table: msg }) }) }));
|
|
11356
|
+
};
|
|
11357
|
+
var TableMiddleware = function (next) { return function (props) {
|
|
11358
|
+
var msg = props.msg, ctx = props.ctx;
|
|
11359
|
+
if (isTable(msg)) {
|
|
11360
|
+
return require$$0.jsx(TableMiddlewareWidget, { msg: msg, ctx: ctx });
|
|
11361
|
+
}
|
|
11362
|
+
return next(props);
|
|
11363
|
+
}; };
|
|
11364
|
+
|
|
11098
11365
|
var StandardMiddlewaresBuilder = joinMiddlewares([
|
|
11099
11366
|
ListMiddleware,
|
|
11100
11367
|
CardMiddleware,
|
|
11101
|
-
MultiSelectMiddleware
|
|
11368
|
+
MultiSelectMiddleware,
|
|
11369
|
+
TableMiddleware
|
|
11102
11370
|
]);
|
|
11103
11371
|
function useStandardMiddlewareBuilder() {
|
|
11104
11372
|
var lateMiddleware = useLateMiddleware();
|
|
@@ -11352,14 +11620,58 @@ var ToolCallDisplay = function (_a) {
|
|
|
11352
11620
|
};
|
|
11353
11621
|
|
|
11354
11622
|
/**
|
|
11355
|
-
*
|
|
11356
|
-
*
|
|
11623
|
+
* Escape &, <, > for safe insertion into HTML text nodes.
|
|
11624
|
+
* Not suitable for attribute values.
|
|
11357
11625
|
*/
|
|
11626
|
+
function escapeHtml(text) {
|
|
11627
|
+
return text
|
|
11628
|
+
.replace(/&/g, "&")
|
|
11629
|
+
.replace(/</g, "<")
|
|
11630
|
+
.replace(/>/g, ">");
|
|
11631
|
+
}
|
|
11632
|
+
function parseInlineMarkdown(text) {
|
|
11633
|
+
return escapeHtml(text)
|
|
11634
|
+
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
|
|
11635
|
+
.replace(/__(.+?)__/g, "<strong>$1</strong>")
|
|
11636
|
+
.replace(/\*([^*]+?)\*/g, "<em>$1</em>")
|
|
11637
|
+
.replace(/_([^_]+?)_/g, "<em>$1</em>");
|
|
11638
|
+
}
|
|
11639
|
+
function tableToHtml(headerLine, separatorLine, bodyBlock) {
|
|
11640
|
+
var parseRow = function (row) {
|
|
11641
|
+
return row.split("|").slice(1, -1).map(function (cell) { return parseInlineMarkdown(cell.trim()); });
|
|
11642
|
+
};
|
|
11643
|
+
var separators = separatorLine.split("|").slice(1, -1).map(function (s) { return s.trim(); });
|
|
11644
|
+
var alignments = separators.map(function (sep) {
|
|
11645
|
+
if (sep.startsWith(":") && sep.endsWith(":"))
|
|
11646
|
+
return "center";
|
|
11647
|
+
if (sep.endsWith(":"))
|
|
11648
|
+
return "right";
|
|
11649
|
+
return "left";
|
|
11650
|
+
});
|
|
11651
|
+
var headers = parseRow(headerLine);
|
|
11652
|
+
var headerHtml = headers.map(function (h, i) {
|
|
11653
|
+
return "<th scope=\"col\" style=\"text-align:".concat(alignments[i] || "left", "\">").concat(h, "</th>");
|
|
11654
|
+
}).join("");
|
|
11655
|
+
var bodyRows = bodyBlock.trim().split("\n").filter(function (row) { return row.trim() !== ""; });
|
|
11656
|
+
var bodyHtml = bodyRows.map(function (row) {
|
|
11657
|
+
var cells = parseRow(row);
|
|
11658
|
+
return "<tr>" + cells.map(function (c, i) {
|
|
11659
|
+
return "<td style=\"text-align:".concat(alignments[i] || "left", "\">").concat(c, "</td>");
|
|
11660
|
+
}).join("") + "</tr>";
|
|
11661
|
+
}).join("");
|
|
11662
|
+
return "<div style=\"overflow-x:auto\"><table><thead><tr>".concat(headerHtml, "</tr></thead><tbody>").concat(bodyHtml, "</tbody></table></div>");
|
|
11663
|
+
}
|
|
11358
11664
|
function parseMarkdown(text) {
|
|
11359
11665
|
if (!text)
|
|
11360
11666
|
return "";
|
|
11361
|
-
|
|
11362
|
-
|
|
11667
|
+
// Extract tables first, replace with placeholders, then process the rest
|
|
11668
|
+
var tables = [];
|
|
11669
|
+
var processed = text.replace(/(?:^|\n)(\|.+\|)\n(\|[\s:|-]+\|)\n((?:\|.+\|\n?)+)/g, function (_match, headerLine, separatorLine, bodyBlock) {
|
|
11670
|
+
tables.push(tableToHtml(headerLine, separatorLine, bodyBlock));
|
|
11671
|
+
return "\n%%TABLE".concat(tables.length - 1, "%%\n");
|
|
11672
|
+
});
|
|
11673
|
+
// Now safely escape and parse markdown on the non-table text
|
|
11674
|
+
processed = processed
|
|
11363
11675
|
.replace(/&/g, "&")
|
|
11364
11676
|
.replace(/</g, "<")
|
|
11365
11677
|
.replace(/>/g, ">")
|
|
@@ -11380,13 +11692,17 @@ function parseMarkdown(text) {
|
|
|
11380
11692
|
// Line breaks
|
|
11381
11693
|
.replace(/\n/g, "<br>");
|
|
11382
11694
|
// Wrap consecutive <li> elements in <ul>
|
|
11383
|
-
|
|
11384
|
-
|
|
11695
|
+
processed = processed.replace(/(<li>.*?<\/li>)(<br>)?/g, "$1");
|
|
11696
|
+
processed = processed.replace(/(<li>.*?<\/li>)+/g, "<ul>$&</ul>");
|
|
11385
11697
|
// Clean up <br> inside <ul>
|
|
11386
|
-
|
|
11698
|
+
processed = processed.replace(/<ul>(.*?)<\/ul>/g, function (match) {
|
|
11387
11699
|
return match.replace(/<br>/g, "");
|
|
11388
11700
|
});
|
|
11389
|
-
|
|
11701
|
+
// Restore table placeholders (strip adjacent <br> tags)
|
|
11702
|
+
tables.forEach(function (tableHtml, i) {
|
|
11703
|
+
processed = processed.replace(new RegExp("(?:<br>)?%%TABLE".concat(i, "%%(?:<br>)?")), tableHtml);
|
|
11704
|
+
});
|
|
11705
|
+
return processed;
|
|
11390
11706
|
}
|
|
11391
11707
|
var ChatTextMessage = function (props) {
|
|
11392
11708
|
var _a;
|
|
@@ -35454,7 +35770,7 @@ var ChatWidgetConnected = function (props) {
|
|
|
35454
35770
|
* Exported for use by StaticChatWidgetContainer to avoid infinite loops.
|
|
35455
35771
|
*/
|
|
35456
35772
|
var ChatWidgetUI = function (props) {
|
|
35457
|
-
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
|
|
35773
|
+
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;
|
|
35458
35774
|
var innerDispatch = useChatDispatch();
|
|
35459
35775
|
var dispatch = useChatServerDispatch();
|
|
35460
35776
|
// From Redux
|
|
@@ -35484,24 +35800,30 @@ var ChatWidgetUI = function (props) {
|
|
|
35484
35800
|
chatState.visuals = {};
|
|
35485
35801
|
}
|
|
35486
35802
|
// Our state - pull from storage
|
|
35487
|
-
|
|
35803
|
+
// Parse autoOpenOnWidth once for use in both initial state and useEffect
|
|
35804
|
+
var autoOpenOnWidthValue = ((_m = props.config) === null || _m === void 0 ? void 0 : _m.autoOpenOnWidth)
|
|
35805
|
+
? parseInt(props.config.autoOpenOnWidth, 10)
|
|
35806
|
+
: NaN;
|
|
35807
|
+
var autoOpenOnWidthMatch = !isNaN(autoOpenOnWidthValue) && window.innerWidth >= autoOpenOnWidthValue;
|
|
35808
|
+
// When autoOpenOnWidth is configured, use width check instead of persisted visible state
|
|
35809
|
+
// to prevent greeting requests on viewports below the threshold
|
|
35810
|
+
var _14 = require$$1.useState((!canMinimize && !canCancel) ||
|
|
35488
35811
|
// !!get("visible") ||
|
|
35489
|
-
chatState.visuals.visible
|
|
35490
|
-
|
|
35491
|
-
window.matchMedia("(min-width: ".concat((_o = props.config) === null || _o === void 0 ? void 0 : _o.autoOpenOnWidth, ")")).matches)), visible = _15[0], setVisibleState = _15[1];
|
|
35492
|
-
var _16 = require$$1.useState(false); _16[0]; var setTypingState = _16[1]; // false initially - state kept for potential external observers
|
|
35812
|
+
(!isNaN(autoOpenOnWidthValue) ? autoOpenOnWidthMatch : chatState.visuals.visible)), visible = _14[0], setVisibleState = _14[1];
|
|
35813
|
+
var _15 = require$$1.useState(false); _15[0]; var setTypingState = _15[1]; // false initially - state kept for potential external observers
|
|
35493
35814
|
// Ref to track typing state for use in timeout callbacks
|
|
35494
35815
|
var typingRef = require$$1.useRef(false);
|
|
35495
35816
|
// Timeout ref for debouncing "stop typing" events
|
|
35496
35817
|
var stopTypingTimeoutRef = require$$1.useRef(null);
|
|
35818
|
+
// Track whether autoOpenOnWidth has already fired so it doesn't re-open after user closes
|
|
35819
|
+
var autoOpenFiredRef = require$$1.useRef(autoOpenOnWidthMatch);
|
|
35497
35820
|
var chatServer = require$$1.useContext(ChatServerContext);
|
|
35498
|
-
var patternsConfig = (
|
|
35821
|
+
var patternsConfig = (_p = (_o = props.config) === null || _o === void 0 ? void 0 : _o.autoOpenOnPattern) === null || _p === void 0 ? void 0 : _p.patterns;
|
|
35499
35822
|
var currentUrl = window.location.href;
|
|
35500
35823
|
var patternExist = patternsConfig && patternsConfig.length > 0;
|
|
35501
35824
|
var patternMatches = patternsConfig === null || patternsConfig === void 0 ? void 0 : patternsConfig.some(function (pattern) { return currentUrl.includes(pattern); });
|
|
35502
|
-
var configWidth = (
|
|
35503
|
-
|
|
35504
|
-
var currentWidth = screen.width;
|
|
35825
|
+
var configWidth = (_r = (_q = props.config) === null || _q === void 0 ? void 0 : _q.autoOpenOnPattern) === null || _r === void 0 ? void 0 : _r.minimumWidth;
|
|
35826
|
+
var currentWidth = window.innerWidth;
|
|
35505
35827
|
var setVisible = require$$1.useCallback(function (newVisible) {
|
|
35506
35828
|
if (staticMode || previewMode) {
|
|
35507
35829
|
return;
|
|
@@ -35535,7 +35857,7 @@ var ChatWidgetUI = function (props) {
|
|
|
35535
35857
|
};
|
|
35536
35858
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
35537
35859
|
}, []);
|
|
35538
|
-
var
|
|
35860
|
+
var _16 = require$$1.useState(!document.hidden), isTabVisible = _16[0], setIsTabVisible = _16[1];
|
|
35539
35861
|
require$$1.useEffect(function () {
|
|
35540
35862
|
var handleVisibilityChange = function () {
|
|
35541
35863
|
setIsTabVisible(!document.hidden);
|
|
@@ -35568,7 +35890,7 @@ var ChatWidgetUI = function (props) {
|
|
|
35568
35890
|
var handleBeforeUnload = function () {
|
|
35569
35891
|
log("beforeunload - notifying router of user disconnecting");
|
|
35570
35892
|
if (chatServer.notifyDisconnecting) {
|
|
35571
|
-
chatServer.notifyDisconnecting();
|
|
35893
|
+
chatServer.notifyDisconnecting("close_tab");
|
|
35572
35894
|
}
|
|
35573
35895
|
};
|
|
35574
35896
|
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
@@ -35584,10 +35906,8 @@ var ChatWidgetUI = function (props) {
|
|
|
35584
35906
|
if (chatState.visuals.opened) {
|
|
35585
35907
|
setVisible(true);
|
|
35586
35908
|
}
|
|
35587
|
-
else {
|
|
35588
|
-
|
|
35589
|
-
setVisible(false);
|
|
35590
|
-
}
|
|
35909
|
+
else if (mode === "normal" && !autoOpenOnWidthMatch) {
|
|
35910
|
+
setVisible(false);
|
|
35591
35911
|
}
|
|
35592
35912
|
if (patternExist && patternMatches) {
|
|
35593
35913
|
setVisible(true);
|
|
@@ -35595,6 +35915,10 @@ var ChatWidgetUI = function (props) {
|
|
|
35595
35915
|
if (currentWidth < +configWidth) {
|
|
35596
35916
|
setVisible(false);
|
|
35597
35917
|
}
|
|
35918
|
+
if (autoOpenOnWidthMatch && !autoOpenFiredRef.current) {
|
|
35919
|
+
autoOpenFiredRef.current = true;
|
|
35920
|
+
setVisible(true);
|
|
35921
|
+
}
|
|
35598
35922
|
}, [
|
|
35599
35923
|
currentWidth,
|
|
35600
35924
|
patternExist,
|
|
@@ -35603,6 +35927,7 @@ var ChatWidgetUI = function (props) {
|
|
|
35603
35927
|
setVisible,
|
|
35604
35928
|
chatState.visuals.opened,
|
|
35605
35929
|
mode,
|
|
35930
|
+
autoOpenOnWidthMatch,
|
|
35606
35931
|
]);
|
|
35607
35932
|
var stopTyping = require$$1.useCallback(function () {
|
|
35608
35933
|
if (!typingRef.current)
|
|
@@ -35722,6 +36047,9 @@ var ChatWidgetUI = function (props) {
|
|
|
35722
36047
|
}
|
|
35723
36048
|
/** Called when cancel is clicked */
|
|
35724
36049
|
function handleCancelClick() {
|
|
36050
|
+
if (chatServer === null || chatServer === void 0 ? void 0 : chatServer.notifyDisconnecting) {
|
|
36051
|
+
chatServer.notifyDisconnecting("widget_close");
|
|
36052
|
+
}
|
|
35725
36053
|
// First reset to clear all state
|
|
35726
36054
|
innerDispatch(reset());
|
|
35727
36055
|
// Then set hasInteracted to prevent CTA from showing after cancel
|
|
@@ -35747,8 +36075,8 @@ var ChatWidgetUI = function (props) {
|
|
|
35747
36075
|
}));
|
|
35748
36076
|
}
|
|
35749
36077
|
// Action Bar state and handlers
|
|
35750
|
-
var
|
|
35751
|
-
var
|
|
36078
|
+
var _17 = require$$1.useState(false), formModalOpen = _17[0], setFormModalOpen = _17[1];
|
|
36079
|
+
var _18 = require$$1.useState(null), activeFormConfig = _18[0], setActiveFormConfig = _18[1];
|
|
35752
36080
|
var handleFormButtonClick = require$$1.useCallback(function (formButton) {
|
|
35753
36081
|
// If form-widget is configured, use its openForm API
|
|
35754
36082
|
// The form-widget script should already be pre-loaded
|
|
@@ -35815,7 +36143,7 @@ var ChatWidgetUI = function (props) {
|
|
|
35815
36143
|
var messages = chatState && chatState.chats;
|
|
35816
36144
|
log("Rendering - accountStatus: \"".concat(chatState.accountStatus, "\", connectionStatus: \"").concat(chatState.connection.connectionStatus, "\""));
|
|
35817
36145
|
var config = props.config, onConnectionStatusChange = props.onConnectionStatusChange;
|
|
35818
|
-
var actionBarEnabled = ((
|
|
36146
|
+
var actionBarEnabled = ((_s = config === null || config === void 0 ? void 0 : config.actionBar) === null || _s === void 0 ? void 0 : _s.enabled) === true;
|
|
35819
36147
|
// Disable greeting in preview mode
|
|
35820
36148
|
useGreeting(!isOffline && !props.preChatFormEnabled && visible && !previewMode);
|
|
35821
36149
|
var connectionStatus = chatState.connection.connectionStatus;
|
|
@@ -35824,7 +36152,7 @@ var ChatWidgetUI = function (props) {
|
|
|
35824
36152
|
onConnectionStatusChange(connectionStatus);
|
|
35825
36153
|
}
|
|
35826
36154
|
}, [connectionStatus, onConnectionStatusChange]);
|
|
35827
|
-
useExternalScript((
|
|
36155
|
+
useExternalScript((_t = props.config) === null || _t === void 0 ? void 0 : _t.middlewareUrl);
|
|
35828
36156
|
// Pre-load form-widget script if formWidgetKey is provided
|
|
35829
36157
|
require$$1.useEffect(function () {
|
|
35830
36158
|
if (config === null || config === void 0 ? void 0 : config.formWidgetKey) {
|
|
@@ -35856,15 +36184,15 @@ var ChatWidgetUI = function (props) {
|
|
|
35856
36184
|
};
|
|
35857
36185
|
}, [actionBarEnabled]);
|
|
35858
36186
|
// This is a pseudo agent. It represent's the widget (shown in the header avatar for instance)
|
|
35859
|
-
var widgetAgent = ((
|
|
36187
|
+
var widgetAgent = ((_u = chatState.agents["agent:robot"]) === null || _u === void 0 ? void 0 : _u.user) ||
|
|
35860
36188
|
(config === null || config === void 0 ? void 0 : config.agent) || {
|
|
35861
36189
|
nick: "agent:robot",
|
|
35862
36190
|
avatarPath: config.avatarUrl,
|
|
35863
36191
|
display_name: "Agent",
|
|
35864
36192
|
};
|
|
35865
|
-
return (require$$0.jsxs(require$$0.Fragment, { children: [require$$0.jsxs("div", { className: "widget-container ".concat(modeClass, " ").concat(getVisibilityClass()).concat(actionBarEnabled ? " 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: (
|
|
36193
|
+
return (require$$0.jsxs(require$$0.Fragment, { children: [require$$0.jsxs("div", { className: "widget-container ".concat(modeClass, " ").concat(getVisibilityClass()).concat(actionBarEnabled ? " widget-container--with-action-bar" : ""), children: [require$$0.jsx(WidgetStylesheet, { theme: config === null || config === void 0 ? void 0 : config.theme }), require$$0.jsx(ChatHeader, { accountStatus: chatState.accountStatus, refreshOnClick: handleRestartClick, minimizeOnClick: handleMinimizeClick, cancelOnClick: handleCancelClick, agent: widgetAgent, canRefresh: canRefresh, canMinimize: canMinimize, canCancel: canCancel, config: config === null || config === void 0 ? void 0 : config.header, menuConfig: config.menu, debugMode: (_x = (_w = (_v = props.config) === null || _v === void 0 ? void 0 : _v.connection) === null || _w === void 0 ? void 0 : _w.mcp) === null || _x === void 0 ? void 0 : _x.debugMode, onSubmit: handleOnSubmit }), require$$0.jsx(MessageList, { visible: visible, queuePosition: chatState.queuePosition, isChatting: chatState.isChatting, isOffline: isOffline, messages: messages, agents: chatState.agents, agent: config === null || config === void 0 ? void 0 : config.agent, lastRatingRequestTimestamp: chatState.lastRatingRequestTimestamp, hasRating: chatState.hasRating, visitorId: chatState.visitorId, hasWsButton: !!chatState.wsButton, messageMiddleware: props.messageMiddleware, textTypingStatusEnabled: (_z = (_y = props.config) === null || _y === void 0 ? void 0 : _y.typingStatus) === null || _z === void 0 ? void 0 : _z.textTypingStatusEnabled, disableAutoScroll: props.disableAutoScroll, onSend: handleSendMessage, onWrite: handleWriteMessage, onOpenUrl: handleOpenUrl, minimizeOnClick: handleMinimizeClick }), require$$0.jsx("div", { className: "xa-spinner-container ".concat(visible && connectionStatus === "pending" ? "visible" : ""), children: require$$0.jsx("div", { className: "xa-spinner" }) }), connectionStatus === "offline" && require$$0.jsx(ServerOffline, {}), chatState.wsButton && visible && (require$$0.jsx(WsButton, { button: chatState.wsButton, onPress: handleWsButtonPress })), require$$0.jsx(ChatFooter, { isAdmin: config === null || config === void 0 ? void 0 : config.isAdmin, isChatting: chatState.isChatting, placeholder: (_0 = config === null || config === void 0 ? void 0 : config.input) === null || _0 === void 0 ? void 0 : _0.placeholder, sendButtonIcon: (_2 = (_1 = config === null || config === void 0 ? void 0 : config.footer) === null || _1 === void 0 ? void 0 : _1.sendButton) === null || _2 === void 0 ? void 0 : _2.icon, sendButtonIconHover: (_4 = (_3 = config === null || config === void 0 ? void 0 : config.footer) === null || _3 === void 0 ? void 0 : _3.sendButton) === null || _4 === void 0 ? void 0 : _4.iconHover, sendButtonIconDisabled: (_6 = (_5 = config === null || config === void 0 ? void 0 : config.footer) === null || _5 === void 0 ? void 0 : _5.sendButton) === null || _6 === void 0 ? void 0 : _6.iconDisabled, visible: visible, hasWsButton: !!chatState.wsButton, menuConfig: (_7 = props.config) === null || _7 === void 0 ? void 0 : _7.menu, footerConfig: (_8 = props.config) === null || _8 === void 0 ? void 0 : _8.footer, inputConfig: (_9 = props.config) === null || _9 === void 0 ? void 0 : _9.input, disabled: previewMode, onChange: handleOnChange, onSubmit: handleOnSubmit, onFileUpload: handleFileUpload }), require$$0.jsx("div", { className: "restartModal", ref: modalRef, onClick: handleRestartModalCloseClick, children: require$$0.jsx(ModalContent, { onClose: handleRestartModalCloseClick, onReset: handleReset }) })] }), actionBarEnabled && config.actionBar ? (require$$0.jsxs(require$$0.Fragment, { children: [require$$0.jsx(ActionBar, { config: __assign(__assign({}, config.actionBar), {
|
|
35866
36194
|
// Use actionBar.cta if set, otherwise fall back to widget-level cta
|
|
35867
|
-
cta: (
|
|
36195
|
+
cta: (_10 = config.actionBar.cta) !== null && _10 !== void 0 ? _10 : config.cta }), visible: visible, chatDisabled: config.disabled, hasUserInteracted: (_11 = chatState.visuals) === null || _11 === void 0 ? void 0 : _11.hasInteracted, onChatClick: chatButtonOnClick, onChatMinimize: handleMinimizeClick, onFormClick: handleFormButtonClick, onCtaDismiss: handleCtaDismiss }), formModalOpen && activeFormConfig && (require$$0.jsx(FormModal, { config: activeFormConfig, widgetEnv: config, onClose: handleFormModalClose }))] })) : (require$$0.jsx(ChatButton, { addClass: visible ? "visible" : "", onClick: chatButtonOnClick, config: config === null || config === void 0 ? void 0 : config.cta, imageUrl: (_12 = config === null || config === void 0 ? void 0 : config.chatButton) === null || _12 === void 0 ? void 0 : _12.imageUrl, visible: visible, hasInteracted: (_13 = chatState.visuals) === null || _13 === void 0 ? void 0 : _13.hasInteracted, onCtaDismiss: handleCtaDismiss })), require$$0.jsx(ErrorOverlay, { enableErrorOverlay: config === null || config === void 0 ? void 0 : config.enableErrorOverlay })] }));
|
|
35868
36196
|
};
|
|
35869
36197
|
/**
|
|
35870
36198
|
* Top-level wrapper that dispatches between preview mode and connected mode.
|
|
@@ -35957,9 +36285,10 @@ var LocalStorage = /** @class */ (function () {
|
|
|
35957
36285
|
function persistStateReducer(storage, initialState, innerReducer) {
|
|
35958
36286
|
var _a;
|
|
35959
36287
|
var obj = (_a = storage.get()) !== null && _a !== void 0 ? _a : initialState;
|
|
35960
|
-
// Clean up
|
|
36288
|
+
// Clean up transient visual state on page load - don't persist across browser refreshes
|
|
36289
|
+
// visible is derived from config (autoOpenOnWidth, autoOpenOnPattern) and visuals.opened
|
|
35961
36290
|
if (obj && obj.visuals) {
|
|
35962
|
-
obj = __assign(__assign({}, obj), { visuals: __assign(__assign({}, obj.visuals), { hasInteracted: undefined }) });
|
|
36291
|
+
obj = __assign(__assign({}, obj), { visuals: __assign(__assign({}, obj.visuals), { hasInteracted: undefined, visible: undefined }) });
|
|
35963
36292
|
}
|
|
35964
36293
|
return function (state, action) {
|
|
35965
36294
|
if (state === void 0) { state = obj; }
|
|
@@ -36076,6 +36405,20 @@ function resetReducer(state) {
|
|
|
36076
36405
|
function isChatSystemMessage(chat) {
|
|
36077
36406
|
return chat.type === "chat.systemmessage";
|
|
36078
36407
|
}
|
|
36408
|
+
/**
|
|
36409
|
+
* Find the LAST streaming message index. We must search from the end because
|
|
36410
|
+
* orphan isStreaming messages from interrupted streams may exist earlier in the
|
|
36411
|
+
* array. The active streaming message is always the most recently inserted one.
|
|
36412
|
+
*/
|
|
36413
|
+
function findLastStreamingIndex(chats) {
|
|
36414
|
+
for (var i = chats.length - 1; i >= 0; i--) {
|
|
36415
|
+
var chat = chats[i];
|
|
36416
|
+
if (isChatMsgDetail(chat) && chat.isStreaming) {
|
|
36417
|
+
return i;
|
|
36418
|
+
}
|
|
36419
|
+
}
|
|
36420
|
+
return -1;
|
|
36421
|
+
}
|
|
36079
36422
|
// todo: create reducer (requires redux-thunk dependency)
|
|
36080
36423
|
function appendMessageToState(state, msg) {
|
|
36081
36424
|
state.chats = joinMessages(state.chats, msg);
|
|
@@ -36193,8 +36536,19 @@ function update(state, action) {
|
|
|
36193
36536
|
case "chat.typing":
|
|
36194
36537
|
var agent = state.agents[action.detail.user.nick];
|
|
36195
36538
|
return __assign(__assign({}, state), { lastTimestamp: (_v = action.detail) === null || _v === void 0 ? void 0 : _v.timestamp, agents: __assign(__assign({}, state.agents), (_d = {}, _d[action.detail.user.nick] = __assign(__assign({}, agent), { typing: action.detail.typing }), _d)) });
|
|
36196
|
-
case "chat.stream.start":
|
|
36197
|
-
//
|
|
36539
|
+
case "chat.stream.start": {
|
|
36540
|
+
// Clean up any orphan streaming messages from interrupted streams
|
|
36541
|
+
// before creating the new placeholder.
|
|
36542
|
+
var cleanedChats = newState.chats.map(function (chat) {
|
|
36543
|
+
if (isChatMsgDetail(chat) && chat.isStreaming) {
|
|
36544
|
+
return __assign(__assign({}, chat), { isStreaming: false, wasStreamed: true });
|
|
36545
|
+
}
|
|
36546
|
+
return chat;
|
|
36547
|
+
});
|
|
36548
|
+
// Add placeholder message with streaming state.
|
|
36549
|
+
// Use joinMessages (insertSorted by timestamp) instead of append to
|
|
36550
|
+
// guarantee correct ordering even if events arrive out of order
|
|
36551
|
+
// (e.g., after an SSE abort/retry during tab backgrounding).
|
|
36198
36552
|
var streamingMsg = {
|
|
36199
36553
|
type: "chat.msg",
|
|
36200
36554
|
user: action.detail.user,
|
|
@@ -36204,13 +36558,15 @@ function update(state, action) {
|
|
|
36204
36558
|
},
|
|
36205
36559
|
isStreaming: true,
|
|
36206
36560
|
};
|
|
36207
|
-
|
|
36561
|
+
var newChats = joinMessages(cleanedChats, streamingMsg);
|
|
36562
|
+
return __assign(__assign({}, newState), { chats: newChats, streamingMessageId: action.detail.messageId });
|
|
36563
|
+
}
|
|
36208
36564
|
case "chat.stream.chunk": {
|
|
36209
36565
|
// Find the streaming message by ID and append text
|
|
36210
36566
|
var chunkDetail = action.detail;
|
|
36211
36567
|
if (state.streamingMessageId === chunkDetail.messageId) {
|
|
36212
36568
|
// Find the streaming message by ID (not by index - other messages could be inserted)
|
|
36213
|
-
var streamingIndex_1 = state.chats
|
|
36569
|
+
var streamingIndex_1 = findLastStreamingIndex(state.chats);
|
|
36214
36570
|
if (streamingIndex_1 >= 0) {
|
|
36215
36571
|
var chunkText_1 = chunkDetail.text;
|
|
36216
36572
|
return __assign(__assign({}, state), { lastTimestamp: chunkDetail === null || chunkDetail === void 0 ? void 0 : chunkDetail.timestamp, chats: state.chats.map(function (chat, index) {
|
|
@@ -36230,7 +36586,7 @@ function update(state, action) {
|
|
|
36230
36586
|
var toolStartDetail_1 = action.detail;
|
|
36231
36587
|
if (state.streamingMessageId === toolStartDetail_1.messageId) {
|
|
36232
36588
|
// Find the streaming message by ID (not by index - other messages could be inserted)
|
|
36233
|
-
var streamingIndex_2 = state.chats
|
|
36589
|
+
var streamingIndex_2 = findLastStreamingIndex(state.chats);
|
|
36234
36590
|
if (streamingIndex_2 >= 0) {
|
|
36235
36591
|
return __assign(__assign({}, state), { lastTimestamp: toolStartDetail_1.timestamp, chats: state.chats.map(function (chat, index) {
|
|
36236
36592
|
if (index === streamingIndex_2 && isChatMsgDetail(chat)) {
|
|
@@ -36260,7 +36616,7 @@ function update(state, action) {
|
|
|
36260
36616
|
var toolEndDetail_1 = action.detail;
|
|
36261
36617
|
if (state.streamingMessageId === toolEndDetail_1.messageId) {
|
|
36262
36618
|
// Find the streaming message by ID (not by index - other messages could be inserted)
|
|
36263
|
-
var streamingIndex_3 = state.chats
|
|
36619
|
+
var streamingIndex_3 = findLastStreamingIndex(state.chats);
|
|
36264
36620
|
if (streamingIndex_3 >= 0) {
|
|
36265
36621
|
return __assign(__assign({}, state), { lastTimestamp: toolEndDetail_1.timestamp, chats: state.chats.map(function (chat, index) {
|
|
36266
36622
|
var _a;
|
|
@@ -36289,7 +36645,7 @@ function update(state, action) {
|
|
|
36289
36645
|
var endDetail = action.detail;
|
|
36290
36646
|
if (state.streamingMessageId === endDetail.messageId) {
|
|
36291
36647
|
// Find the streaming message by ID (not by index - other messages could be inserted)
|
|
36292
|
-
var streamingIndex_4 = state.chats
|
|
36648
|
+
var streamingIndex_4 = findLastStreamingIndex(state.chats);
|
|
36293
36649
|
var endStreamingChat = streamingIndex_4 >= 0 ? state.chats[streamingIndex_4] : undefined;
|
|
36294
36650
|
// Verify it's the right message using type guard
|
|
36295
36651
|
if (streamingIndex_4 >= 0 && endStreamingChat && isChatMsgDetail(endStreamingChat)) {
|
|
@@ -36308,6 +36664,51 @@ function update(state, action) {
|
|
|
36308
36664
|
}
|
|
36309
36665
|
return state;
|
|
36310
36666
|
}
|
|
36667
|
+
case "chat.stream.abort": {
|
|
36668
|
+
// Finalize a streaming message interrupted by connection loss during sleep.
|
|
36669
|
+
// Keeps whatever partial text is already in the message.
|
|
36670
|
+
var abortDetail = action.detail;
|
|
36671
|
+
if (state.streamingMessageId === abortDetail.messageId) {
|
|
36672
|
+
var streamingIndex_5 = findLastStreamingIndex(state.chats);
|
|
36673
|
+
if (streamingIndex_5 >= 0) {
|
|
36674
|
+
return __assign(__assign({}, state), { lastTimestamp: abortDetail.timestamp, streamingMessageId: undefined, chats: state.chats.map(function (chat, index) {
|
|
36675
|
+
if (index === streamingIndex_5 && isChatMsgDetail(chat)) {
|
|
36676
|
+
return __assign(__assign({}, chat), { isStreaming: false, wasStreamed: true });
|
|
36677
|
+
}
|
|
36678
|
+
return chat;
|
|
36679
|
+
}) });
|
|
36680
|
+
}
|
|
36681
|
+
}
|
|
36682
|
+
return state;
|
|
36683
|
+
}
|
|
36684
|
+
case "chat.stream.resume": {
|
|
36685
|
+
// Re-enable streaming on the last aborted bot message after replay reconnect.
|
|
36686
|
+
// Targets the most recent wasStreamed message that is NOT currently streaming
|
|
36687
|
+
// (i.e., the one finalized by chat.stream.abort). Updates streamingMessageId
|
|
36688
|
+
// so replayed chunks route to the existing message.
|
|
36689
|
+
// Guard: only resume if no stream is currently active.
|
|
36690
|
+
var resumeDetail = action.detail;
|
|
36691
|
+
if (state.streamingMessageId) {
|
|
36692
|
+
return state;
|
|
36693
|
+
}
|
|
36694
|
+
var lastBotIndex_1 = -1;
|
|
36695
|
+
for (var i = state.chats.length - 1; i >= 0; i--) {
|
|
36696
|
+
var chat = state.chats[i];
|
|
36697
|
+
if (isChatMsgDetail(chat) && chat.wasStreamed && !chat.isStreaming) {
|
|
36698
|
+
lastBotIndex_1 = i;
|
|
36699
|
+
break;
|
|
36700
|
+
}
|
|
36701
|
+
}
|
|
36702
|
+
if (lastBotIndex_1 >= 0) {
|
|
36703
|
+
return __assign(__assign({}, state), { lastTimestamp: resumeDetail.timestamp, streamingMessageId: resumeDetail.messageId, chats: state.chats.map(function (chat, index) {
|
|
36704
|
+
if (index === lastBotIndex_1 && isChatMsgDetail(chat)) {
|
|
36705
|
+
return __assign(__assign({}, chat), { isStreaming: true, wasStreamed: false });
|
|
36706
|
+
}
|
|
36707
|
+
return chat;
|
|
36708
|
+
}) });
|
|
36709
|
+
}
|
|
36710
|
+
return state;
|
|
36711
|
+
}
|
|
36311
36712
|
default:
|
|
36312
36713
|
return state;
|
|
36313
36714
|
}
|