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