@carbon/ai-chat 0.1.1-alpha5 → 0.1.1-react17
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/App.js +933 -55468
- package/dist/Carousel.js +1 -5705
- package/dist/Chat.js +1 -8339
- package/dist/GenesysMessengerServiceDesk.js +1 -579
- package/dist/HumanAgentServiceImpl.js +1 -1122
- package/dist/NiceDFOServiceDesk.js +2 -2097
- package/dist/PDFViewerContainer.js +2 -27439
- package/dist/SFServiceDesk.js +1 -1012
- package/dist/ServiceDeskImpl.js +1 -72
- package/dist/ZendeskServiceDesk.js +1 -649
- package/dist/_commonjsHelpers.js +1 -33
- package/dist/_node-resolve_empty.js +1 -25
- package/dist/agentActions.js +1 -187
- package/dist/aiChatEntry.js +1 -28
- package/dist/aiChatEntry2.js +4 -7022
- package/dist/anonymousUserIDStorage.js +2 -250
- package/dist/ar-dz.js +1 -55
- package/dist/ar-kw.js +1 -55
- package/dist/ar-ly.js +1 -55
- package/dist/ar-ma.js +1 -55
- package/dist/ar-sa.js +1 -55
- package/dist/ar-tn.js +1 -55
- package/dist/ar.js +1 -55
- package/dist/ar2.js +1 -470
- package/dist/cs.js +1 -55
- package/dist/cs2.js +1 -470
- package/dist/de-at.js +1 -55
- package/dist/de-ch.js +1 -55
- package/dist/de.js +1 -55
- package/dist/de2.js +1 -470
- package/dist/domUtils.js +2 -820
- package/dist/en-au.js +1 -55
- package/dist/en-ca.js +1 -55
- package/dist/en-gb.js +1 -55
- package/dist/en-ie.js +1 -55
- package/dist/en-il.js +1 -55
- package/dist/en-nz.js +1 -55
- package/dist/es-do.js +1 -55
- package/dist/es-us.js +1 -55
- package/dist/es.js +1 -55
- package/dist/es2.js +1 -470
- package/dist/export.js +1 -25
- package/dist/export.legacy.js +1 -25
- package/dist/fontUtils.js +1 -1036
- package/dist/fr-ca.js +1 -55
- package/dist/fr-ch.js +1 -55
- package/dist/fr.js +1 -55
- package/dist/fr2.js +1 -470
- package/dist/humanAgentUtils.js +1 -1393
- package/dist/it-ch.js +1 -55
- package/dist/it.js +1 -55
- package/dist/it2.js +1 -470
- package/dist/ja.js +1 -55
- package/dist/ja2.js +1 -470
- package/dist/jstz.min.js +1 -41
- package/dist/ko.js +1 -55
- package/dist/ko2.js +1 -470
- package/dist/messageUtils.js +1 -1338
- package/dist/mockServiceDesk.js +1 -851
- package/dist/moduleFederationPluginUtils.js +2 -5852
- package/dist/nl.js +1 -55
- package/dist/nl2.js +1 -470
- package/dist/pt-br.js +1 -55
- package/dist/pt-br2.js +1 -470
- package/dist/pt.js +1 -55
- package/dist/render.js +1 -88
- package/dist/web-components/cds-aichat-container/index.js +3 -3
- package/dist/web-components/cds-aichat-container/index.js.map +1 -1
- package/dist/web-components/cds-aichat-custom-element/index.js +2 -2
- package/dist/web-components/cds-aichat-custom-element/index.js.map +1 -1
- package/dist/zh-cn.js +1 -55
- package/dist/zh-tw.js +1 -55
- package/dist/zh-tw2.js +1 -470
- package/dist/zh.js +1 -470
- package/package.json +1 -1
|
@@ -1,1122 +1 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
(C) Copyright IBM Corp. 2017, 2024. All Rights Reserved.
|
|
5
|
-
|
|
6
|
-
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
7
|
-
in compliance with the License. You may obtain a copy of the License at
|
|
8
|
-
|
|
9
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
|
|
11
|
-
Unless required by applicable law or agreed to in writing, software distributed under the License
|
|
12
|
-
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
13
|
-
or implied. See the License for the specific language governing permissions and limitations under
|
|
14
|
-
the License.
|
|
15
|
-
|
|
16
|
-
@carbon/ai-chat 0.1.1-alpha5
|
|
17
|
-
|
|
18
|
-
Built: Oct 24 2024 10:59 am -04:00
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*/
|
|
23
|
-
import { aC as BuiltInServiceDesk, aG as addEndChatMessages, aH as addMessages, M as MessageErrorState, a as actions, f as createLocalMessageForInlineError, aI as toPair, aJ as createAgentLocalMessage, o as outputItemToLocalItem, r as resolvablePromise } from './humanAgentUtils.js';
|
|
24
|
-
import { ak as ErrorType, ad as ScreenShareState, f as createMessageRequestForText, al as createMessageRequestForFileUpload, A as AgentsOnlineStatus, X as AgentMessageType, j as createMessageResponseForText, g as addDefaultsToMessage } from './messageUtils.js';
|
|
25
|
-
import { l as cloneDeep, h as debugLog, Y as getRegionHostname, c as consoleError, a7 as resolveOrTimeout, k as deepFreeze, Q as FileStatusValue, m as merge, aa as VERSION } from './aiChatEntry2.js';
|
|
26
|
-
import { u as updateFilesUploadInProgress, s as setIsConnecting, o as endChat, i as inputItemToLocalItem, p as setIsScreenSharing, q as setIsReconnecting, r as setShowScreenShareRequest, t as updateCapabilities, v as setAgentAvailability, w as setAgentJoined, x as agentUpdateIsTyping, y as setAgentLeftChat, z as setPersistedServiceDeskState } from './agentActions.js';
|
|
27
|
-
import '@lit/react';
|
|
28
|
-
import 'react';
|
|
29
|
-
import 'lit';
|
|
30
|
-
import 'lit/decorators.js';
|
|
31
|
-
import 'react-dom';
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
*
|
|
35
|
-
* IBM Confidential
|
|
36
|
-
*
|
|
37
|
-
* (C) Copyright IBM Corp. 2021
|
|
38
|
-
*
|
|
39
|
-
* The source code for this program is not published or otherwise
|
|
40
|
-
* divested of its trade secrets, irrespective of what has been
|
|
41
|
-
* deposited with the U. S. Copyright Office
|
|
42
|
-
*
|
|
43
|
-
* US Government Users Restricted Rights - Use, duplication or
|
|
44
|
-
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
|
|
45
|
-
*
|
|
46
|
-
*/
|
|
47
|
-
const SESSION_HISTORY_KEY_DELIMITER = '::';
|
|
48
|
-
/**
|
|
49
|
-
* Specifies the strict order of the fields from which the key should be generated from.
|
|
50
|
-
*/
|
|
51
|
-
const SessionHistoryKeyOrder = [
|
|
52
|
-
'region',
|
|
53
|
-
'version',
|
|
54
|
-
'auth_code',
|
|
55
|
-
'session_id',
|
|
56
|
-
'integration_id',
|
|
57
|
-
'service_instance_id',
|
|
58
|
-
'subscription_id',
|
|
59
|
-
];
|
|
60
|
-
/**
|
|
61
|
-
* Object containing utility functions for serializing and deserializing the session history key with the pattern:
|
|
62
|
-
* ${ region }::${ version }::${ authCode }::${ sessionID }::${ integrationID }::${ serviceInstanceID }::${ subscriptionID }
|
|
63
|
-
*/
|
|
64
|
-
const SessionHistoryKeySerializer = {
|
|
65
|
-
serialize: (sessionHistoryKey) => {
|
|
66
|
-
const orderedSessionKeyValues = SessionHistoryKeyOrder.map((key) => sessionHistoryKey[key]);
|
|
67
|
-
return orderedSessionKeyValues.join(SESSION_HISTORY_KEY_DELIMITER);
|
|
68
|
-
},
|
|
69
|
-
deserialize: (serializedSessionHistoryKey) => {
|
|
70
|
-
const sessionHistoryKeyParams = serializedSessionHistoryKey.split(SESSION_HISTORY_KEY_DELIMITER);
|
|
71
|
-
const sessionHistoryKeyFields = {};
|
|
72
|
-
SessionHistoryKeyOrder.forEach((key, index) => {
|
|
73
|
-
sessionHistoryKeyFields[key] = sessionHistoryKeyParams[index];
|
|
74
|
-
});
|
|
75
|
-
return sessionHistoryKeyFields;
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
*
|
|
81
|
-
* IBM Confidential
|
|
82
|
-
*
|
|
83
|
-
* (C) Copyright IBM Corp. 2020, 2024
|
|
84
|
-
*
|
|
85
|
-
* The source code for this program is not published or otherwise
|
|
86
|
-
* divested of its trade secrets, irrespective of what has been
|
|
87
|
-
* deposited with the U. S. Copyright Office
|
|
88
|
-
*
|
|
89
|
-
* US Government Users Restricted Rights - Use, duplication or
|
|
90
|
-
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
|
|
91
|
-
*
|
|
92
|
-
*/
|
|
93
|
-
/**
|
|
94
|
-
* The amount of time to wait when a message is sent to the service desk before displaying a warning if the service
|
|
95
|
-
* desk doesn't indicate the message was received.
|
|
96
|
-
*/
|
|
97
|
-
const SEND_TIMEOUT_WARNING_MS = 3000;
|
|
98
|
-
/**
|
|
99
|
-
* The amount of time to wait when a message is sent to the service desk before displaying an error if the service
|
|
100
|
-
* desk doesn't indicate the message was received.
|
|
101
|
-
*/
|
|
102
|
-
const SEND_TIMEOUT_ERROR_MS = 20000;
|
|
103
|
-
/**
|
|
104
|
-
* The amount of time to wait before an attempt to end a chat times out, and we close it anyway.
|
|
105
|
-
*/
|
|
106
|
-
const END_CHAT_TIMEOUT_MS = 5000;
|
|
107
|
-
/**
|
|
108
|
-
* The amount of time to wait before a check for agent availability times out if there's no answer.
|
|
109
|
-
*/
|
|
110
|
-
const AVAILABILITY_TIMEOUT_MS = 5000;
|
|
111
|
-
/**
|
|
112
|
-
* The amount of time to wait before displaying the "bot returns" message.
|
|
113
|
-
*/
|
|
114
|
-
const BOT_RETURN_DELAY = 1500;
|
|
115
|
-
const { FROM_USER, RECONNECTED, DISCONNECTED, AGENT_ENDED_CHAT, AGENT_JOINED, USER_ENDED_CHAT, CHAT_WAS_ENDED, TRANSFER_TO_AGENT, AGENT_LEFT_CHAT, RELOAD_WARNING, SHARING_CANCELLED, SHARING_DECLINED, SHARING_ACCEPTED, SHARING_REQUESTED, SHARING_ENDED, } = AgentMessageType;
|
|
116
|
-
class HumanAgentServiceImpl {
|
|
117
|
-
constructor(serviceManager) {
|
|
118
|
-
/**
|
|
119
|
-
* Indicates if a chat has started (the startChat function has been called). It does not necessarily mean that an
|
|
120
|
-
* agent has joined and a full chat is in progress.
|
|
121
|
-
*/
|
|
122
|
-
this.chatStarted = false;
|
|
123
|
-
/**
|
|
124
|
-
* Indicates if the service desk has gotten into a disconnected error state.
|
|
125
|
-
*/
|
|
126
|
-
this.showingDisconnectedError = false;
|
|
127
|
-
/**
|
|
128
|
-
* Indicates if an agent is currently typing.
|
|
129
|
-
*/
|
|
130
|
-
this.isAgentTyping = false;
|
|
131
|
-
/**
|
|
132
|
-
* The current set of files that are being uploaded.
|
|
133
|
-
*/
|
|
134
|
-
this.uploadingFiles = new Set();
|
|
135
|
-
/**
|
|
136
|
-
* We only want to show the refresh/leave warning when the first agent joins, so we use this boolean to track if the
|
|
137
|
-
* warning has been shown.
|
|
138
|
-
*/
|
|
139
|
-
this.showLeaveWarning = true;
|
|
140
|
-
this.serviceManager = serviceManager;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* If a custom service desk is configured, returns the name.
|
|
144
|
-
*/
|
|
145
|
-
getCustomServiceDeskName() {
|
|
146
|
-
return this.serviceManager.store.getState().config.public.serviceDeskFactory
|
|
147
|
-
? this.serviceDesk.getName?.()
|
|
148
|
-
: undefined;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Initializes this service. This will create the service desk instance that can be used for communicating with
|
|
152
|
-
* service desks.
|
|
153
|
-
*/
|
|
154
|
-
async initialize() {
|
|
155
|
-
if (this.serviceDesk) {
|
|
156
|
-
throw new Error('A service desk has already been created!');
|
|
157
|
-
}
|
|
158
|
-
const { store, instance } = this.serviceManager;
|
|
159
|
-
const state = store.getState();
|
|
160
|
-
const { config, persistedToBrowserStorage } = state;
|
|
161
|
-
const serviceDeskState = cloneDeep(persistedToBrowserStorage.chatState.agentState.serviceDeskState);
|
|
162
|
-
this.serviceDeskCallback = new ServiceDeskCallbackImpl(this.serviceManager, this);
|
|
163
|
-
if (config.public.serviceDeskFactory) {
|
|
164
|
-
// A custom service desk factory was provided so use that to create the service desk.
|
|
165
|
-
const parameters = {
|
|
166
|
-
callback: this.serviceDeskCallback,
|
|
167
|
-
instance,
|
|
168
|
-
persistedState: serviceDeskState,
|
|
169
|
-
};
|
|
170
|
-
this.serviceDesk = await config.public.serviceDeskFactory(parameters);
|
|
171
|
-
validateCustomServiceDesk(this.serviceDesk);
|
|
172
|
-
debugLog('Initializing a custom service desk');
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
const { initConfig, mainConfig } = config.remote;
|
|
176
|
-
const { serviceDesk } = config.public;
|
|
177
|
-
const integrationType = serviceDesk.integrationType || initConfig.service_desk.integration_type;
|
|
178
|
-
console.log(`[debug] Checking for ${BuiltInServiceDesk.SALES_FORCE}`);
|
|
179
|
-
switch (integrationType) {
|
|
180
|
-
case BuiltInServiceDesk.ZENDESK: {
|
|
181
|
-
const sdConfig = mainConfig.service_desk;
|
|
182
|
-
const { ZendeskServiceDesk } = await import(
|
|
183
|
-
/* webpackChunkName: 'zendesk' */ './ZendeskServiceDesk.js');
|
|
184
|
-
this.serviceDesk = new ZendeskServiceDesk(this.serviceDeskCallback, sdConfig, this.serviceManager);
|
|
185
|
-
break;
|
|
186
|
-
}
|
|
187
|
-
case BuiltInServiceDesk.SALES_FORCE: {
|
|
188
|
-
const regionHostname = getRegionHostname(config.public);
|
|
189
|
-
const sdConfig = mainConfig.service_desk;
|
|
190
|
-
const { SFServiceDesk } = await import(
|
|
191
|
-
/* webpackChunkName: 'salesforce' */ './SFServiceDesk.js');
|
|
192
|
-
this.serviceDesk = new SFServiceDesk(this.serviceDeskCallback, sdConfig, regionHostname, this.serviceManager);
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
195
|
-
case BuiltInServiceDesk.NICE_DFO: {
|
|
196
|
-
const { NiceDFOServiceDesk } = await import(
|
|
197
|
-
/* webpackChunkName: 'nice' */ './NiceDFOServiceDesk.js');
|
|
198
|
-
this.serviceDesk = new NiceDFOServiceDesk(this.serviceDeskCallback, serviceDesk.niceDFO, this.serviceManager);
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
case BuiltInServiceDesk.GENESYS_MESSENGER: {
|
|
202
|
-
const sdConfig = serviceDesk.genesysMessenger;
|
|
203
|
-
const { GenesysMessengerServiceDesk } = await import(
|
|
204
|
-
/* webpackChunkName: 'genesys' */ './GenesysMessengerServiceDesk.js');
|
|
205
|
-
this.serviceDesk = new GenesysMessengerServiceDesk(this.serviceDeskCallback, sdConfig, this.serviceManager);
|
|
206
|
-
break;
|
|
207
|
-
}
|
|
208
|
-
default:
|
|
209
|
-
throw new Error(`Invalid service desk type: "${integrationType}"`);
|
|
210
|
-
}
|
|
211
|
-
debugLog(`Initializing built-in service desk ${integrationType}`);
|
|
212
|
-
}
|
|
213
|
-
// If the service desk supports reconnecting, we don't need to show this warning.
|
|
214
|
-
this.showLeaveWarning = !this.serviceDesk?.reconnect;
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Informs the service desk of a change in the state of the web chat that is relevant to the service desks. These
|
|
218
|
-
* values may change at any time.
|
|
219
|
-
*/
|
|
220
|
-
updateState(state) {
|
|
221
|
-
if (this.serviceDesk?.updateState) {
|
|
222
|
-
this.serviceDesk.updateState(state);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Begins a chat between the current user and the currently configured service desk. This may not be called if
|
|
227
|
-
* there is already a service desk being used.
|
|
228
|
-
*
|
|
229
|
-
* @param localConnectMessage The specific localMessage caused the connection to an agent. It will
|
|
230
|
-
* contain specific information to send to the service desk as part of the connection. This can include things
|
|
231
|
-
* like a message to display to a human agent.
|
|
232
|
-
* @param originalMessage The full original message that this Connect to Agent item belongs to.
|
|
233
|
-
*/
|
|
234
|
-
async startChat(localConnectMessage, originalMessage) {
|
|
235
|
-
if (!this.serviceDesk) {
|
|
236
|
-
// No service desk connected.
|
|
237
|
-
throw new Error('A service desk has not been configured.');
|
|
238
|
-
}
|
|
239
|
-
if (this.serviceManager.store.getState().persistedToBrowserStorage.chatState.agentState.isSuspended) {
|
|
240
|
-
// If the user is currently engaged in a conversation with an agent that is suspended and we start a new chat, we
|
|
241
|
-
// need to end the current conversation first.
|
|
242
|
-
await this.endChat(true, true);
|
|
243
|
-
}
|
|
244
|
-
if (this.chatStarted) {
|
|
245
|
-
throw new Error('A chat is already running. A call to endChat must be made before a new chat can start.');
|
|
246
|
-
}
|
|
247
|
-
const { serviceManager } = this;
|
|
248
|
-
// Track when user clicks to start a human chat.
|
|
249
|
-
serviceManager.actions.track({
|
|
250
|
-
eventName: 'User Clicked to Start Human Chat',
|
|
251
|
-
eventDescription: 'User clicked button to start a human chat.',
|
|
252
|
-
});
|
|
253
|
-
try {
|
|
254
|
-
this.chatStarted = true;
|
|
255
|
-
this.isAgentTyping = false;
|
|
256
|
-
this.uploadingFiles.clear();
|
|
257
|
-
this.serviceManager.store.dispatch(updateFilesUploadInProgress(this.uploadingFiles.size > 0));
|
|
258
|
-
// Create the public config that will be used by the web chat when it is loaded as an agent app inside the
|
|
259
|
-
// service desk. Note that we want that instance of the web chat to be the same version as this instance.
|
|
260
|
-
const sessionHistoryKey = replaceVersionInSessionHistoryKey(localConnectMessage);
|
|
261
|
-
// Fire off the pre-start event.
|
|
262
|
-
const event = {
|
|
263
|
-
type: "agent:pre:startChat" /* BusEventType.AGENT_PRE_START_CHAT */,
|
|
264
|
-
message: originalMessage,
|
|
265
|
-
sessionHistoryKey,
|
|
266
|
-
};
|
|
267
|
-
await serviceManager.fire(event);
|
|
268
|
-
if (event.cancelStartChat) {
|
|
269
|
-
// Abort the connecting.
|
|
270
|
-
this.chatStarted = false;
|
|
271
|
-
await this.fireEndChat(false, true);
|
|
272
|
-
serviceManager.store.dispatch(setIsConnecting(false, null));
|
|
273
|
-
serviceManager.actions.track({
|
|
274
|
-
eventName: `Human Chat Canceled By ${"agent:pre:startChat" /* BusEventType.AGENT_PRE_START_CHAT */} `,
|
|
275
|
-
eventDescription: `Human chat was canceled by ${"agent:pre:startChat" /* BusEventType.AGENT_PRE_START_CHAT */} `,
|
|
276
|
-
});
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
const agentJoinTimeout = serviceManager.store.getState().config.public.serviceDesk?.agentJoinTimeoutSeconds;
|
|
280
|
-
if (agentJoinTimeout) {
|
|
281
|
-
this.waitingForAgentJoinedTimer = setTimeout(() => this.handleAgentJoinedTimeout(), agentJoinTimeout * 1000);
|
|
282
|
-
}
|
|
283
|
-
serviceManager.store.dispatch(setIsConnecting(true, localConnectMessage.ui_state.id));
|
|
284
|
-
await this.serviceDesk.startChat(originalMessage, {
|
|
285
|
-
agentAppInfo: {
|
|
286
|
-
sessionHistoryKey,
|
|
287
|
-
},
|
|
288
|
-
preStartChatPayload: event.preStartChatPayload,
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
catch (error) {
|
|
292
|
-
consoleError('[startChat] An error with the service desk occurred.', error);
|
|
293
|
-
// If it failed to start, then stop connecting and clear the service desk.
|
|
294
|
-
if (this.serviceDeskCallback) {
|
|
295
|
-
await this.serviceDeskCallback.setErrorStatus({ type: ErrorType.CONNECTING, logInfo: error });
|
|
296
|
-
}
|
|
297
|
-
serviceManager.store.dispatch(setIsConnecting(false, null));
|
|
298
|
-
this.chatStarted = false;
|
|
299
|
-
this.cancelAgentJoinedTimer();
|
|
300
|
-
throw error;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
/**
|
|
304
|
-
* Fires the {@link BusEventType.AGENT_PRE_END_CHAT} event. The event fired is returned which can contain information
|
|
305
|
-
* added by a listener.
|
|
306
|
-
*/
|
|
307
|
-
async firePreEndChat(endedByAgent) {
|
|
308
|
-
// Before ending the chat, fire an event.
|
|
309
|
-
const event = {
|
|
310
|
-
type: "agent:pre:endChat" /* BusEventType.AGENT_PRE_END_CHAT */,
|
|
311
|
-
endedByAgent,
|
|
312
|
-
preEndChatPayload: null,
|
|
313
|
-
cancelEndChat: false,
|
|
314
|
-
};
|
|
315
|
-
await this.serviceManager.fire(event);
|
|
316
|
-
return event;
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Fires the {@link BusEventType.AGENT_END_CHAT} event.
|
|
320
|
-
*/
|
|
321
|
-
async fireEndChat(endedByAgent, requestCancelled) {
|
|
322
|
-
// Before ending the chat, fire an event.
|
|
323
|
-
await this.serviceManager.fire({
|
|
324
|
-
type: "agent:endChat" /* BusEventType.AGENT_END_CHAT */,
|
|
325
|
-
endedByAgent,
|
|
326
|
-
requestCancelled,
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Tells the service desk to terminate the chat.
|
|
331
|
-
*
|
|
332
|
-
* @param endedByUser Indicates if the chat is being ended as a result of the user or if it was ended
|
|
333
|
-
* programmatically from an instance method.
|
|
334
|
-
* @param skipEndMessages Indicates if the chat should be ended without generating the messages that are normally
|
|
335
|
-
* displayed to the user indicating the chat has ended.
|
|
336
|
-
* @returns Returns a Promise that resolves when the service desk has successfully handled the call.
|
|
337
|
-
*/
|
|
338
|
-
async endChat(endedByUser, skipEndMessages = false) {
|
|
339
|
-
if (!this.chatStarted || !this.serviceDesk) {
|
|
340
|
-
// Already ended or no service desk.
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
// Track when user clicks to end human chat.
|
|
344
|
-
const trackProps = {
|
|
345
|
-
eventName: 'Human chat ended',
|
|
346
|
-
eventDescription: endedByUser ? 'User ended chat' : 'Chat was ended by instance method',
|
|
347
|
-
};
|
|
348
|
-
this.serviceManager.actions.track(trackProps);
|
|
349
|
-
const { isConnected } = this.persistedAgentState();
|
|
350
|
-
let event;
|
|
351
|
-
if (isConnected) {
|
|
352
|
-
event = await this.firePreEndChat(false);
|
|
353
|
-
if (event.cancelEndChat) {
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
const wasSuspended = this.isSuspended();
|
|
358
|
-
await this.doEndChat(false, event?.preEndChatPayload);
|
|
359
|
-
if (isConnected && !skipEndMessages) {
|
|
360
|
-
// Display the "end chat" messages.
|
|
361
|
-
const messageType = endedByUser ? USER_ENDED_CHAT : CHAT_WAS_ENDED;
|
|
362
|
-
await addEndChatMessages(messageType, null, BOT_RETURN_DELAY, true, wasSuspended, this.serviceManager);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* This function will end the chat with a service class and clear the service state for it.
|
|
367
|
-
*/
|
|
368
|
-
async doEndChat(endedByAgent, preEndChatPayload) {
|
|
369
|
-
const { isConnected } = this.persistedAgentState();
|
|
370
|
-
this.cancelAgentJoinedTimer();
|
|
371
|
-
this.closeScreenShareRequestModal(ScreenShareState.CANCELLED);
|
|
372
|
-
try {
|
|
373
|
-
await resolveOrTimeout(this.serviceDesk.endChat({ endedByAgent, preEndChatPayload }), END_CHAT_TIMEOUT_MS);
|
|
374
|
-
}
|
|
375
|
-
catch (error) {
|
|
376
|
-
consoleError('[doEndChat] An error with the service desk occurred.', error);
|
|
377
|
-
}
|
|
378
|
-
this.chatStarted = false;
|
|
379
|
-
this.isAgentTyping = false;
|
|
380
|
-
this.serviceManager.store.dispatch(endChat());
|
|
381
|
-
await this.fireEndChat(endedByAgent, !isConnected);
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Sends a message to the agent in the service desk.
|
|
385
|
-
*
|
|
386
|
-
* @param text The message from the user.
|
|
387
|
-
* @param uploads An optional set of files to upload.
|
|
388
|
-
* @returns Returns a Promise that resolves when the service desk has successfully handled the call.
|
|
389
|
-
*/
|
|
390
|
-
async sendMessageToAgent(text, uploads) {
|
|
391
|
-
if (!this.serviceDesk || !this.chatStarted) {
|
|
392
|
-
// No service desk connected.
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
const { serviceManager } = this;
|
|
396
|
-
deepFreeze(uploads);
|
|
397
|
-
const originalMessage = createMessageRequestForText(text);
|
|
398
|
-
originalMessage.input.agent_message_type = FROM_USER;
|
|
399
|
-
// Fire the pre:send event that will allow code to customize the message.
|
|
400
|
-
await serviceManager.fire({ type: "agent:pre:send" /* BusEventType.AGENT_PRE_SEND */, data: originalMessage, files: uploads });
|
|
401
|
-
// Add the outgoing message to the store immediately.
|
|
402
|
-
const textMessage = inputItemToLocalItem(originalMessage, originalMessage.input.text);
|
|
403
|
-
const localMessageID = textMessage.ui_state.id;
|
|
404
|
-
const pairs = [];
|
|
405
|
-
if (textMessage.item.text) {
|
|
406
|
-
pairs.push(toPair([textMessage], originalMessage));
|
|
407
|
-
}
|
|
408
|
-
// Add a message for each file upload.
|
|
409
|
-
uploads.forEach(upload => {
|
|
410
|
-
// Note that we're going to reuse the file ID for the MessageRequest and LocalMessage to make it easier to
|
|
411
|
-
// locate the objects when we need to update their states.
|
|
412
|
-
const uploadOriginalMessage = createMessageRequestForFileUpload(upload);
|
|
413
|
-
const uploadLocalMessage = inputItemToLocalItem(uploadOriginalMessage, uploadOriginalMessage.input.text, upload.id);
|
|
414
|
-
pairs.push(toPair([uploadLocalMessage], uploadOriginalMessage));
|
|
415
|
-
this.uploadingFiles.add(upload.id);
|
|
416
|
-
});
|
|
417
|
-
this.serviceManager.store.dispatch(updateFilesUploadInProgress(this.uploadingFiles.size > 0));
|
|
418
|
-
await addMessages(pairs, true, true, this.isSuspended(), serviceManager);
|
|
419
|
-
// Track when a message from the user is sent to human agent.
|
|
420
|
-
const trackProps = {
|
|
421
|
-
eventName: 'Human Message Received from User',
|
|
422
|
-
eventDescription: 'User sends message to human agent.',
|
|
423
|
-
};
|
|
424
|
-
serviceManager.actions.track(trackProps);
|
|
425
|
-
// Start some timeouts to display a warning or error if the service desk doesn't indicate if the message was
|
|
426
|
-
// sent successfully (or it failed).
|
|
427
|
-
let messageSucceeded = false;
|
|
428
|
-
let messageFailed = false;
|
|
429
|
-
setTimeout(() => {
|
|
430
|
-
if (!messageSucceeded && !messageFailed) {
|
|
431
|
-
this.setMessageErrorState(textMessage.fullMessageID, MessageErrorState.RETRYING);
|
|
432
|
-
}
|
|
433
|
-
}, SEND_TIMEOUT_WARNING_MS);
|
|
434
|
-
setTimeout(() => {
|
|
435
|
-
if (!messageSucceeded) {
|
|
436
|
-
this.setMessageErrorState(textMessage.fullMessageID, MessageErrorState.FAILED);
|
|
437
|
-
}
|
|
438
|
-
}, SEND_TIMEOUT_ERROR_MS);
|
|
439
|
-
const additionalData = {
|
|
440
|
-
filesToUpload: uploads,
|
|
441
|
-
};
|
|
442
|
-
try {
|
|
443
|
-
// Send the message to the service desk.
|
|
444
|
-
await this.serviceDesk.sendMessageToAgent(originalMessage, localMessageID, additionalData);
|
|
445
|
-
messageSucceeded = true;
|
|
446
|
-
this.setMessageErrorState(textMessage.fullMessageID, MessageErrorState.NONE);
|
|
447
|
-
await serviceManager.fire({ type: "agent:send" /* BusEventType.AGENT_SEND */, data: originalMessage, files: uploads });
|
|
448
|
-
}
|
|
449
|
-
catch (error) {
|
|
450
|
-
messageFailed = true;
|
|
451
|
-
consoleError('[sendMessageToAgent] An error with the service desk occurred.', error);
|
|
452
|
-
this.setMessageErrorState(textMessage.fullMessageID, MessageErrorState.FAILED);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* Indicates that the user has selected some files to be uploaded but that the user has not yet chosen to send
|
|
457
|
-
* them to the agent.
|
|
458
|
-
*/
|
|
459
|
-
filesSelectedForUpload(uploads) {
|
|
460
|
-
if (!this.serviceDesk || !this.chatStarted) {
|
|
461
|
-
// No service desk connected.
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
try {
|
|
465
|
-
this.serviceDesk.filesSelectedForUpload?.(uploads);
|
|
466
|
-
}
|
|
467
|
-
catch (error) {
|
|
468
|
-
consoleError('[userReadMessages] An error with the service desk occurred.', error);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
/**
|
|
472
|
-
* Informs the service desk that the user has read all the messages that have been sent by the service desk.
|
|
473
|
-
*/
|
|
474
|
-
async userReadMessages() {
|
|
475
|
-
if (!this.serviceDesk || !this.chatStarted) {
|
|
476
|
-
// No service desk connected.
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
try {
|
|
480
|
-
await this.serviceDesk.userReadMessages();
|
|
481
|
-
}
|
|
482
|
-
catch (error) {
|
|
483
|
-
consoleError('[userReadMessages] An error with the service desk occurred.', error);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Checks if any agents are online and ready to communicate with the user. This function will time out after 5
|
|
488
|
-
* seconds and will return false when that happens.
|
|
489
|
-
*
|
|
490
|
-
* @param connectMessage The message that contains the transfer_info object that may be used by the service desk,
|
|
491
|
-
* so it can perform a more specific check.
|
|
492
|
-
*/
|
|
493
|
-
async checkAreAnyAgentsOnline(connectMessage) {
|
|
494
|
-
let resultValue;
|
|
495
|
-
const initialRestartCount = this.serviceManager.restartCount;
|
|
496
|
-
if (!this.serviceDesk?.areAnyAgentsOnline) {
|
|
497
|
-
resultValue = AgentsOnlineStatus.UNKNOWN;
|
|
498
|
-
}
|
|
499
|
-
else {
|
|
500
|
-
try {
|
|
501
|
-
const timeoutSeconds = this.serviceManager.store.getState().config.public.serviceDesk?.availabilityTimeoutSeconds;
|
|
502
|
-
const timeout = timeoutSeconds ? timeoutSeconds * 1000 : AVAILABILITY_TIMEOUT_MS;
|
|
503
|
-
const result = await resolveOrTimeout(this.serviceDesk.areAnyAgentsOnline(connectMessage), timeout);
|
|
504
|
-
if (result === true) {
|
|
505
|
-
resultValue = AgentsOnlineStatus.ONLINE;
|
|
506
|
-
}
|
|
507
|
-
else if (result === false) {
|
|
508
|
-
resultValue = AgentsOnlineStatus.OFFLINE;
|
|
509
|
-
}
|
|
510
|
-
else {
|
|
511
|
-
// Any other value for result will return an unknown status.
|
|
512
|
-
resultValue = AgentsOnlineStatus.UNKNOWN;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
catch (error) {
|
|
516
|
-
consoleError('Error attempting to get agent availability', error);
|
|
517
|
-
// If we fail to get an answer we'll just return false to indicate that no agents are available.
|
|
518
|
-
resultValue = AgentsOnlineStatus.OFFLINE;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
if (initialRestartCount === this.serviceManager.restartCount) {
|
|
522
|
-
// Don't await this since we don't want any event handlers to hold up this check.
|
|
523
|
-
this.serviceManager.fire({
|
|
524
|
-
type: "agent:areAnyAgentsOnline" /* BusEventType.AGENT_ARE_ANY_AGENTS_ONLINE */,
|
|
525
|
-
areAnyAgentsOnline: resultValue,
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
return resultValue;
|
|
529
|
-
}
|
|
530
|
-
/**
|
|
531
|
-
* Tells the service desk if a user has started or stopped typing.
|
|
532
|
-
*
|
|
533
|
-
* @param isTyping If true, indicates that the user is typing. False indicates the user has stopped typing.
|
|
534
|
-
*/
|
|
535
|
-
async userTyping(isTyping) {
|
|
536
|
-
if (!this.serviceDesk || !this.chatStarted) {
|
|
537
|
-
// No service desk connected.
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
try {
|
|
541
|
-
await this.serviceDesk.userTyping?.(isTyping);
|
|
542
|
-
}
|
|
543
|
-
catch (error) {
|
|
544
|
-
consoleError('[userTyping] An error with the service desk occurred.', error);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
/**
|
|
548
|
-
* Sets the error state for the message with the given id.
|
|
549
|
-
*
|
|
550
|
-
* @param messageID The ID of the message to set the state for. This will be the ID that was passed on the service
|
|
551
|
-
* desk as part of the {@link ServiceDesk#sendMessageToAgent} call.
|
|
552
|
-
* @param errorState The state to set of the message.
|
|
553
|
-
*/
|
|
554
|
-
setMessageErrorState(messageID, errorState) {
|
|
555
|
-
this.serviceManager.store.dispatch(actions.setMessageErrorState(messageID, errorState));
|
|
556
|
-
}
|
|
557
|
-
/**
|
|
558
|
-
* This is called when an agent fails to join a chat after a given period of time.
|
|
559
|
-
*/
|
|
560
|
-
async handleAgentJoinedTimeout() {
|
|
561
|
-
// Display an error to the user.
|
|
562
|
-
const message = this.serviceManager.store.getState().languagePack.errors_noAgentsJoined;
|
|
563
|
-
const { originalMessage, localMessage } = createLocalMessageForInlineError(message);
|
|
564
|
-
await addMessages([toPair([localMessage], originalMessage)], true, false, this.isSuspended(), this.serviceManager);
|
|
565
|
-
// End the chat.
|
|
566
|
-
this.endChat(false);
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* Cancels the agent joined timer if one is running.
|
|
570
|
-
*/
|
|
571
|
-
cancelAgentJoinedTimer() {
|
|
572
|
-
if (this.waitingForAgentJoinedTimer) {
|
|
573
|
-
clearTimeout(this.waitingForAgentJoinedTimer);
|
|
574
|
-
this.waitingForAgentJoinedTimer = null;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
/**
|
|
578
|
-
* Informs the service desk of a change in the state of screen sharing from the user side.
|
|
579
|
-
*
|
|
580
|
-
* @param state The new state of the screen sharing.
|
|
581
|
-
*/
|
|
582
|
-
async screenShareUpdateRequestState(state) {
|
|
583
|
-
if (!this.persistedAgentState().isConnected) {
|
|
584
|
-
// Not connected to an agent.
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
// Close the modal.
|
|
588
|
-
this.closeScreenShareRequestModal(state);
|
|
589
|
-
let agentMessageType;
|
|
590
|
-
switch (state) {
|
|
591
|
-
case ScreenShareState.ACCEPTED:
|
|
592
|
-
agentMessageType = SHARING_ACCEPTED;
|
|
593
|
-
break;
|
|
594
|
-
case ScreenShareState.DECLINED:
|
|
595
|
-
agentMessageType = SHARING_DECLINED;
|
|
596
|
-
break;
|
|
597
|
-
case ScreenShareState.CANCELLED:
|
|
598
|
-
agentMessageType = SHARING_CANCELLED;
|
|
599
|
-
break;
|
|
600
|
-
case ScreenShareState.ENDED:
|
|
601
|
-
agentMessageType = SHARING_ENDED;
|
|
602
|
-
break;
|
|
603
|
-
default:
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
// Display a message to the user.
|
|
607
|
-
await this.addAgentLocalMessage(agentMessageType);
|
|
608
|
-
}
|
|
609
|
-
/**
|
|
610
|
-
* Informs the service desk that it should stop screen sharing.
|
|
611
|
-
*/
|
|
612
|
-
async screenShareStop() {
|
|
613
|
-
this.serviceManager.store.dispatch(setIsScreenSharing(false));
|
|
614
|
-
await this.addAgentLocalMessage(SHARING_ENDED);
|
|
615
|
-
await this.serviceDesk?.screenShareStop?.();
|
|
616
|
-
}
|
|
617
|
-
/**
|
|
618
|
-
* Called during the hydration process to allow the service to deal with hydration.
|
|
619
|
-
*/
|
|
620
|
-
async handleHydration() {
|
|
621
|
-
const { store } = this.serviceManager;
|
|
622
|
-
let didReconnect = false;
|
|
623
|
-
const { isConnected } = this.persistedAgentState();
|
|
624
|
-
if (isConnected) {
|
|
625
|
-
this.chatStarted = true;
|
|
626
|
-
if (this.serviceDesk?.reconnect) {
|
|
627
|
-
// If the user was previously connected to an agent, we need to see if we can reconnect the user to the agent.
|
|
628
|
-
try {
|
|
629
|
-
store.dispatch(setIsReconnecting(true));
|
|
630
|
-
setTimeout(this.serviceManager.appWindow.requestFocus);
|
|
631
|
-
// Let the service desk do whatever it needs to do to reconnect.
|
|
632
|
-
didReconnect = await this.serviceDesk.reconnect();
|
|
633
|
-
}
|
|
634
|
-
catch (error) {
|
|
635
|
-
consoleError(`Error while trying to reconnect to an agent.`, error);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
store.dispatch(setIsReconnecting(false));
|
|
639
|
-
if (!this.persistedAgentState().isConnected) {
|
|
640
|
-
// The user may have disconnected while waiting for the reconnect in which case, just stop what we're doing.
|
|
641
|
-
this.chatStarted = false;
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
setTimeout(this.serviceManager.appWindow.requestFocus);
|
|
645
|
-
if (!didReconnect) {
|
|
646
|
-
// End the chat.
|
|
647
|
-
this.chatStarted = false;
|
|
648
|
-
const wasSuspended = this.isSuspended();
|
|
649
|
-
store.dispatch(endChat());
|
|
650
|
-
// If we didn't reconnect, then show the "end chat" messages to the user.
|
|
651
|
-
await addEndChatMessages(AgentMessageType.CHAT_WAS_ENDED, null, 0, false, wasSuspended, this.serviceManager);
|
|
652
|
-
}
|
|
653
|
-
else {
|
|
654
|
-
this.showLeaveWarning = false;
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
/**
|
|
659
|
-
* Closes the screen share request modal and completes the promise waiting on it.
|
|
660
|
-
*/
|
|
661
|
-
closeScreenShareRequestModal(state) {
|
|
662
|
-
// Close the modal if it was open.
|
|
663
|
-
this.serviceManager.store.dispatch(setShowScreenShareRequest(false));
|
|
664
|
-
// If someone is waiting on the Promise, then resolve it.
|
|
665
|
-
if (this.screenShareRequestPromise) {
|
|
666
|
-
this.screenShareRequestPromise.doResolve(state);
|
|
667
|
-
this.screenShareRequestPromise = null;
|
|
668
|
-
}
|
|
669
|
-
this.serviceManager.store.dispatch(setIsScreenSharing(state === ScreenShareState.ACCEPTED));
|
|
670
|
-
}
|
|
671
|
-
/**
|
|
672
|
-
* Adds a local agent message.
|
|
673
|
-
*/
|
|
674
|
-
async addAgentLocalMessage(agentMessageType, agentProfile, fireEvents = true, saveInHistory = true) {
|
|
675
|
-
if (!agentProfile) {
|
|
676
|
-
agentProfile = this.persistedAgentState().agentProfile;
|
|
677
|
-
}
|
|
678
|
-
const { localMessage, originalMessage } = await createAgentLocalMessage(agentMessageType, this.serviceManager, agentProfile, fireEvents);
|
|
679
|
-
await addMessages([toPair([localMessage], originalMessage)], saveInHistory, false, this.isSuspended(), this.serviceManager);
|
|
680
|
-
}
|
|
681
|
-
/**
|
|
682
|
-
* Returns the persisted agent state from the store.
|
|
683
|
-
*/
|
|
684
|
-
persistedAgentState() {
|
|
685
|
-
return this.serviceManager.store.getState().persistedToBrowserStorage.chatState.agentState;
|
|
686
|
-
}
|
|
687
|
-
/**
|
|
688
|
-
* Indicates if the conversation with the agent is suspended.
|
|
689
|
-
*/
|
|
690
|
-
isSuspended() {
|
|
691
|
-
return this.serviceManager.store.getState().persistedToBrowserStorage.chatState.agentState.isSuspended;
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
/**
|
|
695
|
-
* This class implements the callback that is passed to the service desk that it can use to send us information that
|
|
696
|
-
* it produced by the service desk.
|
|
697
|
-
*/
|
|
698
|
-
class ServiceDeskCallbackImpl {
|
|
699
|
-
constructor(serviceManager, service) {
|
|
700
|
-
this.serviceManager = serviceManager;
|
|
701
|
-
this.service = service;
|
|
702
|
-
}
|
|
703
|
-
/**
|
|
704
|
-
* Updates web chat with the capabilities supported by the service desk. Some of these capabilities may support
|
|
705
|
-
* being changed dynamically and can be updated at any time.
|
|
706
|
-
*
|
|
707
|
-
* @param capabilities The set of capabilities to update. Only properties that need to be changed need to be included.
|
|
708
|
-
*/
|
|
709
|
-
updateCapabilities(capabilities) {
|
|
710
|
-
this.serviceManager.store.dispatch(updateCapabilities(cloneDeep(capabilities)));
|
|
711
|
-
}
|
|
712
|
-
/**
|
|
713
|
-
* Sends updated availability information to the chat widget for a user who is waiting to be connected to an
|
|
714
|
-
* agent. This may be called at any point while waiting for the connection to provide newer information.
|
|
715
|
-
*
|
|
716
|
-
* @param availability The availability information to display to the user.
|
|
717
|
-
*/
|
|
718
|
-
async updateAgentAvailability(availability) {
|
|
719
|
-
if (!this.service.chatStarted) {
|
|
720
|
-
// The chat is no longer running.
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
this.serviceManager.store.dispatch(setAgentAvailability(availability));
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* Informs the chat widget that the agent has read all the messages that have been sent to the service desk.
|
|
727
|
-
*/
|
|
728
|
-
async agentJoined(profile) {
|
|
729
|
-
if (!this.service.chatStarted) {
|
|
730
|
-
// The chat is no longer running.
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
this.service.cancelAgentJoinedTimer();
|
|
734
|
-
// Update the store with the current agent's profile information.
|
|
735
|
-
this.serviceManager.store.dispatch(setAgentJoined(profile));
|
|
736
|
-
// Then generate a message we can display in the UI to indicate that the agent has joined.
|
|
737
|
-
await this.service.addAgentLocalMessage(AGENT_JOINED, profile);
|
|
738
|
-
if (this.service.showLeaveWarning) {
|
|
739
|
-
await this.service.addAgentLocalMessage(RELOAD_WARNING, null, false, false);
|
|
740
|
-
this.service.showLeaveWarning = false;
|
|
741
|
-
}
|
|
742
|
-
// Track when a human agent joins a human chat with user.
|
|
743
|
-
const trackProps = {
|
|
744
|
-
eventName: 'Human Chat Started',
|
|
745
|
-
eventDescription: 'Human chat started and agent joined.',
|
|
746
|
-
};
|
|
747
|
-
this.serviceManager.actions.track(trackProps);
|
|
748
|
-
}
|
|
749
|
-
/**
|
|
750
|
-
* Informs the chat widget that the agent has read all the messages that have been sent to the service desk.
|
|
751
|
-
*
|
|
752
|
-
* This functionality is not yet implemented.
|
|
753
|
-
*/
|
|
754
|
-
async agentReadMessages() {
|
|
755
|
-
if (!this.service.chatStarted) {
|
|
756
|
-
// The chat is no longer running.
|
|
757
|
-
return;
|
|
758
|
-
}
|
|
759
|
-
debugLog('[ServiceDeskCallbackImpl] agentReadMessages');
|
|
760
|
-
}
|
|
761
|
-
/**
|
|
762
|
-
* Tells the chat widget if an agent has started or stopped typing.
|
|
763
|
-
*
|
|
764
|
-
* @param isTyping If true, indicates that the agent is typing. False indicates the agent has stopped typing.
|
|
765
|
-
*/
|
|
766
|
-
async agentTyping(isTyping) {
|
|
767
|
-
if (this.persistedAgentState().isConnected && isTyping !== this.service.isAgentTyping) {
|
|
768
|
-
this.serviceManager.store.dispatch(agentUpdateIsTyping(isTyping));
|
|
769
|
-
this.service.isAgentTyping = isTyping;
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
/**
|
|
773
|
-
* Sends a message to the chat widget from an agent.
|
|
774
|
-
*
|
|
775
|
-
* Note: The text response type from the standard Watson API is supported in addition to the web chat specific
|
|
776
|
-
* {@link MessageResponseTypes.INLINE_ERROR} response type.
|
|
777
|
-
*
|
|
778
|
-
* @param message The message to display to the user. Note, the ability to pass a string for the message was added in
|
|
779
|
-
* web chat 6.7.0. Earlier versions of web chat will not work if you pass just a string.
|
|
780
|
-
* @param agentID The ID of the agent who is sending the message. If this is not provided, then the ID of the last
|
|
781
|
-
* agent who joined the conversation will be used.
|
|
782
|
-
*/
|
|
783
|
-
async sendMessageToUser(message, agentID) {
|
|
784
|
-
if (!this.service.chatStarted || !message) {
|
|
785
|
-
// The chat is no longer running or no message was actually provided.
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
const messageResponse = typeof message === 'string' ? createMessageResponseForText(message) : message;
|
|
789
|
-
addDefaultsToMessage(messageResponse);
|
|
790
|
-
if (messageResponse.output?.generic?.length) {
|
|
791
|
-
messageResponse.output.generic.forEach(messageItem => {
|
|
792
|
-
if (!messageItem.agent_message_type) {
|
|
793
|
-
messageItem.agent_message_type = AgentMessageType.FROM_AGENT;
|
|
794
|
-
}
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
|
-
const { serviceManager } = this;
|
|
798
|
-
// If no agent ID is provided, just use the current one.
|
|
799
|
-
let agentProfile;
|
|
800
|
-
if (agentID === undefined) {
|
|
801
|
-
agentProfile = this.persistedAgentState().agentProfile;
|
|
802
|
-
}
|
|
803
|
-
else {
|
|
804
|
-
agentProfile = this.persistedAgentState().agentProfiles[agentID];
|
|
805
|
-
if (!agentProfile) {
|
|
806
|
-
// If we don't have a profile for the agent who sent this message, we need to use the profile for the current
|
|
807
|
-
// agent (if there is one).
|
|
808
|
-
agentProfile = this.persistedAgentState().agentProfile;
|
|
809
|
-
if (agentProfile) {
|
|
810
|
-
consoleError(`Got agent ID ${agentID} but no agent with that ID joined the conversation. Using the current agent instead.`);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
// Fire the pre:receive event that will allow code to customize the message.
|
|
815
|
-
await serviceManager.fire({
|
|
816
|
-
type: "agent:pre:receive" /* BusEventType.AGENT_PRE_RECEIVE */,
|
|
817
|
-
data: messageResponse,
|
|
818
|
-
agentProfile,
|
|
819
|
-
});
|
|
820
|
-
messageResponse.history.agent_profile = agentProfile;
|
|
821
|
-
const localMessages = messageResponse.output.generic.map(item => {
|
|
822
|
-
return outputItemToLocalItem(item, messageResponse);
|
|
823
|
-
});
|
|
824
|
-
await addMessages([toPair(localMessages, messageResponse)], true, true, this.service.isSuspended(), this.serviceManager);
|
|
825
|
-
// Track when a message from a human agent is sent to the user.
|
|
826
|
-
const trackProps = {
|
|
827
|
-
eventName: 'Human Message Sent to User',
|
|
828
|
-
eventDescription: 'Human agent sends message to user.',
|
|
829
|
-
};
|
|
830
|
-
serviceManager.actions.track(trackProps);
|
|
831
|
-
await serviceManager.fire({
|
|
832
|
-
type: "agent:receive" /* BusEventType.AGENT_RECEIVE */,
|
|
833
|
-
data: messageResponse,
|
|
834
|
-
agentProfile,
|
|
835
|
-
});
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
|
-
* Informs the chat widget that a transfer to another agent is in progress. The agent profile information is
|
|
839
|
-
* optional if the service desk doesn't have the information available. This message simply tells the chat widget
|
|
840
|
-
* that the transfer has started. The service desk should inform the widget when the transfer is complete by
|
|
841
|
-
* sending a {@link agentJoined} message later.
|
|
842
|
-
*/
|
|
843
|
-
async beginTransferToAnotherAgent(profile) {
|
|
844
|
-
if (!this.service.chatStarted) {
|
|
845
|
-
// The chat is no longer running.
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
if (profile) {
|
|
849
|
-
// Update the store with the current agent's profile information.
|
|
850
|
-
this.serviceManager.store.dispatch(setAgentJoined(profile));
|
|
851
|
-
}
|
|
852
|
-
await this.service.addAgentLocalMessage(TRANSFER_TO_AGENT, profile);
|
|
853
|
-
}
|
|
854
|
-
/**
|
|
855
|
-
* Informs the chat widget that the current agent has left the conversation.
|
|
856
|
-
*/
|
|
857
|
-
async agentLeftChat() {
|
|
858
|
-
if (!this.service.chatStarted) {
|
|
859
|
-
// The chat is no longer running.
|
|
860
|
-
return;
|
|
861
|
-
}
|
|
862
|
-
await this.service.addAgentLocalMessage(AGENT_LEFT_CHAT);
|
|
863
|
-
this.service.isAgentTyping = false;
|
|
864
|
-
this.serviceManager.store.dispatch(setAgentLeftChat());
|
|
865
|
-
}
|
|
866
|
-
/**
|
|
867
|
-
* Informs the chat widget that the agent has closed the conversation.
|
|
868
|
-
*/
|
|
869
|
-
async agentEndedChat() {
|
|
870
|
-
if (!this.service.chatStarted) {
|
|
871
|
-
// The chat is no longer running.
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
const event = await this.service.firePreEndChat(true);
|
|
875
|
-
if (event.cancelEndChat) {
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
const trackProps = {
|
|
879
|
-
eventName: 'Human chat ended',
|
|
880
|
-
eventDescription: 'Agent ended chat',
|
|
881
|
-
};
|
|
882
|
-
this.serviceManager.actions.track(trackProps);
|
|
883
|
-
const wasSuspended = this.service.isSuspended();
|
|
884
|
-
await this.service.doEndChat(true, event.preEndChatPayload);
|
|
885
|
-
// Display the "end chat" messages.
|
|
886
|
-
const { agentProfile } = this.persistedAgentState();
|
|
887
|
-
await addEndChatMessages(AGENT_ENDED_CHAT, agentProfile, BOT_RETURN_DELAY, true, wasSuspended, this.serviceManager);
|
|
888
|
-
}
|
|
889
|
-
/**
|
|
890
|
-
* Sets the state of the given error type.
|
|
891
|
-
*
|
|
892
|
-
* @param errorInfo Details for the error whose state is being set.
|
|
893
|
-
*/
|
|
894
|
-
async setErrorStatus(errorInfo) {
|
|
895
|
-
if (!this.service.chatStarted) {
|
|
896
|
-
// The chat is no longer running.
|
|
897
|
-
return;
|
|
898
|
-
}
|
|
899
|
-
const { type, logInfo } = errorInfo;
|
|
900
|
-
const { store } = this.serviceManager;
|
|
901
|
-
const { isConnecting } = store.getState().agentState;
|
|
902
|
-
if (logInfo) {
|
|
903
|
-
consoleError(`An error occurred in the service desk (type=${type})`, logInfo);
|
|
904
|
-
}
|
|
905
|
-
// If the service desk reports a disconnected error while we're in the middle of connecting, then handle it as a
|
|
906
|
-
// connecting error instead. This avoids us sending the user a message when we never actually connected.
|
|
907
|
-
if (isConnecting && errorInfo.type === ErrorType.DISCONNECTED && errorInfo.isDisconnected) {
|
|
908
|
-
errorInfo = { type: ErrorType.CONNECTING };
|
|
909
|
-
}
|
|
910
|
-
switch (errorInfo.type) {
|
|
911
|
-
case ErrorType.DISCONNECTED: {
|
|
912
|
-
if (errorInfo.isDisconnected) {
|
|
913
|
-
// The service desk has become disconnected so show an error and don't allow the user to send messages.
|
|
914
|
-
this.service.showingDisconnectedError = true;
|
|
915
|
-
await this.service.addAgentLocalMessage(DISCONNECTED, null, true, false);
|
|
916
|
-
store.dispatch(actions.updateInputState({ isReadonly: true }, true));
|
|
917
|
-
}
|
|
918
|
-
else if (this.service.showingDisconnectedError) {
|
|
919
|
-
// The service desk says it's no longer disconnected but double check that we previously thought we were
|
|
920
|
-
// disconnected.
|
|
921
|
-
this.service.showingDisconnectedError = false;
|
|
922
|
-
await this.service.addAgentLocalMessage(RECONNECTED, null, true, false);
|
|
923
|
-
store.dispatch(actions.updateInputState({ isReadonly: false }, true));
|
|
924
|
-
}
|
|
925
|
-
break;
|
|
926
|
-
}
|
|
927
|
-
case ErrorType.CONNECTING: {
|
|
928
|
-
// If we can't connect, display an inline error message on the bot view.
|
|
929
|
-
const { languagePack } = this.serviceManager.store.getState();
|
|
930
|
-
const message = errorInfo.messageToUser || languagePack.errors_connectingToAgent;
|
|
931
|
-
const { originalMessage, localMessage } = createLocalMessageForInlineError(message);
|
|
932
|
-
await addMessages([toPair([localMessage], originalMessage)], true, false, this.service.isSuspended(), this.serviceManager);
|
|
933
|
-
// Cancel the connecting status.
|
|
934
|
-
this.serviceManager.store.dispatch(setIsConnecting(false, null));
|
|
935
|
-
this.service.chatStarted = false;
|
|
936
|
-
this.service.cancelAgentJoinedTimer();
|
|
937
|
-
await this.service.fireEndChat(false, isConnecting);
|
|
938
|
-
break;
|
|
939
|
-
}
|
|
940
|
-
case ErrorType.USER_MESSAGE: {
|
|
941
|
-
this.service.setMessageErrorState(errorInfo.messageID, MessageErrorState.FAILED);
|
|
942
|
-
break;
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
/**
|
|
947
|
-
* Updates the status of a file upload. The upload may either be successful or an error may have occurred. The
|
|
948
|
-
* location of a file upload may be in one of two places. The first occurs when the user has selected a file to be
|
|
949
|
-
* uploaded but has not yet sent the file. In this case, the file appears inside the web chat input area. If an
|
|
950
|
-
* error is indicated on the file, the error message will be displayed along with the file and the user must
|
|
951
|
-
* remove the file from the input area before a message can be sent.
|
|
952
|
-
*
|
|
953
|
-
* The second occurs after the user has sent the file and the service desk has begun to upload the file. In this
|
|
954
|
-
* case, the file no longer appears in the input area but appears as a sent message in the message list. If an
|
|
955
|
-
* error occurs during this time, an icon will appear next to the message to indicate an error occurred and an
|
|
956
|
-
* error message will be added to the message list.
|
|
957
|
-
*
|
|
958
|
-
* @param fileID The ID of the file upload to update.
|
|
959
|
-
* @param isError Indicates that the upload has an error or failed to upload.
|
|
960
|
-
* @param errorMessage An error message to display along with a file in error.
|
|
961
|
-
*/
|
|
962
|
-
async setFileUploadStatus(fileID, isError, errorMessage) {
|
|
963
|
-
const { store } = this.serviceManager;
|
|
964
|
-
// First we need to determine if the file upload has been sent or not. A message will exist in the store if so;
|
|
965
|
-
// otherwise the file upload only exists in the input area.
|
|
966
|
-
const uploadMessage = store.getState().allMessagesByID[fileID];
|
|
967
|
-
if (uploadMessage) {
|
|
968
|
-
// Update the value in the redux store.
|
|
969
|
-
const partialMessage = {
|
|
970
|
-
history: { file_upload_status: FileStatusValue.COMPLETE },
|
|
971
|
-
};
|
|
972
|
-
if (isError) {
|
|
973
|
-
store.dispatch(actions.setMessageHistoryProperty(fileID, 'file_upload_status', FileStatusValue.COMPLETE));
|
|
974
|
-
store.dispatch(actions.setMessageHistoryProperty(fileID, 'error_state', MessageErrorState.FAILED));
|
|
975
|
-
partialMessage.history.error_state = MessageErrorState.FAILED;
|
|
976
|
-
if (errorMessage) {
|
|
977
|
-
// Generate an inline error message to show the error to the user.
|
|
978
|
-
const { originalMessage, localMessage } = createLocalMessageForInlineError(errorMessage);
|
|
979
|
-
localMessage.item.agent_message_type = AgentMessageType.INLINE_ERROR;
|
|
980
|
-
await addMessages([toPair([localMessage], originalMessage)], true, true, this.service.isSuspended(), this.serviceManager);
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
else {
|
|
984
|
-
// If the upload was completed successfully, we display a temporary "success" status. This will display a
|
|
985
|
-
// checkmark temporarily before fading out. Session history will store "complete" as the status.
|
|
986
|
-
store.dispatch(actions.setMessageHistoryProperty(fileID, 'file_upload_status', FileStatusValue.SUCCESS));
|
|
987
|
-
store.dispatch(actions.announceMessage({ messageID: 'fileSharing_ariaAnnounceSuccess' }));
|
|
988
|
-
}
|
|
989
|
-
// Send an update to store the status in session history.
|
|
990
|
-
await this.serviceManager.actions.sendUpdateHistoryEvent(fileID, partialMessage);
|
|
991
|
-
}
|
|
992
|
-
else if (isError) {
|
|
993
|
-
// Update the input area.
|
|
994
|
-
store.dispatch(actions.fileUploadInputError(fileID, errorMessage, true));
|
|
995
|
-
}
|
|
996
|
-
this.service.uploadingFiles.delete(fileID);
|
|
997
|
-
this.serviceManager.store.dispatch(updateFilesUploadInProgress(this.service.uploadingFiles.size > 0));
|
|
998
|
-
}
|
|
999
|
-
/**
|
|
1000
|
-
* Requests that the user share their screen with the agent. This will present a modal dialog to the user who must
|
|
1001
|
-
* respond before continuing the conversation. This method returns a Promise that resolves when the user has
|
|
1002
|
-
* responded to the request or the request times out.
|
|
1003
|
-
*
|
|
1004
|
-
* @returns Returns a Promise that will resolve with the state the of the request. This Promise will reject if no
|
|
1005
|
-
* chat with an agent is currently running.
|
|
1006
|
-
*/
|
|
1007
|
-
async screenShareRequest() {
|
|
1008
|
-
if (!this.persistedAgentState().isConnected) {
|
|
1009
|
-
return Promise.reject(new Error('Cannot request screen sharing if no chat is in progress.'));
|
|
1010
|
-
}
|
|
1011
|
-
if (!this.service.screenShareRequestPromise) {
|
|
1012
|
-
this.service.screenShareRequestPromise = resolvablePromise();
|
|
1013
|
-
this.serviceManager.store.dispatch(setShowScreenShareRequest(true));
|
|
1014
|
-
await this.service.addAgentLocalMessage(SHARING_REQUESTED);
|
|
1015
|
-
}
|
|
1016
|
-
return this.service.screenShareRequestPromise;
|
|
1017
|
-
}
|
|
1018
|
-
/**
|
|
1019
|
-
* Informs web chat that a screen sharing session has ended or been cancelled. This may occur while waiting for a
|
|
1020
|
-
* screen sharing request to be accepted or while screen sharing is in progress.
|
|
1021
|
-
*/
|
|
1022
|
-
async screenShareEnded() {
|
|
1023
|
-
const wasScreenSharing = this.serviceManager.store.getState().agentState.isScreenSharing;
|
|
1024
|
-
const requestPending = this.service.screenShareRequestPromise;
|
|
1025
|
-
this.service.closeScreenShareRequestModal(ScreenShareState.CANCELLED);
|
|
1026
|
-
if (wasScreenSharing) {
|
|
1027
|
-
this.serviceManager.store.dispatch(setIsScreenSharing(false));
|
|
1028
|
-
await this.service.addAgentLocalMessage(SHARING_ENDED);
|
|
1029
|
-
}
|
|
1030
|
-
else if (requestPending) {
|
|
1031
|
-
await this.service.addAgentLocalMessage(SHARING_CANCELLED);
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
/**
|
|
1035
|
-
* Returns the persisted agent state from the store.
|
|
1036
|
-
*/
|
|
1037
|
-
persistedAgentState() {
|
|
1038
|
-
return this.serviceManager.store.getState().persistedToBrowserStorage.chatState.agentState;
|
|
1039
|
-
}
|
|
1040
|
-
/**
|
|
1041
|
-
* Returns the persisted service desk state from the store. This is the current state as updated by
|
|
1042
|
-
* {@link updatePersistedState}. The object returned here is frozen and may not be modified.
|
|
1043
|
-
*/
|
|
1044
|
-
persistedState() {
|
|
1045
|
-
return this.serviceManager.store.getState().persistedToBrowserStorage.chatState.agentState
|
|
1046
|
-
.serviceDeskState;
|
|
1047
|
-
}
|
|
1048
|
-
/**
|
|
1049
|
-
* Allows the service desk to store state that may be retrieved when web chat is reloaded on a page. This information
|
|
1050
|
-
* is stored in browser session storage which has a total limit of 5MB per origin so the storage should be used
|
|
1051
|
-
* sparingly. Also, the value provided here must be JSON serializable.
|
|
1052
|
-
*
|
|
1053
|
-
* When web chat is reloaded, the data provided here will be returned to the service desk via the
|
|
1054
|
-
* ServiceDeskFactoryParameters.persistedState property.
|
|
1055
|
-
*
|
|
1056
|
-
* @param state The state to update.
|
|
1057
|
-
* @param mergeWithCurrent Indicates if the new state should be merged into the existing state. If false, then the
|
|
1058
|
-
* existing state will be fully replaced with the new state. Merging with existing state expects the state to be
|
|
1059
|
-
* an object.
|
|
1060
|
-
*/
|
|
1061
|
-
updatePersistedState(state, mergeWithCurrent = true) {
|
|
1062
|
-
const { store } = this.serviceManager;
|
|
1063
|
-
let newState;
|
|
1064
|
-
if (mergeWithCurrent) {
|
|
1065
|
-
newState = merge({}, store.getState().persistedToBrowserStorage.chatState.agentState.serviceDeskState, state);
|
|
1066
|
-
}
|
|
1067
|
-
else {
|
|
1068
|
-
newState = cloneDeep(state);
|
|
1069
|
-
}
|
|
1070
|
-
store.dispatch(setPersistedServiceDeskState(deepFreeze(newState)));
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
/**
|
|
1074
|
-
* Returns a new instance of the service implementation.
|
|
1075
|
-
*/
|
|
1076
|
-
function createService(serviceManager) {
|
|
1077
|
-
return new HumanAgentServiceImpl(serviceManager);
|
|
1078
|
-
}
|
|
1079
|
-
/**
|
|
1080
|
-
* Performs some minimal validation of the provided custom service desk to make sure it meets the minimum
|
|
1081
|
-
* requirements. This simply checks that the service desk has the required properties and that those properties are
|
|
1082
|
-
* functions. If there are any errors, they are logged to the console.
|
|
1083
|
-
*/
|
|
1084
|
-
function validateCustomServiceDesk(serviceDesk) {
|
|
1085
|
-
if (!serviceDesk) {
|
|
1086
|
-
consoleError('The custom service desk does not appear to be valid. No service desk was provided.', serviceDesk);
|
|
1087
|
-
}
|
|
1088
|
-
else if (typeof serviceDesk !== 'object') {
|
|
1089
|
-
consoleError(`The custom service desk does not appear to be valid. The type should be "object" but is "${typeof serviceDesk}"`, serviceDesk);
|
|
1090
|
-
}
|
|
1091
|
-
else {
|
|
1092
|
-
const propertyNames = ['startChat', 'endChat', 'sendMessageToAgent'];
|
|
1093
|
-
propertyNames.forEach(propertyName => {
|
|
1094
|
-
const value = serviceDesk[propertyName];
|
|
1095
|
-
if (typeof value !== 'function') {
|
|
1096
|
-
consoleError(`The custom service desk does not appear to be valid. The type of property "${propertyName}"should be "function" but is "${typeof value}"`, value, serviceDesk);
|
|
1097
|
-
}
|
|
1098
|
-
});
|
|
1099
|
-
const name = serviceDesk.getName?.();
|
|
1100
|
-
if (!name) {
|
|
1101
|
-
throw Error('The custom service desk does not have a name.');
|
|
1102
|
-
}
|
|
1103
|
-
if (name && (typeof name !== 'string' || name.length > 40)) {
|
|
1104
|
-
throw new Error(`The custom service desk name "${name}" is not valid.`);
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
/**
|
|
1109
|
-
* Swap out "latest" for the exact version of the web chat being used so that agent app will render using same code
|
|
1110
|
-
* base.
|
|
1111
|
-
*/
|
|
1112
|
-
function replaceVersionInSessionHistoryKey(localConnectMessage) {
|
|
1113
|
-
const sessionHistoryKey = localConnectMessage.item.transfer_info?.session_history_key;
|
|
1114
|
-
if (sessionHistoryKey) {
|
|
1115
|
-
const sessionHistoryKeySerialized = SessionHistoryKeySerializer.deserialize(sessionHistoryKey);
|
|
1116
|
-
sessionHistoryKeySerialized.version = VERSION;
|
|
1117
|
-
return SessionHistoryKeySerializer.serialize(sessionHistoryKeySerialized);
|
|
1118
|
-
}
|
|
1119
|
-
return '';
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
export { HumanAgentServiceImpl, createService, validateCustomServiceDesk };
|
|
1
|
+
import{aC as e,aG as t,aH as s,M as a,a as i,f as r,aI as n,aJ as c,o,r as h}from"./humanAgentUtils.js";import{ak as d,ad as g,f as l,al as v,A as p,X as u,j as S,g as E}from"./messageUtils.js";import{l as y,h as M,Y as D,c as A,a7 as k,k as C,Q as f,m as w,aa as m}from"./aiChatEntry2.js";import{u as T,s as N,o as _,i as b,p as I,q as R,r as L,t as F,v as P,w as O,x as H,y as U,z as G}from"./agentActions.js";import"@lit/react";import"react";import"lit";import"lit/decorators.js";import"react-dom";const q=["region","version","auth_code","session_id","integration_id","service_instance_id","subscription_id"],j=e=>q.map((t=>e[t])).join("::"),J=e=>{const t=e.split("::"),s={};return q.forEach(((e,a)=>{s[e]=t[a]})),s},{FROM_USER:B,RECONNECTED:W,DISCONNECTED:$,AGENT_ENDED_CHAT:z,AGENT_JOINED:x,USER_ENDED_CHAT:K,CHAT_WAS_ENDED:Y,TRANSFER_TO_AGENT:Z,AGENT_LEFT_CHAT:Q,RELOAD_WARNING:X,SHARING_CANCELLED:V,SHARING_DECLINED:ee,SHARING_ACCEPTED:te,SHARING_REQUESTED:se,SHARING_ENDED:ae}=u;class ie{constructor(e){this.chatStarted=!1,this.showingDisconnectedError=!1,this.isAgentTyping=!1,this.uploadingFiles=new Set,this.showLeaveWarning=!0,this.serviceManager=e}getCustomServiceDeskName(){return this.serviceManager.store.getState().config.public.serviceDeskFactory?this.serviceDesk.getName?.():void 0}async initialize(){if(this.serviceDesk)throw new Error("A service desk has already been created!");const{store:t,instance:s}=this.serviceManager,a=t.getState(),{config:i,persistedToBrowserStorage:r}=a,n=y(r.chatState.agentState.serviceDeskState);if(this.serviceDeskCallback=new re(this.serviceManager,this),i.public.serviceDeskFactory){const e={callback:this.serviceDeskCallback,instance:s,persistedState:n};this.serviceDesk=await i.public.serviceDeskFactory(e),ce(this.serviceDesk),M("Initializing a custom service desk")}else{const{initConfig:t,mainConfig:s}=i.remote,{serviceDesk:a}=i.public,r=a.integrationType||t.service_desk.integration_type;switch(console.log(`[debug] Checking for ${e.SALES_FORCE}`),r){case e.ZENDESK:{const e=s.service_desk,{ZendeskServiceDesk:t}=await import("./ZendeskServiceDesk.js");this.serviceDesk=new t(this.serviceDeskCallback,e,this.serviceManager);break}case e.SALES_FORCE:{const e=D(i.public),t=s.service_desk,{SFServiceDesk:a}=await import("./SFServiceDesk.js");this.serviceDesk=new a(this.serviceDeskCallback,t,e,this.serviceManager);break}case e.NICE_DFO:{const{NiceDFOServiceDesk:e}=await import("./NiceDFOServiceDesk.js");this.serviceDesk=new e(this.serviceDeskCallback,a.niceDFO,this.serviceManager);break}case e.GENESYS_MESSENGER:{const e=a.genesysMessenger,{GenesysMessengerServiceDesk:t}=await import("./GenesysMessengerServiceDesk.js");this.serviceDesk=new t(this.serviceDeskCallback,e,this.serviceManager);break}default:throw new Error(`Invalid service desk type: "${r}"`)}M(`Initializing built-in service desk ${r}`)}this.showLeaveWarning=!this.serviceDesk?.reconnect}updateState(e){this.serviceDesk?.updateState&&this.serviceDesk.updateState(e)}async startChat(e,t){if(!this.serviceDesk)throw new Error("A service desk has not been configured.");if(this.serviceManager.store.getState().persistedToBrowserStorage.chatState.agentState.isSuspended&&await this.endChat(!0,!0),this.chatStarted)throw new Error("A chat is already running. A call to endChat must be made before a new chat can start.");const{serviceManager:s}=this;s.actions.track({eventName:"User Clicked to Start Human Chat",eventDescription:"User clicked button to start a human chat."});try{this.chatStarted=!0,this.isAgentTyping=!1,this.uploadingFiles.clear(),this.serviceManager.store.dispatch(T(this.uploadingFiles.size>0));const a=function(e){const t=e.item.transfer_info?.session_history_key;if(t){const e=J(t);return e.version=m,j(e)}return""}(e),i={type:"agent:pre:startChat",message:t,sessionHistoryKey:a};if(await s.fire(i),i.cancelStartChat)return this.chatStarted=!1,await this.fireEndChat(!1,!0),s.store.dispatch(N(!1,null)),void s.actions.track({eventName:"Human Chat Canceled By agent:pre:startChat ",eventDescription:"Human chat was canceled by agent:pre:startChat "});const r=s.store.getState().config.public.serviceDesk?.agentJoinTimeoutSeconds;r&&(this.waitingForAgentJoinedTimer=setTimeout((()=>this.handleAgentJoinedTimeout()),1e3*r)),s.store.dispatch(N(!0,e.ui_state.id)),await this.serviceDesk.startChat(t,{agentAppInfo:{sessionHistoryKey:a},preStartChatPayload:i.preStartChatPayload})}catch(e){throw A("[startChat] An error with the service desk occurred.",e),this.serviceDeskCallback&&await this.serviceDeskCallback.setErrorStatus({type:d.CONNECTING,logInfo:e}),s.store.dispatch(N(!1,null)),this.chatStarted=!1,this.cancelAgentJoinedTimer(),e}}async firePreEndChat(e){const t={type:"agent:pre:endChat",endedByAgent:e,preEndChatPayload:null,cancelEndChat:!1};return await this.serviceManager.fire(t),t}async fireEndChat(e,t){await this.serviceManager.fire({type:"agent:endChat",endedByAgent:e,requestCancelled:t})}async endChat(e,s=!1){if(!this.chatStarted||!this.serviceDesk)return;const a={eventName:"Human chat ended",eventDescription:e?"User ended chat":"Chat was ended by instance method"};this.serviceManager.actions.track(a);const{isConnected:i}=this.persistedAgentState();let r;if(i&&(r=await this.firePreEndChat(!1),r.cancelEndChat))return;const n=this.isSuspended();if(await this.doEndChat(!1,r?.preEndChatPayload),i&&!s){const s=e?K:Y;await t(s,null,1500,!0,n,this.serviceManager)}}async doEndChat(e,t){const{isConnected:s}=this.persistedAgentState();this.cancelAgentJoinedTimer(),this.closeScreenShareRequestModal(g.CANCELLED);try{await k(this.serviceDesk.endChat({endedByAgent:e,preEndChatPayload:t}),5e3)}catch(e){A("[doEndChat] An error with the service desk occurred.",e)}this.chatStarted=!1,this.isAgentTyping=!1,this.serviceManager.store.dispatch(_()),await this.fireEndChat(e,!s)}async sendMessageToAgent(e,t){if(!this.serviceDesk||!this.chatStarted)return;const{serviceManager:i}=this;C(t);const r=l(e);r.input.agent_message_type=B,await i.fire({type:"agent:pre:send",data:r,files:t});const c=b(r,r.input.text),o=c.ui_state.id,h=[];c.item.text&&h.push(n([c],r)),t.forEach((e=>{const t=v(e),s=b(t,t.input.text,e.id);h.push(n([s],t)),this.uploadingFiles.add(e.id)})),this.serviceManager.store.dispatch(T(this.uploadingFiles.size>0)),await s(h,!0,!0,this.isSuspended(),i);i.actions.track({eventName:"Human Message Received from User",eventDescription:"User sends message to human agent."});let d=!1,g=!1;setTimeout((()=>{d||g||this.setMessageErrorState(c.fullMessageID,a.RETRYING)}),3e3),setTimeout((()=>{d||this.setMessageErrorState(c.fullMessageID,a.FAILED)}),2e4);const p={filesToUpload:t};try{await this.serviceDesk.sendMessageToAgent(r,o,p),d=!0,this.setMessageErrorState(c.fullMessageID,a.NONE),await i.fire({type:"agent:send",data:r,files:t})}catch(e){g=!0,A("[sendMessageToAgent] An error with the service desk occurred.",e),this.setMessageErrorState(c.fullMessageID,a.FAILED)}}filesSelectedForUpload(e){if(this.serviceDesk&&this.chatStarted)try{this.serviceDesk.filesSelectedForUpload?.(e)}catch(e){A("[userReadMessages] An error with the service desk occurred.",e)}}async userReadMessages(){if(this.serviceDesk&&this.chatStarted)try{await this.serviceDesk.userReadMessages()}catch(e){A("[userReadMessages] An error with the service desk occurred.",e)}}async checkAreAnyAgentsOnline(e){let t;const s=this.serviceManager.restartCount;if(this.serviceDesk?.areAnyAgentsOnline)try{const s=this.serviceManager.store.getState().config.public.serviceDesk?.availabilityTimeoutSeconds,a=s?1e3*s:5e3,i=await k(this.serviceDesk.areAnyAgentsOnline(e),a);t=!0===i?p.ONLINE:!1===i?p.OFFLINE:p.UNKNOWN}catch(e){A("Error attempting to get agent availability",e),t=p.OFFLINE}else t=p.UNKNOWN;return s===this.serviceManager.restartCount&&this.serviceManager.fire({type:"agent:areAnyAgentsOnline",areAnyAgentsOnline:t}),t}async userTyping(e){if(this.serviceDesk&&this.chatStarted)try{await(this.serviceDesk.userTyping?.(e))}catch(e){A("[userTyping] An error with the service desk occurred.",e)}}setMessageErrorState(e,t){this.serviceManager.store.dispatch(i.setMessageErrorState(e,t))}async handleAgentJoinedTimeout(){const e=this.serviceManager.store.getState().languagePack.errors_noAgentsJoined,{originalMessage:t,localMessage:a}=r(e);await s([n([a],t)],!0,!1,this.isSuspended(),this.serviceManager),this.endChat(!1)}cancelAgentJoinedTimer(){this.waitingForAgentJoinedTimer&&(clearTimeout(this.waitingForAgentJoinedTimer),this.waitingForAgentJoinedTimer=null)}async screenShareUpdateRequestState(e){if(!this.persistedAgentState().isConnected)return;let t;switch(this.closeScreenShareRequestModal(e),e){case g.ACCEPTED:t=te;break;case g.DECLINED:t=ee;break;case g.CANCELLED:t=V;break;case g.ENDED:t=ae;break;default:return}await this.addAgentLocalMessage(t)}async screenShareStop(){this.serviceManager.store.dispatch(I(!1)),await this.addAgentLocalMessage(ae),await(this.serviceDesk?.screenShareStop?.())}async handleHydration(){const{store:e}=this.serviceManager;let s=!1;const{isConnected:a}=this.persistedAgentState();if(a){if(this.chatStarted=!0,this.serviceDesk?.reconnect)try{e.dispatch(R(!0)),setTimeout(this.serviceManager.appWindow.requestFocus),s=await this.serviceDesk.reconnect()}catch(e){A("Error while trying to reconnect to an agent.",e)}if(e.dispatch(R(!1)),!this.persistedAgentState().isConnected)return void(this.chatStarted=!1);if(setTimeout(this.serviceManager.appWindow.requestFocus),s)this.showLeaveWarning=!1;else{this.chatStarted=!1;const s=this.isSuspended();e.dispatch(_()),await t(u.CHAT_WAS_ENDED,null,0,!1,s,this.serviceManager)}}}closeScreenShareRequestModal(e){this.serviceManager.store.dispatch(L(!1)),this.screenShareRequestPromise&&(this.screenShareRequestPromise.doResolve(e),this.screenShareRequestPromise=null),this.serviceManager.store.dispatch(I(e===g.ACCEPTED))}async addAgentLocalMessage(e,t,a=!0,i=!0){t||(t=this.persistedAgentState().agentProfile);const{localMessage:r,originalMessage:o}=await c(e,this.serviceManager,t,a);await s([n([r],o)],i,!1,this.isSuspended(),this.serviceManager)}persistedAgentState(){return this.serviceManager.store.getState().persistedToBrowserStorage.chatState.agentState}isSuspended(){return this.serviceManager.store.getState().persistedToBrowserStorage.chatState.agentState.isSuspended}}class re{constructor(e,t){this.serviceManager=e,this.service=t}updateCapabilities(e){this.serviceManager.store.dispatch(F(y(e)))}async updateAgentAvailability(e){this.service.chatStarted&&this.serviceManager.store.dispatch(P(e))}async agentJoined(e){if(!this.service.chatStarted)return;this.service.cancelAgentJoinedTimer(),this.serviceManager.store.dispatch(O(e)),await this.service.addAgentLocalMessage(x,e),this.service.showLeaveWarning&&(await this.service.addAgentLocalMessage(X,null,!1,!1),this.service.showLeaveWarning=!1);this.serviceManager.actions.track({eventName:"Human Chat Started",eventDescription:"Human chat started and agent joined."})}async agentReadMessages(){this.service.chatStarted&&M("[ServiceDeskCallbackImpl] agentReadMessages")}async agentTyping(e){this.persistedAgentState().isConnected&&e!==this.service.isAgentTyping&&(this.serviceManager.store.dispatch(H(e)),this.service.isAgentTyping=e)}async sendMessageToUser(e,t){if(!this.service.chatStarted||!e)return;const a="string"==typeof e?S(e):e;E(a),a.output?.generic?.length&&a.output.generic.forEach((e=>{e.agent_message_type||(e.agent_message_type=u.FROM_AGENT)}));const{serviceManager:i}=this;let r;void 0===t?r=this.persistedAgentState().agentProfile:(r=this.persistedAgentState().agentProfiles[t],r||(r=this.persistedAgentState().agentProfile,r&&A(`Got agent ID ${t} but no agent with that ID joined the conversation. Using the current agent instead.`))),await i.fire({type:"agent:pre:receive",data:a,agentProfile:r}),a.history.agent_profile=r;const c=a.output.generic.map((e=>o(e,a)));await s([n(c,a)],!0,!0,this.service.isSuspended(),this.serviceManager);i.actions.track({eventName:"Human Message Sent to User",eventDescription:"Human agent sends message to user."}),await i.fire({type:"agent:receive",data:a,agentProfile:r})}async beginTransferToAnotherAgent(e){this.service.chatStarted&&(e&&this.serviceManager.store.dispatch(O(e)),await this.service.addAgentLocalMessage(Z,e))}async agentLeftChat(){this.service.chatStarted&&(await this.service.addAgentLocalMessage(Q),this.service.isAgentTyping=!1,this.serviceManager.store.dispatch(U()))}async agentEndedChat(){if(!this.service.chatStarted)return;const e=await this.service.firePreEndChat(!0);if(e.cancelEndChat)return;this.serviceManager.actions.track({eventName:"Human chat ended",eventDescription:"Agent ended chat"});const s=this.service.isSuspended();await this.service.doEndChat(!0,e.preEndChatPayload);const{agentProfile:a}=this.persistedAgentState();await t(z,a,1500,!0,s,this.serviceManager)}async setErrorStatus(e){if(!this.service.chatStarted)return;const{type:t,logInfo:c}=e,{store:o}=this.serviceManager,{isConnecting:h}=o.getState().agentState;switch(c&&A(`An error occurred in the service desk (type=${t})`,c),h&&e.type===d.DISCONNECTED&&e.isDisconnected&&(e={type:d.CONNECTING}),e.type){case d.DISCONNECTED:e.isDisconnected?(this.service.showingDisconnectedError=!0,await this.service.addAgentLocalMessage($,null,!0,!1),o.dispatch(i.updateInputState({isReadonly:!0},!0))):this.service.showingDisconnectedError&&(this.service.showingDisconnectedError=!1,await this.service.addAgentLocalMessage(W,null,!0,!1),o.dispatch(i.updateInputState({isReadonly:!1},!0)));break;case d.CONNECTING:{const{languagePack:t}=this.serviceManager.store.getState(),a=e.messageToUser||t.errors_connectingToAgent,{originalMessage:i,localMessage:c}=r(a);await s([n([c],i)],!0,!1,this.service.isSuspended(),this.serviceManager),this.serviceManager.store.dispatch(N(!1,null)),this.service.chatStarted=!1,this.service.cancelAgentJoinedTimer(),await this.service.fireEndChat(!1,h);break}case d.USER_MESSAGE:this.service.setMessageErrorState(e.messageID,a.FAILED)}}async setFileUploadStatus(e,t,c){const{store:o}=this.serviceManager;if(o.getState().allMessagesByID[e]){const h={history:{file_upload_status:f.COMPLETE}};if(t){if(o.dispatch(i.setMessageHistoryProperty(e,"file_upload_status",f.COMPLETE)),o.dispatch(i.setMessageHistoryProperty(e,"error_state",a.FAILED)),h.history.error_state=a.FAILED,c){const{originalMessage:e,localMessage:t}=r(c);t.item.agent_message_type=u.INLINE_ERROR,await s([n([t],e)],!0,!0,this.service.isSuspended(),this.serviceManager)}}else o.dispatch(i.setMessageHistoryProperty(e,"file_upload_status",f.SUCCESS)),o.dispatch(i.announceMessage({messageID:"fileSharing_ariaAnnounceSuccess"}));await this.serviceManager.actions.sendUpdateHistoryEvent(e,h)}else t&&o.dispatch(i.fileUploadInputError(e,c,!0));this.service.uploadingFiles.delete(e),this.serviceManager.store.dispatch(T(this.service.uploadingFiles.size>0))}async screenShareRequest(){return this.persistedAgentState().isConnected?(this.service.screenShareRequestPromise||(this.service.screenShareRequestPromise=h(),this.serviceManager.store.dispatch(L(!0)),await this.service.addAgentLocalMessage(se)),this.service.screenShareRequestPromise):Promise.reject(new Error("Cannot request screen sharing if no chat is in progress."))}async screenShareEnded(){const e=this.serviceManager.store.getState().agentState.isScreenSharing,t=this.service.screenShareRequestPromise;this.service.closeScreenShareRequestModal(g.CANCELLED),e?(this.serviceManager.store.dispatch(I(!1)),await this.service.addAgentLocalMessage(ae)):t&&await this.service.addAgentLocalMessage(V)}persistedAgentState(){return this.serviceManager.store.getState().persistedToBrowserStorage.chatState.agentState}persistedState(){return this.serviceManager.store.getState().persistedToBrowserStorage.chatState.agentState.serviceDeskState}updatePersistedState(e,t=!0){const{store:s}=this.serviceManager;let a;a=t?w({},s.getState().persistedToBrowserStorage.chatState.agentState.serviceDeskState,e):y(e),s.dispatch(G(C(a)))}}function ne(e){return new ie(e)}function ce(e){if(e)if("object"!=typeof e)A(`The custom service desk does not appear to be valid. The type should be "object" but is "${typeof e}"`,e);else{["startChat","endChat","sendMessageToAgent"].forEach((t=>{const s=e[t];"function"!=typeof s&&A(`The custom service desk does not appear to be valid. The type of property "${t}"should be "function" but is "${typeof s}"`,s,e)}));const t=e.getName?.();if(!t)throw Error("The custom service desk does not have a name.");if(t&&("string"!=typeof t||t.length>40))throw new Error(`The custom service desk name "${t}" is not valid.`)}else A("The custom service desk does not appear to be valid. No service desk was provided.",e)}export{ie as HumanAgentServiceImpl,ne as createService,ce as validateCustomServiceDesk};
|