@xapp/chat-widget 1.88.0 → 1.91.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +1 -1
- package/dist/index.es.js +316 -30
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +316 -30
- package/dist/index.js.map +1 -1
- package/dist/store/ChatAction.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.js +4 -4
- package/dist/xapp-chat-widget.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1900,7 +1900,15 @@ function sendGreeting(PRECHAT_FORM_ENABLED) {
|
|
|
1900
1900
|
}
|
|
1901
1901
|
|
|
1902
1902
|
function sendMessage(msg) {
|
|
1903
|
-
return function (chatServer) { return function () {
|
|
1903
|
+
return function (chatServer) { return function (dispatch, getState) {
|
|
1904
|
+
var state = getState();
|
|
1905
|
+
// Don't allow visitor to send message if offline
|
|
1906
|
+
if (state.accountStatus === "offline" && !state.isChatting)
|
|
1907
|
+
return;
|
|
1908
|
+
// Write user message to Redux before server send so it appears above streaming response
|
|
1909
|
+
if (msg.msg) {
|
|
1910
|
+
dispatch(writeMessage(msg.msg, state.visitor, +new Date()));
|
|
1911
|
+
}
|
|
1904
1912
|
chatServer.sendChatMsgRequest(msg, function () { });
|
|
1905
1913
|
}; };
|
|
1906
1914
|
}
|
|
@@ -2985,7 +2993,9 @@ function setSessionId(sessionId) {
|
|
|
2985
2993
|
|
|
2986
2994
|
// Endpoint path constants
|
|
2987
2995
|
var HOMEOWNER_ENDPOINT_PATH = "/chat/stream";
|
|
2996
|
+
var HOMEOWNER_CLOSE_ENDPOINT_PATH = "/chat/close";
|
|
2988
2997
|
var BUSINESS_OWNER_ENDPOINT_PATH = "/api/chat/stream";
|
|
2998
|
+
var BUSINESS_OWNER_CLOSE_ENDPOINT_PATH = "/api/chat/close";
|
|
2989
2999
|
// Default retry configuration for MCP connections
|
|
2990
3000
|
var DEFAULT_MCP_RETRY_CONFIG = [
|
|
2991
3001
|
{ retry: 0, delay: 0, text: "" }, // Initial attempt (silent)
|
|
@@ -3139,6 +3149,22 @@ function clearStoredLastSeq(sessionId) {
|
|
|
3139
3149
|
// sessionStorage may be unavailable
|
|
3140
3150
|
}
|
|
3141
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
|
+
}
|
|
3142
3168
|
/**
|
|
3143
3169
|
* Gets the current page context from browser globals.
|
|
3144
3170
|
* If an override is provided, uses that instead (useful for testing).
|
|
@@ -3147,7 +3173,7 @@ function clearStoredLastSeq(sessionId) {
|
|
|
3147
3173
|
* may contain sensitive data (tokens, PII). Only origin + pathname are sent.
|
|
3148
3174
|
*/
|
|
3149
3175
|
function getPageContext(override) {
|
|
3150
|
-
var localTime =
|
|
3176
|
+
var localTime = getLocalISOString();
|
|
3151
3177
|
// If override provided, merge with defaults (override wins)
|
|
3152
3178
|
// Overrides are typically for testing and may include full URLs
|
|
3153
3179
|
if (override && Object.keys(override).length > 0) {
|
|
@@ -3199,12 +3225,15 @@ var MCPChat = /** @class */ (function () {
|
|
|
3199
3225
|
this.isValidatingSession = false;
|
|
3200
3226
|
// Tool call tracking for debug mode
|
|
3201
3227
|
this.currentToolCalls = [];
|
|
3202
|
-
// TODO: isReplaying and lastSeq are tracked for future replay support.
|
|
3203
|
-
// Wire up replay requests in wakeup() once the server endpoint supports it.
|
|
3204
3228
|
this.isReplaying = false;
|
|
3229
|
+
// Sleep/wakeup state for tab backgrounding recovery
|
|
3230
|
+
this.isSleeping = false;
|
|
3231
|
+
this.isRequestingReplay = false;
|
|
3232
|
+
this.needsReplayOnWakeup = false;
|
|
3205
3233
|
// True when userId was assigned by the server (via connected SSE event),
|
|
3206
3234
|
// meaning it should not be overwritten by the default client fingerprint.
|
|
3207
3235
|
this.userIdIsServerAssigned = false;
|
|
3236
|
+
this.sentMessageCount = 0;
|
|
3208
3237
|
this.config = config;
|
|
3209
3238
|
this.options = options;
|
|
3210
3239
|
this.configurableMessages = options === null || options === void 0 ? void 0 : options.configurableMessages;
|
|
@@ -3240,6 +3269,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
3240
3269
|
}
|
|
3241
3270
|
MCPChat.prototype.init = function (dispatch) {
|
|
3242
3271
|
this.dispatch = dispatch;
|
|
3272
|
+
this.sessionStartTime = Date.now();
|
|
3243
3273
|
var mode = this.config.mode || "homeowner";
|
|
3244
3274
|
log("MCPChat initialized with mode: ".concat(mode));
|
|
3245
3275
|
// Warn if not using HTTPS with authentication
|
|
@@ -3320,7 +3350,9 @@ var MCPChat = /** @class */ (function () {
|
|
|
3320
3350
|
// Page context
|
|
3321
3351
|
pageContext: getPageContext(this.config.pageContextOverride),
|
|
3322
3352
|
// Client timestamp for latency measurement and clock drift detection
|
|
3323
|
-
clientTime: new Date().toISOString()
|
|
3353
|
+
clientTime: new Date().toISOString(),
|
|
3354
|
+
// Last sequence number for replay on reconnect
|
|
3355
|
+
lastSeq: this.lastSeq }, additionalData);
|
|
3324
3356
|
};
|
|
3325
3357
|
/**
|
|
3326
3358
|
* Initializes a new session by calling the endpoint with { newSession: true }.
|
|
@@ -3393,6 +3425,14 @@ var MCPChat = /** @class */ (function () {
|
|
|
3393
3425
|
// homeowner mode - serverUrl should already include appId
|
|
3394
3426
|
return "".concat(serverUrl).concat(HOMEOWNER_ENDPOINT_PATH);
|
|
3395
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
|
+
};
|
|
3396
3436
|
MCPChat.prototype.sendNewMessage = function (msg, user) {
|
|
3397
3437
|
log("SERVER: new message: ".concat(JSON.stringify(msg)));
|
|
3398
3438
|
this.dispatch({
|
|
@@ -3543,6 +3583,30 @@ var MCPChat = /** @class */ (function () {
|
|
|
3543
3583
|
},
|
|
3544
3584
|
});
|
|
3545
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
|
+
};
|
|
3546
3610
|
MCPChat.prototype.dispatchToolStart = function (params) {
|
|
3547
3611
|
this.dispatch({
|
|
3548
3612
|
type: "chat",
|
|
@@ -3856,7 +3920,10 @@ var MCPChat = /** @class */ (function () {
|
|
|
3856
3920
|
attempt++;
|
|
3857
3921
|
return [3 /*break*/, 3];
|
|
3858
3922
|
case 14:
|
|
3859
|
-
if (
|
|
3923
|
+
if (success) {
|
|
3924
|
+
this.sentMessageCount++;
|
|
3925
|
+
}
|
|
3926
|
+
else {
|
|
3860
3927
|
log("All MCP retry attempts exhausted");
|
|
3861
3928
|
}
|
|
3862
3929
|
return [2 /*return*/];
|
|
@@ -3908,6 +3975,10 @@ var MCPChat = /** @class */ (function () {
|
|
|
3908
3975
|
: {})),
|
|
3909
3976
|
body: JSON.stringify(body),
|
|
3910
3977
|
signal: _this.abortController.signal,
|
|
3978
|
+
// Keep the connection open when the tab is hidden. Without this,
|
|
3979
|
+
// fetchEventSource closes and re-POSTs on visibility change, which
|
|
3980
|
+
// creates duplicate streaming messages and out-of-order responses.
|
|
3981
|
+
openWhenHidden: true,
|
|
3911
3982
|
onopen: function (response) { return __awaiter$1(_this, void 0, void 0, function () {
|
|
3912
3983
|
var error;
|
|
3913
3984
|
return __generator$1(this, function (_a) {
|
|
@@ -3924,6 +3995,11 @@ var MCPChat = /** @class */ (function () {
|
|
|
3924
3995
|
});
|
|
3925
3996
|
}); },
|
|
3926
3997
|
onmessage: function (event) {
|
|
3998
|
+
// Guard: ignore events from stale/aborted requests
|
|
3999
|
+
if (_this.currentMessageId !== messageId) {
|
|
4000
|
+
log("[SSE] Ignoring stale event for messageId: ".concat(messageId, ", current: ").concat(_this.currentMessageId));
|
|
4001
|
+
return;
|
|
4002
|
+
}
|
|
3927
4003
|
log("SSE event: ".concat(event.event, ", data: ").concat(event.data));
|
|
3928
4004
|
var eventType = event.event || "message";
|
|
3929
4005
|
var data = {};
|
|
@@ -3963,11 +4039,17 @@ var MCPChat = /** @class */ (function () {
|
|
|
3963
4039
|
storeIdentity(data.userId, data.sessionId);
|
|
3964
4040
|
log("[SSE] Identity from connected - userId: ".concat(data.userId, ", sessionId: ").concat(data.sessionId));
|
|
3965
4041
|
}
|
|
3966
|
-
// Always start streaming - server sends greeting for both new and returning users
|
|
3967
|
-
// (resumeSession requests always get a personalized greeting)
|
|
3968
4042
|
_this.currentToolCalls = [];
|
|
3969
4043
|
_this.stopTyping();
|
|
3970
|
-
|
|
4044
|
+
if (data.isReplay) {
|
|
4045
|
+
// Replay connection — resume into the existing partial
|
|
4046
|
+
// message instead of creating a duplicate placeholder
|
|
4047
|
+
_this.dispatchStreamResume(messageId);
|
|
4048
|
+
}
|
|
4049
|
+
else {
|
|
4050
|
+
// Normal connection — create new streaming placeholder
|
|
4051
|
+
_this.dispatchStreamStart(messageId);
|
|
4052
|
+
}
|
|
3971
4053
|
_this.startStreamThrottle(messageId);
|
|
3972
4054
|
break;
|
|
3973
4055
|
case "session_expired":
|
|
@@ -3990,9 +4072,9 @@ var MCPChat = /** @class */ (function () {
|
|
|
3990
4072
|
if (data.text) {
|
|
3991
4073
|
log("[SSE] Text chunk received (".concat(data.text.length, " chars), total chunks: ").concat(textChunks.length + 1, ", isReplay: ").concat(data.isReplay));
|
|
3992
4074
|
textChunks.push(data.text);
|
|
3993
|
-
if (data.isReplay) {
|
|
3994
|
-
// Replayed text — dispatch immediately
|
|
3995
|
-
//
|
|
4075
|
+
if (data.isReplay || _this.isSleeping) {
|
|
4076
|
+
// Replayed text or tab hidden — dispatch immediately
|
|
4077
|
+
// (no throttle timer running in either case)
|
|
3996
4078
|
_this.dispatchStreamChunk(messageId, data.text);
|
|
3997
4079
|
}
|
|
3998
4080
|
else {
|
|
@@ -4089,6 +4171,7 @@ var MCPChat = /** @class */ (function () {
|
|
|
4089
4171
|
text: accumulatedText || data.response.response || "",
|
|
4090
4172
|
options: options,
|
|
4091
4173
|
sources: sources,
|
|
4174
|
+
displays: data.response.displays,
|
|
4092
4175
|
};
|
|
4093
4176
|
// Capture tool calls before flushing (debug mode)
|
|
4094
4177
|
var toolCalls_1 = _this.config.debugMode ? __spreadArray$1([], _this.currentToolCalls, true) : undefined;
|
|
@@ -4126,7 +4209,20 @@ var MCPChat = /** @class */ (function () {
|
|
|
4126
4209
|
if (_this.currentMessageId === messageId && !hasError) {
|
|
4127
4210
|
hasError = true;
|
|
4128
4211
|
_this.isReplaying = false;
|
|
4129
|
-
_this.
|
|
4212
|
+
if (_this.isSleeping) {
|
|
4213
|
+
// Connection dropped while tab is hidden — don't treat as
|
|
4214
|
+
// normal error. Finalize partial message and request replay
|
|
4215
|
+
// when the user returns.
|
|
4216
|
+
log("[MCPChat] SSE error during sleep - will request replay on wakeup");
|
|
4217
|
+
_this.needsReplayOnWakeup = true;
|
|
4218
|
+
if (_this.streamingMessageIdForThrottle) {
|
|
4219
|
+
_this.dispatchStreamAbort(messageId);
|
|
4220
|
+
}
|
|
4221
|
+
_this.stopStreamThrottle();
|
|
4222
|
+
}
|
|
4223
|
+
else {
|
|
4224
|
+
_this.stopStreamThrottle();
|
|
4225
|
+
}
|
|
4130
4226
|
sseError = error instanceof Error ? error : new Error(String(error));
|
|
4131
4227
|
// Get status code if available
|
|
4132
4228
|
var statusCode = (error === null || error === void 0 ? void 0 : error.statusCode) || httpStatusCode;
|
|
@@ -4195,23 +4291,138 @@ var MCPChat = /** @class */ (function () {
|
|
|
4195
4291
|
}
|
|
4196
4292
|
};
|
|
4197
4293
|
MCPChat.prototype.sleep = function () {
|
|
4198
|
-
this.
|
|
4199
|
-
this.
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
this.
|
|
4294
|
+
this.isSleeping = true;
|
|
4295
|
+
if (this.streamingMessageIdForThrottle) {
|
|
4296
|
+
// Active stream in progress — keep SSE connection alive so the
|
|
4297
|
+
// response completes naturally while the tab is hidden.
|
|
4298
|
+
this.needsReplayOnWakeup = false;
|
|
4299
|
+
log("[MCPChat] sleep() - stream in progress, keeping SSE connection alive");
|
|
4300
|
+
// Flush any buffered text to Redux immediately
|
|
4301
|
+
if (this.streamBuffer.length > 0) {
|
|
4302
|
+
this.dispatchStreamChunk(this.streamingMessageIdForThrottle, this.streamBuffer);
|
|
4303
|
+
this.streamBuffer = "";
|
|
4304
|
+
}
|
|
4305
|
+
// Stop the visual throttle timer (browsers throttle timers anyway)
|
|
4306
|
+
// but keep streamingMessageIdForThrottle so incoming chunks can be dispatched
|
|
4307
|
+
if (this.streamThrottleTimer) {
|
|
4308
|
+
clearInterval(this.streamThrottleTimer);
|
|
4309
|
+
this.streamThrottleTimer = undefined;
|
|
4310
|
+
}
|
|
4311
|
+
if (this.visibilityHandler) {
|
|
4312
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
4313
|
+
this.visibilityHandler = undefined;
|
|
4314
|
+
}
|
|
4315
|
+
}
|
|
4316
|
+
else {
|
|
4317
|
+
// No active stream — safe to abort the idle connection
|
|
4318
|
+
this.stopStreamThrottle();
|
|
4319
|
+
this.isReplaying = false;
|
|
4320
|
+
if (this.abortController) {
|
|
4321
|
+
this.abortController.abort();
|
|
4322
|
+
this.abortController = undefined;
|
|
4323
|
+
}
|
|
4203
4324
|
}
|
|
4204
4325
|
};
|
|
4205
4326
|
MCPChat.prototype.wakeup = function () {
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4327
|
+
this.isSleeping = false;
|
|
4328
|
+
if (this.streamingMessageIdForThrottle && this.abortController) {
|
|
4329
|
+
// Stream is still in progress — restart the visual throttle
|
|
4330
|
+
log("[MCPChat] wakeup() - stream still in progress, restarting throttle");
|
|
4331
|
+
// Flush accumulated buffer before startStreamThrottle (which resets it)
|
|
4332
|
+
if (this.streamBuffer.length > 0) {
|
|
4333
|
+
this.dispatchStreamChunk(this.streamingMessageIdForThrottle, this.streamBuffer);
|
|
4334
|
+
this.streamBuffer = "";
|
|
4335
|
+
}
|
|
4336
|
+
this.startStreamThrottle(this.streamingMessageIdForThrottle);
|
|
4337
|
+
}
|
|
4338
|
+
else if (this.needsReplayOnWakeup) {
|
|
4339
|
+
this.needsReplayOnWakeup = false;
|
|
4340
|
+
if (this.lastSeq !== undefined && this._sessionId) {
|
|
4341
|
+
// Connection dropped during sleep — request replay of missed events.
|
|
4342
|
+
// Don't set "online" yet — requestReplay is fire-and-forget and the
|
|
4343
|
+
// connection status will be updated by the SSE onopen handler.
|
|
4344
|
+
log("[MCPChat] wakeup() - connection lost during sleep, requesting replay");
|
|
4345
|
+
this.requestReplay();
|
|
4346
|
+
return;
|
|
4347
|
+
}
|
|
4348
|
+
// Cannot replay — missing lastSeq or sessionId
|
|
4349
|
+
err("[MCPChat] wakeup() - cannot replay, missing lastSeq (".concat(this.lastSeq, ") or sessionId (").concat(this._sessionId, ")"));
|
|
4350
|
+
}
|
|
4210
4351
|
if (this._sessionId) {
|
|
4211
4352
|
log("[MCPChat] Wakeup - session exists, lastSeq: ".concat(this.lastSeq));
|
|
4212
4353
|
this.setConnectionStatus("online");
|
|
4213
4354
|
}
|
|
4214
4355
|
};
|
|
4356
|
+
MCPChat.prototype.requestReplay = function () {
|
|
4357
|
+
return __awaiter$1(this, void 0, void 0, function () {
|
|
4358
|
+
var error_5;
|
|
4359
|
+
return __generator$1(this, function (_a) {
|
|
4360
|
+
switch (_a.label) {
|
|
4361
|
+
case 0:
|
|
4362
|
+
if (this.isRequestingReplay) {
|
|
4363
|
+
log("[MCPChat] Replay already in progress, skipping");
|
|
4364
|
+
return [2 /*return*/];
|
|
4365
|
+
}
|
|
4366
|
+
this.isRequestingReplay = true;
|
|
4367
|
+
this.typing();
|
|
4368
|
+
_a.label = 1;
|
|
4369
|
+
case 1:
|
|
4370
|
+
_a.trys.push([1, 3, 4, 5]);
|
|
4371
|
+
// lastSeq is already included by buildRequestPayload
|
|
4372
|
+
return [4 /*yield*/, this.performSSERequest(this.buildRequestPayload())];
|
|
4373
|
+
case 2:
|
|
4374
|
+
// lastSeq is already included by buildRequestPayload
|
|
4375
|
+
_a.sent();
|
|
4376
|
+
log("[MCPChat] Replay request completed");
|
|
4377
|
+
return [3 /*break*/, 5];
|
|
4378
|
+
case 3:
|
|
4379
|
+
error_5 = _a.sent();
|
|
4380
|
+
err("[MCPChat] Replay request failed: ".concat(error_5));
|
|
4381
|
+
this.setConnectionStatus("offline");
|
|
4382
|
+
return [3 /*break*/, 5];
|
|
4383
|
+
case 4:
|
|
4384
|
+
this.stopTyping();
|
|
4385
|
+
this.isRequestingReplay = false;
|
|
4386
|
+
return [7 /*endfinally*/];
|
|
4387
|
+
case 5: return [2 /*return*/];
|
|
4388
|
+
}
|
|
4389
|
+
});
|
|
4390
|
+
});
|
|
4391
|
+
};
|
|
4392
|
+
/**
|
|
4393
|
+
* Sends a best-effort close notification to the agent server.
|
|
4394
|
+
* Uses navigator.sendBeacon() so the request survives page unload.
|
|
4395
|
+
* Called from the beforeunload event handler and the widget close button.
|
|
4396
|
+
*/
|
|
4397
|
+
MCPChat.prototype.notifyDisconnecting = function (reason) {
|
|
4398
|
+
if (reason === void 0) { reason = "close_tab"; }
|
|
4399
|
+
if (!this._sessionId) {
|
|
4400
|
+
return;
|
|
4401
|
+
}
|
|
4402
|
+
var endpoint = this.getCloseEndpoint();
|
|
4403
|
+
var payload = JSON.stringify({
|
|
4404
|
+
sessionId: this._sessionId,
|
|
4405
|
+
userId: this._userId || undefined,
|
|
4406
|
+
reason: reason,
|
|
4407
|
+
messageCount: this.sentMessageCount,
|
|
4408
|
+
durationMs: this.sessionStartTime !== undefined ? Date.now() - this.sessionStartTime : 0,
|
|
4409
|
+
});
|
|
4410
|
+
log("[MCPChat] notifyDisconnecting: reason=".concat(reason, ", endpoint=").concat(endpoint));
|
|
4411
|
+
// For widget_close, the page is not unloading so fetch() can be used — it supports
|
|
4412
|
+
// auth headers (needed for business_owner mode) unlike sendBeacon.
|
|
4413
|
+
if (reason === "widget_close" && typeof fetch !== "undefined") {
|
|
4414
|
+
var headers = { "Content-Type": "application/json" };
|
|
4415
|
+
if (this.config.authToken) {
|
|
4416
|
+
headers["Authorization"] = "Bearer ".concat(this.config.authToken);
|
|
4417
|
+
}
|
|
4418
|
+
fetch(endpoint, { method: "POST", headers: headers, body: payload, keepalive: true });
|
|
4419
|
+
return;
|
|
4420
|
+
}
|
|
4421
|
+
if (typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
4422
|
+
var blob = new Blob([payload], { type: "application/json" });
|
|
4423
|
+
navigator.sendBeacon(endpoint, blob);
|
|
4424
|
+
}
|
|
4425
|
+
};
|
|
4215
4426
|
MCPChat.prototype.startSession = function (sessionId) {
|
|
4216
4427
|
// userId priority:
|
|
4217
4428
|
// 1. Existing server-assigned userId (from localStorage or previous connected event)
|
|
@@ -35559,7 +35770,7 @@ var ChatWidgetUI = function (props) {
|
|
|
35559
35770
|
var handleBeforeUnload = function () {
|
|
35560
35771
|
log("beforeunload - notifying router of user disconnecting");
|
|
35561
35772
|
if (chatServer.notifyDisconnecting) {
|
|
35562
|
-
chatServer.notifyDisconnecting();
|
|
35773
|
+
chatServer.notifyDisconnecting("close_tab");
|
|
35563
35774
|
}
|
|
35564
35775
|
};
|
|
35565
35776
|
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
@@ -35713,6 +35924,9 @@ var ChatWidgetUI = function (props) {
|
|
|
35713
35924
|
}
|
|
35714
35925
|
/** Called when cancel is clicked */
|
|
35715
35926
|
function handleCancelClick() {
|
|
35927
|
+
if (chatServer === null || chatServer === void 0 ? void 0 : chatServer.notifyDisconnecting) {
|
|
35928
|
+
chatServer.notifyDisconnecting("widget_close");
|
|
35929
|
+
}
|
|
35716
35930
|
// First reset to clear all state
|
|
35717
35931
|
innerDispatch(reset());
|
|
35718
35932
|
// Then set hasInteracted to prevent CTA from showing after cancel
|
|
@@ -36067,6 +36281,20 @@ function resetReducer(state) {
|
|
|
36067
36281
|
function isChatSystemMessage(chat) {
|
|
36068
36282
|
return chat.type === "chat.systemmessage";
|
|
36069
36283
|
}
|
|
36284
|
+
/**
|
|
36285
|
+
* Find the LAST streaming message index. We must search from the end because
|
|
36286
|
+
* orphan isStreaming messages from interrupted streams may exist earlier in the
|
|
36287
|
+
* array. The active streaming message is always the most recently inserted one.
|
|
36288
|
+
*/
|
|
36289
|
+
function findLastStreamingIndex(chats) {
|
|
36290
|
+
for (var i = chats.length - 1; i >= 0; i--) {
|
|
36291
|
+
var chat = chats[i];
|
|
36292
|
+
if (isChatMsgDetail(chat) && chat.isStreaming) {
|
|
36293
|
+
return i;
|
|
36294
|
+
}
|
|
36295
|
+
}
|
|
36296
|
+
return -1;
|
|
36297
|
+
}
|
|
36070
36298
|
// todo: create reducer (requires redux-thunk dependency)
|
|
36071
36299
|
function appendMessageToState(state, msg) {
|
|
36072
36300
|
state.chats = joinMessages(state.chats, msg);
|
|
@@ -36184,8 +36412,19 @@ function update(state, action) {
|
|
|
36184
36412
|
case "chat.typing":
|
|
36185
36413
|
var agent = state.agents[action.detail.user.nick];
|
|
36186
36414
|
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)) });
|
|
36187
|
-
case "chat.stream.start":
|
|
36188
|
-
//
|
|
36415
|
+
case "chat.stream.start": {
|
|
36416
|
+
// Clean up any orphan streaming messages from interrupted streams
|
|
36417
|
+
// before creating the new placeholder.
|
|
36418
|
+
var cleanedChats = newState.chats.map(function (chat) {
|
|
36419
|
+
if (isChatMsgDetail(chat) && chat.isStreaming) {
|
|
36420
|
+
return __assign(__assign({}, chat), { isStreaming: false, wasStreamed: true });
|
|
36421
|
+
}
|
|
36422
|
+
return chat;
|
|
36423
|
+
});
|
|
36424
|
+
// Add placeholder message with streaming state.
|
|
36425
|
+
// Use joinMessages (insertSorted by timestamp) instead of append to
|
|
36426
|
+
// guarantee correct ordering even if events arrive out of order
|
|
36427
|
+
// (e.g., after an SSE abort/retry during tab backgrounding).
|
|
36189
36428
|
var streamingMsg = {
|
|
36190
36429
|
type: "chat.msg",
|
|
36191
36430
|
user: action.detail.user,
|
|
@@ -36195,13 +36434,15 @@ function update(state, action) {
|
|
|
36195
36434
|
},
|
|
36196
36435
|
isStreaming: true,
|
|
36197
36436
|
};
|
|
36198
|
-
|
|
36437
|
+
var newChats = joinMessages(cleanedChats, streamingMsg);
|
|
36438
|
+
return __assign(__assign({}, newState), { chats: newChats, streamingMessageId: action.detail.messageId });
|
|
36439
|
+
}
|
|
36199
36440
|
case "chat.stream.chunk": {
|
|
36200
36441
|
// Find the streaming message by ID and append text
|
|
36201
36442
|
var chunkDetail = action.detail;
|
|
36202
36443
|
if (state.streamingMessageId === chunkDetail.messageId) {
|
|
36203
36444
|
// Find the streaming message by ID (not by index - other messages could be inserted)
|
|
36204
|
-
var streamingIndex_1 = state.chats
|
|
36445
|
+
var streamingIndex_1 = findLastStreamingIndex(state.chats);
|
|
36205
36446
|
if (streamingIndex_1 >= 0) {
|
|
36206
36447
|
var chunkText_1 = chunkDetail.text;
|
|
36207
36448
|
return __assign(__assign({}, state), { lastTimestamp: chunkDetail === null || chunkDetail === void 0 ? void 0 : chunkDetail.timestamp, chats: state.chats.map(function (chat, index) {
|
|
@@ -36221,7 +36462,7 @@ function update(state, action) {
|
|
|
36221
36462
|
var toolStartDetail_1 = action.detail;
|
|
36222
36463
|
if (state.streamingMessageId === toolStartDetail_1.messageId) {
|
|
36223
36464
|
// Find the streaming message by ID (not by index - other messages could be inserted)
|
|
36224
|
-
var streamingIndex_2 = state.chats
|
|
36465
|
+
var streamingIndex_2 = findLastStreamingIndex(state.chats);
|
|
36225
36466
|
if (streamingIndex_2 >= 0) {
|
|
36226
36467
|
return __assign(__assign({}, state), { lastTimestamp: toolStartDetail_1.timestamp, chats: state.chats.map(function (chat, index) {
|
|
36227
36468
|
if (index === streamingIndex_2 && isChatMsgDetail(chat)) {
|
|
@@ -36251,7 +36492,7 @@ function update(state, action) {
|
|
|
36251
36492
|
var toolEndDetail_1 = action.detail;
|
|
36252
36493
|
if (state.streamingMessageId === toolEndDetail_1.messageId) {
|
|
36253
36494
|
// Find the streaming message by ID (not by index - other messages could be inserted)
|
|
36254
|
-
var streamingIndex_3 = state.chats
|
|
36495
|
+
var streamingIndex_3 = findLastStreamingIndex(state.chats);
|
|
36255
36496
|
if (streamingIndex_3 >= 0) {
|
|
36256
36497
|
return __assign(__assign({}, state), { lastTimestamp: toolEndDetail_1.timestamp, chats: state.chats.map(function (chat, index) {
|
|
36257
36498
|
var _a;
|
|
@@ -36280,7 +36521,7 @@ function update(state, action) {
|
|
|
36280
36521
|
var endDetail = action.detail;
|
|
36281
36522
|
if (state.streamingMessageId === endDetail.messageId) {
|
|
36282
36523
|
// Find the streaming message by ID (not by index - other messages could be inserted)
|
|
36283
|
-
var streamingIndex_4 = state.chats
|
|
36524
|
+
var streamingIndex_4 = findLastStreamingIndex(state.chats);
|
|
36284
36525
|
var endStreamingChat = streamingIndex_4 >= 0 ? state.chats[streamingIndex_4] : undefined;
|
|
36285
36526
|
// Verify it's the right message using type guard
|
|
36286
36527
|
if (streamingIndex_4 >= 0 && endStreamingChat && isChatMsgDetail(endStreamingChat)) {
|
|
@@ -36299,6 +36540,51 @@ function update(state, action) {
|
|
|
36299
36540
|
}
|
|
36300
36541
|
return state;
|
|
36301
36542
|
}
|
|
36543
|
+
case "chat.stream.abort": {
|
|
36544
|
+
// Finalize a streaming message interrupted by connection loss during sleep.
|
|
36545
|
+
// Keeps whatever partial text is already in the message.
|
|
36546
|
+
var abortDetail = action.detail;
|
|
36547
|
+
if (state.streamingMessageId === abortDetail.messageId) {
|
|
36548
|
+
var streamingIndex_5 = findLastStreamingIndex(state.chats);
|
|
36549
|
+
if (streamingIndex_5 >= 0) {
|
|
36550
|
+
return __assign(__assign({}, state), { lastTimestamp: abortDetail.timestamp, streamingMessageId: undefined, chats: state.chats.map(function (chat, index) {
|
|
36551
|
+
if (index === streamingIndex_5 && isChatMsgDetail(chat)) {
|
|
36552
|
+
return __assign(__assign({}, chat), { isStreaming: false, wasStreamed: true });
|
|
36553
|
+
}
|
|
36554
|
+
return chat;
|
|
36555
|
+
}) });
|
|
36556
|
+
}
|
|
36557
|
+
}
|
|
36558
|
+
return state;
|
|
36559
|
+
}
|
|
36560
|
+
case "chat.stream.resume": {
|
|
36561
|
+
// Re-enable streaming on the last aborted bot message after replay reconnect.
|
|
36562
|
+
// Targets the most recent wasStreamed message that is NOT currently streaming
|
|
36563
|
+
// (i.e., the one finalized by chat.stream.abort). Updates streamingMessageId
|
|
36564
|
+
// so replayed chunks route to the existing message.
|
|
36565
|
+
// Guard: only resume if no stream is currently active.
|
|
36566
|
+
var resumeDetail = action.detail;
|
|
36567
|
+
if (state.streamingMessageId) {
|
|
36568
|
+
return state;
|
|
36569
|
+
}
|
|
36570
|
+
var lastBotIndex_1 = -1;
|
|
36571
|
+
for (var i = state.chats.length - 1; i >= 0; i--) {
|
|
36572
|
+
var chat = state.chats[i];
|
|
36573
|
+
if (isChatMsgDetail(chat) && chat.wasStreamed && !chat.isStreaming) {
|
|
36574
|
+
lastBotIndex_1 = i;
|
|
36575
|
+
break;
|
|
36576
|
+
}
|
|
36577
|
+
}
|
|
36578
|
+
if (lastBotIndex_1 >= 0) {
|
|
36579
|
+
return __assign(__assign({}, state), { lastTimestamp: resumeDetail.timestamp, streamingMessageId: resumeDetail.messageId, chats: state.chats.map(function (chat, index) {
|
|
36580
|
+
if (index === lastBotIndex_1 && isChatMsgDetail(chat)) {
|
|
36581
|
+
return __assign(__assign({}, chat), { isStreaming: true, wasStreamed: false });
|
|
36582
|
+
}
|
|
36583
|
+
return chat;
|
|
36584
|
+
}) });
|
|
36585
|
+
}
|
|
36586
|
+
return state;
|
|
36587
|
+
}
|
|
36302
36588
|
default:
|
|
36303
36589
|
return state;
|
|
36304
36590
|
}
|