@pendo/agent 2.328.2 → 2.329.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/dom.esm.js +1 -1
- package/dist/pendo.module.js +576 -412
- package/dist/pendo.module.min.js +8 -8
- package/dist/servers.json +7 -7
- package/index.js +1 -1
- package/package.json +1 -1
package/dist/pendo.module.js
CHANGED
|
@@ -4010,8 +4010,8 @@ let SERVER = '';
|
|
|
4010
4010
|
let ASSET_HOST = '';
|
|
4011
4011
|
let ASSET_PATH = '';
|
|
4012
4012
|
let DESIGNER_SERVER = '';
|
|
4013
|
-
let VERSION = '2.
|
|
4014
|
-
let PACKAGE_VERSION = '2.
|
|
4013
|
+
let VERSION = '2.329.0_';
|
|
4014
|
+
let PACKAGE_VERSION = '2.329.0';
|
|
4015
4015
|
let LOADER = 'xhr';
|
|
4016
4016
|
/* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
|
|
4017
4017
|
/**
|
|
@@ -20097,6 +20097,7 @@ var GuideStateModule = (function () {
|
|
|
20097
20097
|
storageKey: LAST_STEP_ADVANCED_COOKIE,
|
|
20098
20098
|
guideRequestExpiration: 60 * 60 * 1000,
|
|
20099
20099
|
scopedStorageKey: null,
|
|
20100
|
+
scopedThrottlingStorageKeys: {},
|
|
20100
20101
|
receivedStateChangeAt: null,
|
|
20101
20102
|
receivedLastGuideStepSeen: null,
|
|
20102
20103
|
lastGuideStepSeen: null,
|
|
@@ -20119,6 +20120,11 @@ var GuideStateModule = (function () {
|
|
|
20119
20120
|
*/
|
|
20120
20121
|
context.getters.storage().registry.addLocal(LAST_STEP_ADVANCED_COOKIE);
|
|
20121
20122
|
context.commit('setScopedStorageKey', getPendoCookieKey(context.state.storageKey));
|
|
20123
|
+
var scopedThrottlingStorageKeys = {};
|
|
20124
|
+
_.each(THROTTLING_STATE, function (throttlingStorageKey) {
|
|
20125
|
+
scopedThrottlingStorageKeys[throttlingStorageKey] = getPendoCookieKey(throttlingStorageKey);
|
|
20126
|
+
});
|
|
20127
|
+
context.commit('setScopedThrottlingStorageKeys', scopedThrottlingStorageKeys);
|
|
20122
20128
|
Events.identify.on(function (evt) {
|
|
20123
20129
|
if (wasSessionCleared(evt)) {
|
|
20124
20130
|
removeIdentificationCookies([LAST_STEP_ADVANCED_COOKIE]);
|
|
@@ -20157,11 +20163,21 @@ var GuideStateModule = (function () {
|
|
|
20157
20163
|
});
|
|
20158
20164
|
}
|
|
20159
20165
|
}
|
|
20166
|
+
context.dispatch('loadThrottling');
|
|
20167
|
+
},
|
|
20168
|
+
loadThrottling(context) {
|
|
20169
|
+
const storage = context.getters.storage();
|
|
20160
20170
|
_.each(THROTTLING_STATE, function (throttlingStorageKey) {
|
|
20161
20171
|
const value = storage.read(throttlingStorageKey);
|
|
20162
20172
|
context.dispatch('updateThrottlingState', { name: throttlingStorageKey, value });
|
|
20163
20173
|
});
|
|
20164
20174
|
},
|
|
20175
|
+
applyThrottling(context) {
|
|
20176
|
+
_.each(THROTTLING_STATE, function (throttlingStorageKey) {
|
|
20177
|
+
pendo$1[throttlingStorageKey] = applyTimerCache(pendo$1[throttlingStorageKey], context.state[throttlingStorageKey]);
|
|
20178
|
+
context.dispatch('updateThrottlingState', { name: throttlingStorageKey, value: pendo$1[throttlingStorageKey] });
|
|
20179
|
+
});
|
|
20180
|
+
},
|
|
20165
20181
|
forceExpire(context) {
|
|
20166
20182
|
_.each(context.state.steps, function (stepState, stepId) {
|
|
20167
20183
|
context.commit('expireStepState', stepId);
|
|
@@ -20237,10 +20253,7 @@ var GuideStateModule = (function () {
|
|
|
20237
20253
|
if (mostRecentLastGuideStepSeen) {
|
|
20238
20254
|
context.dispatch('updateLastGuideStepSeen', _.extend({}, context.state.lastGuideStepSeen, mostRecentLastGuideStepSeen));
|
|
20239
20255
|
}
|
|
20240
|
-
|
|
20241
|
-
pendo$1[throttlingStorageKey] = applyTimerCache(pendo$1[throttlingStorageKey], context.state[throttlingStorageKey]);
|
|
20242
|
-
context.dispatch('updateThrottlingState', { name: throttlingStorageKey, value: pendo$1[throttlingStorageKey] });
|
|
20243
|
-
});
|
|
20256
|
+
context.dispatch('applyThrottling');
|
|
20244
20257
|
},
|
|
20245
20258
|
receiveLastGuideStepSeen(context, lastGuideStepSeen) {
|
|
20246
20259
|
context.commit('setReceivedLastGuideStepSeen', lastGuideStepSeen);
|
|
@@ -20303,6 +20316,10 @@ var GuideStateModule = (function () {
|
|
|
20303
20316
|
}
|
|
20304
20317
|
}
|
|
20305
20318
|
}
|
|
20319
|
+
else if (_.contains(_.values(context.state.scopedThrottlingStorageKeys), storageEvent.key)) {
|
|
20320
|
+
context.dispatch('loadThrottling');
|
|
20321
|
+
context.dispatch('applyThrottling');
|
|
20322
|
+
}
|
|
20306
20323
|
},
|
|
20307
20324
|
regainFocus(context) {
|
|
20308
20325
|
var tabLostFocus = context.getters.tabLostFocus();
|
|
@@ -20330,6 +20347,9 @@ var GuideStateModule = (function () {
|
|
|
20330
20347
|
setScopedStorageKey(state, scopedStorageKey) {
|
|
20331
20348
|
state.scopedStorageKey = scopedStorageKey;
|
|
20332
20349
|
},
|
|
20350
|
+
setScopedThrottlingStorageKeys(state, scopedThrottlingStorageKeys) {
|
|
20351
|
+
state.scopedThrottlingStorageKeys = scopedThrottlingStorageKeys;
|
|
20352
|
+
},
|
|
20333
20353
|
setLastGuideStepSeen(state, lastGuideStepSeen) {
|
|
20334
20354
|
state.lastGuideStepSeen = lastGuideStepSeen;
|
|
20335
20355
|
},
|
|
@@ -23570,13 +23590,13 @@ var onGuideDismissed = function (evt, step) {
|
|
|
23570
23590
|
var seenReason = firstStep && firstStep.seenReason;
|
|
23571
23591
|
var language = currentGuide && currentGuide.language;
|
|
23572
23592
|
var dismissCount = _.get(step, 'dismissCount', 0) + 1;
|
|
23573
|
-
dismissedGuide(guideId, stepId, get_visitor_id(), seenReason, language, dismissCount);
|
|
23574
23593
|
var now = getNow();
|
|
23575
|
-
|
|
23576
|
-
// maintain latestDismissedAutoAt client-side
|
|
23594
|
+
// maintain latestDismissedAutoAt client-side before guideDismissed writes lastStepAdvanced
|
|
23577
23595
|
if (shouldAffectThrottling(currentGuide, seenReason)) {
|
|
23578
23596
|
writeThrottlingStateCache(now, THROTTLING_STATE.DISMISSED);
|
|
23579
23597
|
}
|
|
23598
|
+
dismissedGuide(guideId, stepId, get_visitor_id(), seenReason, language, dismissCount);
|
|
23599
|
+
_updateGuideStepStatus(guideId, stepId, 'dismissed', now, dismissCount);
|
|
23580
23600
|
step.hide();
|
|
23581
23601
|
if (!isGuideShown()) {
|
|
23582
23602
|
stopGuides();
|
|
@@ -23632,12 +23652,12 @@ var onGuideSnoozed = function (evt, step, snoozeDuration) {
|
|
|
23632
23652
|
log.info('snoozing guide for ' + snoozeDuration + ' ms');
|
|
23633
23653
|
var snoozeEndTime = now + snoozeDuration;
|
|
23634
23654
|
step.snoozeEndTime = snoozeEndTime;
|
|
23635
|
-
|
|
23636
|
-
_updateGuideStepStatus(guideId, stepId, 'snoozed', now);
|
|
23637
|
-
// maintain latestSnoozedAutoAt client-side
|
|
23655
|
+
// maintain latestSnoozedAutoAt client-side before guideSnoozed writes lastStepAdvanced
|
|
23638
23656
|
if (shouldAffectThrottling(guide, seenReason)) {
|
|
23639
23657
|
writeThrottlingStateCache(now, THROTTLING_STATE.SNOOZED);
|
|
23640
23658
|
}
|
|
23659
|
+
snoozedGuide(guideId, stepId, visitorId, seenReason, language, snoozeDuration);
|
|
23660
|
+
_updateGuideStepStatus(guideId, stepId, 'snoozed', now);
|
|
23641
23661
|
step.hide();
|
|
23642
23662
|
if (!isGuideShown()) {
|
|
23643
23663
|
stopGuides();
|
|
@@ -23786,15 +23806,15 @@ var onGuideAdvanced = function (evt, step, isIntermediateStep) {
|
|
|
23786
23806
|
if (currentGuide && _.last(currentGuide.steps).id === stepId) {
|
|
23787
23807
|
dismissCount = _.get(step, 'dismissCount', 0) + 1;
|
|
23788
23808
|
}
|
|
23789
|
-
log.info('advancing guide');
|
|
23790
|
-
advancedGuide(guideId, stepId, get_visitor_id(), seenReason, language, isIntermediateStep, destinationStepId, dismissCount);
|
|
23791
|
-
log.info('update guide status');
|
|
23792
23809
|
var now = new Date().getTime();
|
|
23793
|
-
|
|
23794
|
-
// maintain finalAdvancedAutoAt client-side
|
|
23810
|
+
// maintain finalAdvancedAutoAt client-side before guideAdvanced writes lastStepAdvanced
|
|
23795
23811
|
if (shouldAffectThrottling(currentGuide, seenReason)) {
|
|
23796
23812
|
writeThrottlingStateCache(now, THROTTLING_STATE.ADVANCED);
|
|
23797
23813
|
}
|
|
23814
|
+
log.info('advancing guide');
|
|
23815
|
+
advancedGuide(guideId, stepId, get_visitor_id(), seenReason, language, isIntermediateStep, destinationStepId, dismissCount);
|
|
23816
|
+
log.info('update guide status');
|
|
23817
|
+
_updateGuideStepStatus(guideId, stepId, 'advanced', now, dismissCount, destinationStepId);
|
|
23798
23818
|
log.info('stop guide');
|
|
23799
23819
|
stopGuides();
|
|
23800
23820
|
log.info('start guides');
|
|
@@ -26035,7 +26055,7 @@ function getWhiteLabelSettings(config) {
|
|
|
26035
26055
|
var whiteLabelSettings = ConfigReader.get('whiteLabelSettings');
|
|
26036
26056
|
if (!whiteLabelSettings && config && config.ux === 'novus') {
|
|
26037
26057
|
whiteLabelSettings = {
|
|
26038
|
-
logoUrl: 'https://novus.pendo.io/novus-
|
|
26058
|
+
logoUrl: 'https://novus.pendo.io/branding/novus-vector-white.svg',
|
|
26039
26059
|
logoStyle: {
|
|
26040
26060
|
maxWidth: '40px',
|
|
26041
26061
|
maxHeight: '40px'
|
|
@@ -28505,6 +28525,7 @@ const PluginAPI = {
|
|
|
28505
28525
|
trim,
|
|
28506
28526
|
base64EncodeString,
|
|
28507
28527
|
parseUrl,
|
|
28528
|
+
testPageRule,
|
|
28508
28529
|
addInlineStyles: _.partial(addInlineStyles, ConfigReader),
|
|
28509
28530
|
getNow,
|
|
28510
28531
|
escapeRegExp
|
|
@@ -40248,364 +40269,6 @@ class DOMPrompt {
|
|
|
40248
40269
|
}
|
|
40249
40270
|
}
|
|
40250
40271
|
|
|
40251
|
-
const ASSISTANT_MESSAGE = 'assistantMessage';
|
|
40252
|
-
const CONVERSATION_ID_URL_PATTERN = 'conversationIdUrlPattern';
|
|
40253
|
-
const MESSAGE_ID_ATTR = 'messageIdAttr';
|
|
40254
|
-
const MODEL_USED_ATTR = 'modelUsedAttr';
|
|
40255
|
-
const REACTION_ACTIVE = 'reactionActive';
|
|
40256
|
-
const STREAMING_ACTIVE = 'streamingActive';
|
|
40257
|
-
const STREAMING_ACTIVE_ATTR = 'streamingActiveAttr';
|
|
40258
|
-
const THUMB_DOWN = 'thumbDown';
|
|
40259
|
-
const THUMB_UP = 'thumbUp';
|
|
40260
|
-
const TURN_CONTAINER = 'turnContainer';
|
|
40261
|
-
const TURN_ATTR = 'turnAttr';
|
|
40262
|
-
const USER_MESSAGE = 'userMessage';
|
|
40263
|
-
const CUSTOM_AGENT_ID_URL_PATTERN = 'customAgentIdUrlPattern';
|
|
40264
|
-
const CUSTOM_AGENT_NAME = 'customAgentName';
|
|
40265
|
-
const REQUIRED_SELECTORS = [
|
|
40266
|
-
ASSISTANT_MESSAGE,
|
|
40267
|
-
CONVERSATION_ID_URL_PATTERN,
|
|
40268
|
-
MESSAGE_ID_ATTR,
|
|
40269
|
-
STREAMING_ACTIVE,
|
|
40270
|
-
TURN_CONTAINER,
|
|
40271
|
-
TURN_ATTR,
|
|
40272
|
-
USER_MESSAGE
|
|
40273
|
-
];
|
|
40274
|
-
const STREAMING_END_SETTLE_MS = 500;
|
|
40275
|
-
const SUBMISSION_START_RETRY_MS = 1000;
|
|
40276
|
-
const SUBMISSION_START_MAX_ATTEMPTS = 4;
|
|
40277
|
-
class DOMConversation {
|
|
40278
|
-
// Returns the list of required selector types that are missing or empty
|
|
40279
|
-
// in the given cssSelectors config. An empty array means the config is
|
|
40280
|
-
// valid for instantiation.
|
|
40281
|
-
static validateConfig(cssSelectors) {
|
|
40282
|
-
if (!Array.isArray(cssSelectors)) {
|
|
40283
|
-
return REQUIRED_SELECTORS.slice();
|
|
40284
|
-
}
|
|
40285
|
-
const present = new Set();
|
|
40286
|
-
for (const cfg of cssSelectors) {
|
|
40287
|
-
if (!cfg || !cfg.type || !cfg.cssSelector)
|
|
40288
|
-
continue;
|
|
40289
|
-
if (cfg.type === CONVERSATION_ID_URL_PATTERN) {
|
|
40290
|
-
try {
|
|
40291
|
-
RegExp(cfg.cssSelector);
|
|
40292
|
-
}
|
|
40293
|
-
catch (e) {
|
|
40294
|
-
continue;
|
|
40295
|
-
}
|
|
40296
|
-
}
|
|
40297
|
-
present.add(cfg.type);
|
|
40298
|
-
}
|
|
40299
|
-
return REQUIRED_SELECTORS.filter((r) => !present.has(r));
|
|
40300
|
-
}
|
|
40301
|
-
static supportedPreset(preset) {
|
|
40302
|
-
return preset === 'chatgpt-full';
|
|
40303
|
-
}
|
|
40304
|
-
constructor(pendo, PluginAPI, id, privacyFilters, cssSelectors) {
|
|
40305
|
-
this.selectors = {};
|
|
40306
|
-
this.listeners = [];
|
|
40307
|
-
this.isActive = true;
|
|
40308
|
-
this.observers = [];
|
|
40309
|
-
this.wasStreaming = false;
|
|
40310
|
-
this.streamingEndTimer = null;
|
|
40311
|
-
this.submissionStartRetryTimer = null;
|
|
40312
|
-
this.requestNode = null;
|
|
40313
|
-
this.conversationIdRegex = null;
|
|
40314
|
-
this.customAgentIdRegex = null;
|
|
40315
|
-
this.lastReturnedMessageId = null;
|
|
40316
|
-
this.dom = pendo.dom;
|
|
40317
|
-
this._ = pendo._;
|
|
40318
|
-
this.api = PluginAPI;
|
|
40319
|
-
this.Sizzle = pendo.Sizzle;
|
|
40320
|
-
this.id = id;
|
|
40321
|
-
this.setFilters(privacyFilters);
|
|
40322
|
-
if (!this._.isArray(cssSelectors)) {
|
|
40323
|
-
this.api.log.error(`cssSelectors must be an array, received: ${typeof cssSelectors}`);
|
|
40324
|
-
cssSelectors = [];
|
|
40325
|
-
}
|
|
40326
|
-
this._.each(cssSelectors, (cfg) => {
|
|
40327
|
-
if (cfg && cfg.type) {
|
|
40328
|
-
this.selectors[cfg.type] = cfg.cssSelector;
|
|
40329
|
-
}
|
|
40330
|
-
});
|
|
40331
|
-
this.conversationIdRegex = new RegExp(this.selectors[CONVERSATION_ID_URL_PATTERN]);
|
|
40332
|
-
const customAgentIdUrlPattern = this.selectors[CUSTOM_AGENT_ID_URL_PATTERN];
|
|
40333
|
-
if (customAgentIdUrlPattern) { // this is optional
|
|
40334
|
-
try {
|
|
40335
|
-
this.customAgentIdRegex = new RegExp(customAgentIdUrlPattern);
|
|
40336
|
-
}
|
|
40337
|
-
catch (error) {
|
|
40338
|
-
this.api.log.error(`bad customAgentIdUrlPattern ${customAgentIdUrlPattern}`, { error });
|
|
40339
|
-
}
|
|
40340
|
-
}
|
|
40341
|
-
this.root = document.body;
|
|
40342
|
-
this.setupReactionListener();
|
|
40343
|
-
this.setupStreamingObserver();
|
|
40344
|
-
}
|
|
40345
|
-
setupReactionListener() {
|
|
40346
|
-
if (!this.findSelector(THUMB_UP) && !this.findSelector(THUMB_DOWN))
|
|
40347
|
-
return;
|
|
40348
|
-
this.reactionClickHandler = this.handleReactionClick.bind(this);
|
|
40349
|
-
this.root.addEventListener('click', this.reactionClickHandler, true);
|
|
40350
|
-
}
|
|
40351
|
-
handleReactionClick(evt) {
|
|
40352
|
-
const target = evt.target;
|
|
40353
|
-
if (!target || target.nodeType !== 1)
|
|
40354
|
-
return;
|
|
40355
|
-
const upSelector = this.findSelector(THUMB_UP);
|
|
40356
|
-
const downSelector = this.findSelector(THUMB_DOWN);
|
|
40357
|
-
const thumb = this.dom(target).closest(`${upSelector}, ${downSelector}`);
|
|
40358
|
-
if (!thumb.length)
|
|
40359
|
-
return;
|
|
40360
|
-
const isUp = this.Sizzle.matchesSelector(thumb[0], upSelector);
|
|
40361
|
-
const activeSelector = this.findSelector(REACTION_ACTIVE);
|
|
40362
|
-
const wasPressed = activeSelector && this.Sizzle.matchesSelector(thumb[0], activeSelector);
|
|
40363
|
-
const direction = isUp ? 'positive' : 'negative';
|
|
40364
|
-
const content = wasPressed ? 'unreact' : direction;
|
|
40365
|
-
const assistantNode = thumb.closest(this.findSelector(TURN_CONTAINER))
|
|
40366
|
-
.find(this.findSelector(ASSISTANT_MESSAGE));
|
|
40367
|
-
this.emit('user_reaction', {
|
|
40368
|
-
agentId: this.id,
|
|
40369
|
-
messageId: this.getMessageId(assistantNode),
|
|
40370
|
-
content,
|
|
40371
|
-
privacyFilterApplied: false
|
|
40372
|
-
});
|
|
40373
|
-
}
|
|
40374
|
-
// observe the streaming indicator
|
|
40375
|
-
setupStreamingObserver() {
|
|
40376
|
-
this.wasStreaming = this.select(STREAMING_ACTIVE).length > 0;
|
|
40377
|
-
this.stopStreamingEndTimer();
|
|
40378
|
-
const MutationObserver = getZoneSafeMethod('MutationObserver');
|
|
40379
|
-
const observer = new MutationObserver(this.handleMutation.bind(this));
|
|
40380
|
-
const attrName = this.findSelector(STREAMING_ACTIVE_ATTR);
|
|
40381
|
-
observer.observe(this.root, {
|
|
40382
|
-
childList: true,
|
|
40383
|
-
subtree: true,
|
|
40384
|
-
attributes: true,
|
|
40385
|
-
attributeFilter: attrName ? [attrName] : undefined
|
|
40386
|
-
});
|
|
40387
|
-
this.observers.push(observer);
|
|
40388
|
-
}
|
|
40389
|
-
// look for changes in the state of the streaming indicator.
|
|
40390
|
-
// streaming started -> a prompt has been submitted
|
|
40391
|
-
// streaming ended -> got a response
|
|
40392
|
-
// we don't send the prompt immediately when it's submitted because it might not have a
|
|
40393
|
-
// conversation id yet.
|
|
40394
|
-
// we don't send the agent_response immediately when the streaming indicator is cleared
|
|
40395
|
-
// because it clears slightly before the final text chunk is committed to the DOM so the
|
|
40396
|
-
// end-handling is deferred by a short settle window
|
|
40397
|
-
handleMutation() {
|
|
40398
|
-
const isStreaming = this.select(STREAMING_ACTIVE).length > 0;
|
|
40399
|
-
if (isStreaming === this.wasStreaming)
|
|
40400
|
-
return;
|
|
40401
|
-
if (isStreaming) { // started streaming
|
|
40402
|
-
if (!this.streamingEndTimer) {
|
|
40403
|
-
this.onSubmissionStart();
|
|
40404
|
-
}
|
|
40405
|
-
else {
|
|
40406
|
-
// stream restarted during the settle window (multi-phase response).
|
|
40407
|
-
// treat as a continuation of the prior request, not a new submission.
|
|
40408
|
-
// we'll handle it next time streaming stops
|
|
40409
|
-
this.stopStreamingEndTimer();
|
|
40410
|
-
}
|
|
40411
|
-
}
|
|
40412
|
-
else { // finished streaming
|
|
40413
|
-
this.startStreamingEndTimer();
|
|
40414
|
-
}
|
|
40415
|
-
this.wasStreaming = isStreaming;
|
|
40416
|
-
}
|
|
40417
|
-
onSubmissionStart(attempt = 1) {
|
|
40418
|
-
this.requestNode = this.findLastRequestNode();
|
|
40419
|
-
if (this.requestNode) {
|
|
40420
|
-
this.handleUserMessage(this.requestNode);
|
|
40421
|
-
return;
|
|
40422
|
-
}
|
|
40423
|
-
// URL hasn't flipped yet to include the conversation id on a fresh chat
|
|
40424
|
-
// retry, if that still fails, onSubmissionEnd handles it
|
|
40425
|
-
if (attempt < SUBMISSION_START_MAX_ATTEMPTS) {
|
|
40426
|
-
this.startSubmissionStartRetryTimer(attempt + 1);
|
|
40427
|
-
}
|
|
40428
|
-
}
|
|
40429
|
-
onSubmissionEnd() {
|
|
40430
|
-
this.stopSubmissionStartRetryTimer();
|
|
40431
|
-
let requestNode = this.requestNode;
|
|
40432
|
-
this.requestNode = null;
|
|
40433
|
-
if (!requestNode) {
|
|
40434
|
-
// onSubmissionStart didn't send, send it now
|
|
40435
|
-
requestNode = this.findLastRequestNode();
|
|
40436
|
-
if (!requestNode)
|
|
40437
|
-
return;
|
|
40438
|
-
this.handleUserMessage(requestNode);
|
|
40439
|
-
}
|
|
40440
|
-
const requestTurn = this.findTurnNumber(requestNode);
|
|
40441
|
-
if (requestTurn === null)
|
|
40442
|
-
return;
|
|
40443
|
-
const assistantNodes = this.select(ASSISTANT_MESSAGE);
|
|
40444
|
-
const toEmit = [];
|
|
40445
|
-
for (let i = assistantNodes.length - 1; i >= 0; i--) {
|
|
40446
|
-
const n = this.findTurnNumber(this.dom(assistantNodes[i]));
|
|
40447
|
-
if (n === null)
|
|
40448
|
-
continue;
|
|
40449
|
-
if (n <= requestTurn)
|
|
40450
|
-
break;
|
|
40451
|
-
toEmit.push(assistantNodes[i]);
|
|
40452
|
-
}
|
|
40453
|
-
// emit in chronological order (reverse of collection order)
|
|
40454
|
-
for (let i = toEmit.length - 1; i >= 0; i--) {
|
|
40455
|
-
this.handleAssistantMessage(this.dom(toEmit[i]));
|
|
40456
|
-
}
|
|
40457
|
-
}
|
|
40458
|
-
extractContent(node) {
|
|
40459
|
-
const rawContent = node.text().trim();
|
|
40460
|
-
const content = this.applyPrivacyFilter(rawContent);
|
|
40461
|
-
return { content, privacyFilterApplied: content !== rawContent };
|
|
40462
|
-
}
|
|
40463
|
-
getMessageId(node) {
|
|
40464
|
-
return node.attr(this.findSelector(MESSAGE_ID_ATTR));
|
|
40465
|
-
}
|
|
40466
|
-
handleUserMessage(node) {
|
|
40467
|
-
this.emit('prompt', Object.assign({ agentId: this.id, messageId: this.getMessageId(node) }, this.extractContent(node)));
|
|
40468
|
-
}
|
|
40469
|
-
handleAssistantMessage(node) {
|
|
40470
|
-
const modelUsedAttr = this.findSelector(MODEL_USED_ATTR);
|
|
40471
|
-
const modelUsed = modelUsedAttr && node.attr(modelUsedAttr);
|
|
40472
|
-
this.emit('agent_response', Object.assign({ agentId: this.id, messageId: this.getMessageId(node), agentModelsUsed: modelUsed ? [modelUsed] : [] }, this.extractContent(node)));
|
|
40473
|
-
}
|
|
40474
|
-
findLastRequestNode() {
|
|
40475
|
-
if (!this.getConversationId())
|
|
40476
|
-
return null;
|
|
40477
|
-
const node = this._.last(this.select(USER_MESSAGE));
|
|
40478
|
-
if (!node)
|
|
40479
|
-
return null;
|
|
40480
|
-
const $node = this.dom(node);
|
|
40481
|
-
const messageId = this.getMessageId($node);
|
|
40482
|
-
if (!messageId)
|
|
40483
|
-
return null;
|
|
40484
|
-
if (messageId === this.lastReturnedMessageId)
|
|
40485
|
-
return null;
|
|
40486
|
-
this.lastReturnedMessageId = messageId;
|
|
40487
|
-
return $node;
|
|
40488
|
-
}
|
|
40489
|
-
findTurnNumber(node) {
|
|
40490
|
-
const turnSelector = this.findSelector(TURN_CONTAINER);
|
|
40491
|
-
const turnAttr = this.findSelector(TURN_ATTR);
|
|
40492
|
-
const turn = node.closest(turnSelector).attr(turnAttr);
|
|
40493
|
-
if (!turn)
|
|
40494
|
-
return null;
|
|
40495
|
-
const seqMatch = String(turn).match(/\d+/);
|
|
40496
|
-
return seqMatch ? parseInt(seqMatch[0], 10) : null;
|
|
40497
|
-
}
|
|
40498
|
-
findSelector(type) {
|
|
40499
|
-
return this.selectors[type];
|
|
40500
|
-
}
|
|
40501
|
-
select(type) {
|
|
40502
|
-
return this.dom(this.findSelector(type), this.root);
|
|
40503
|
-
}
|
|
40504
|
-
getConversationId() {
|
|
40505
|
-
const m = location.href.match(this.conversationIdRegex);
|
|
40506
|
-
if (!m)
|
|
40507
|
-
return null;
|
|
40508
|
-
return m[1] !== undefined ? m[1] : m[0];
|
|
40509
|
-
}
|
|
40510
|
-
getCustomAgentId() {
|
|
40511
|
-
if (!this.customAgentIdRegex)
|
|
40512
|
-
return undefined;
|
|
40513
|
-
const m = location.href.match(this.customAgentIdRegex);
|
|
40514
|
-
if (!m)
|
|
40515
|
-
return undefined;
|
|
40516
|
-
return m[1] !== undefined ? m[1] : m[0];
|
|
40517
|
-
}
|
|
40518
|
-
getCustomAgentName() {
|
|
40519
|
-
if (!this.findSelector(CUSTOM_AGENT_NAME))
|
|
40520
|
-
return '';
|
|
40521
|
-
const node = this.select(CUSTOM_AGENT_NAME);
|
|
40522
|
-
if (!node.length)
|
|
40523
|
-
return '';
|
|
40524
|
-
return node.text().trim();
|
|
40525
|
-
}
|
|
40526
|
-
addCustomAgentInfo(eventPayload) {
|
|
40527
|
-
const customAgentId = this.getCustomAgentId();
|
|
40528
|
-
if (!customAgentId)
|
|
40529
|
-
return;
|
|
40530
|
-
eventPayload.customAgentId = customAgentId;
|
|
40531
|
-
const customAgentName = this.getCustomAgentName();
|
|
40532
|
-
if (customAgentName) {
|
|
40533
|
-
eventPayload.customAgentName = customAgentName;
|
|
40534
|
-
}
|
|
40535
|
-
}
|
|
40536
|
-
setFilters(candidateFilter) {
|
|
40537
|
-
if (!candidateFilter) {
|
|
40538
|
-
this.privacyFilters = null;
|
|
40539
|
-
return null;
|
|
40540
|
-
}
|
|
40541
|
-
try {
|
|
40542
|
-
this.privacyFilters = new RegExp(candidateFilter, 'gmi');
|
|
40543
|
-
}
|
|
40544
|
-
catch (e) {
|
|
40545
|
-
this.privacyFilters = null;
|
|
40546
|
-
this.api.log.error(e);
|
|
40547
|
-
}
|
|
40548
|
-
return this.privacyFilters;
|
|
40549
|
-
}
|
|
40550
|
-
applyPrivacyFilter(candidateValue, filters = null) {
|
|
40551
|
-
const filtersToUse = filters || this.privacyFilters;
|
|
40552
|
-
if (!filtersToUse || !this._.isRegExp(filtersToUse))
|
|
40553
|
-
return candidateValue;
|
|
40554
|
-
return candidateValue.replace(filtersToUse, 'redacted');
|
|
40555
|
-
}
|
|
40556
|
-
onSubmit(callback) {
|
|
40557
|
-
this.listeners.push(callback);
|
|
40558
|
-
}
|
|
40559
|
-
emit(eventType, eventPayload) {
|
|
40560
|
-
eventPayload.conversationId = this.getConversationId();
|
|
40561
|
-
this.addCustomAgentInfo(eventPayload);
|
|
40562
|
-
this._.each(this.listeners, (cb) => cb(eventType, eventPayload));
|
|
40563
|
-
}
|
|
40564
|
-
startStreamingEndTimer() {
|
|
40565
|
-
this.stopStreamingEndTimer();
|
|
40566
|
-
this.streamingEndTimer = setTimeout$1(() => {
|
|
40567
|
-
this.streamingEndTimer = null;
|
|
40568
|
-
this.onSubmissionEnd();
|
|
40569
|
-
}, STREAMING_END_SETTLE_MS);
|
|
40570
|
-
}
|
|
40571
|
-
stopStreamingEndTimer() {
|
|
40572
|
-
if (!this.streamingEndTimer)
|
|
40573
|
-
return;
|
|
40574
|
-
clearTimeout(this.streamingEndTimer);
|
|
40575
|
-
this.streamingEndTimer = null;
|
|
40576
|
-
}
|
|
40577
|
-
startSubmissionStartRetryTimer(attempt) {
|
|
40578
|
-
this.stopSubmissionStartRetryTimer();
|
|
40579
|
-
this.submissionStartRetryTimer = setTimeout$1(() => {
|
|
40580
|
-
this.submissionStartRetryTimer = null;
|
|
40581
|
-
this.onSubmissionStart(attempt);
|
|
40582
|
-
}, SUBMISSION_START_RETRY_MS);
|
|
40583
|
-
}
|
|
40584
|
-
stopSubmissionStartRetryTimer() {
|
|
40585
|
-
if (!this.submissionStartRetryTimer)
|
|
40586
|
-
return;
|
|
40587
|
-
clearTimeout(this.submissionStartRetryTimer);
|
|
40588
|
-
this.submissionStartRetryTimer = null;
|
|
40589
|
-
}
|
|
40590
|
-
suspend() {
|
|
40591
|
-
this.isActive = false;
|
|
40592
|
-
}
|
|
40593
|
-
resume() {
|
|
40594
|
-
this.isActive = true;
|
|
40595
|
-
}
|
|
40596
|
-
teardown() {
|
|
40597
|
-
this._.each(this.observers, (o) => o && o.disconnect && o.disconnect());
|
|
40598
|
-
this.observers = [];
|
|
40599
|
-
if (this.reactionClickHandler && this.root) {
|
|
40600
|
-
this.root.removeEventListener('click', this.reactionClickHandler, true);
|
|
40601
|
-
this.reactionClickHandler = null;
|
|
40602
|
-
}
|
|
40603
|
-
this.stopStreamingEndTimer();
|
|
40604
|
-
this.stopSubmissionStartRetryTimer();
|
|
40605
|
-
this.listeners = [];
|
|
40606
|
-
}
|
|
40607
|
-
}
|
|
40608
|
-
|
|
40609
40272
|
/*
|
|
40610
40273
|
* Prompt Analytics Plugin
|
|
40611
40274
|
*
|
|
@@ -40690,6 +40353,9 @@ class PromptPlugin {
|
|
|
40690
40353
|
});
|
|
40691
40354
|
const currentlyActiveIds = this._.map(this._.filter(this.prompts, prompt => prompt.isActive), prompt => prompt.id);
|
|
40692
40355
|
this._.each(allAgents, aiAgent => {
|
|
40356
|
+
if (this.isConversationAgent(aiAgent)) {
|
|
40357
|
+
return;
|
|
40358
|
+
}
|
|
40693
40359
|
const isCurrentlyActive = this._.contains(currentlyActiveIds, aiAgent.id);
|
|
40694
40360
|
const shouldBeActive = this.testPageRule(aiAgent.pageRule, currentUrl, aiAgent.id);
|
|
40695
40361
|
if (shouldBeActive && !isCurrentlyActive) {
|
|
@@ -40708,6 +40374,10 @@ class PromptPlugin {
|
|
|
40708
40374
|
return candidateValue;
|
|
40709
40375
|
return candidateValue.replace(filters, 'redacted');
|
|
40710
40376
|
}
|
|
40377
|
+
// conversation agents, owned by the ConversationAnalytics plugin.
|
|
40378
|
+
isConversationAgent(agent) {
|
|
40379
|
+
return agent.preset && agent.preset != 'chatgpt';
|
|
40380
|
+
}
|
|
40711
40381
|
onConfigLoaded(aiAgents = []) {
|
|
40712
40382
|
if (!aiAgents || !this._.isArray(aiAgents) || aiAgents.length === 0) {
|
|
40713
40383
|
return;
|
|
@@ -40715,6 +40385,9 @@ class PromptPlugin {
|
|
|
40715
40385
|
const networkAgents = [];
|
|
40716
40386
|
const domAgents = [];
|
|
40717
40387
|
for (const agent of aiAgents) {
|
|
40388
|
+
if (this.isConversationAgent(agent)) {
|
|
40389
|
+
continue;
|
|
40390
|
+
}
|
|
40718
40391
|
if (agent.trackingMethod === 'network') {
|
|
40719
40392
|
networkAgents.push(agent);
|
|
40720
40393
|
}
|
|
@@ -40770,25 +40443,12 @@ class PromptPlugin {
|
|
|
40770
40443
|
}
|
|
40771
40444
|
}
|
|
40772
40445
|
observePrompt(config) {
|
|
40773
|
-
const { id, cssSelectors, privacyFilters
|
|
40446
|
+
const { id, cssSelectors, privacyFilters } = config;
|
|
40774
40447
|
// Check if agent already exists
|
|
40775
40448
|
const existingPrompt = this._.find(this.prompts, prompt => prompt.id === id);
|
|
40776
40449
|
if (existingPrompt) {
|
|
40777
40450
|
return;
|
|
40778
40451
|
}
|
|
40779
|
-
if (DOMConversation.supportedPreset(preset)) {
|
|
40780
|
-
const missing = DOMConversation.validateConfig(cssSelectors);
|
|
40781
|
-
if (missing.length) {
|
|
40782
|
-
this.api.log.error('aiAgent config missing required selectors', { agentId: id, missing });
|
|
40783
|
-
return;
|
|
40784
|
-
}
|
|
40785
|
-
const conversation = new DOMConversation(this.pendo, this.api, id, privacyFilters, cssSelectors);
|
|
40786
|
-
conversation.onSubmit((eventType, payload) => {
|
|
40787
|
-
this.sendConversationEvent(eventType, payload);
|
|
40788
|
-
});
|
|
40789
|
-
this.prompts.push(conversation);
|
|
40790
|
-
return;
|
|
40791
|
-
}
|
|
40792
40452
|
let inputCssSelectors, submitCssSelectors;
|
|
40793
40453
|
inputCssSelectors = this.parseCssSelector('input', cssSelectors);
|
|
40794
40454
|
submitCssSelectors = this.parseCssSelector('submit', cssSelectors);
|
|
@@ -40815,15 +40475,6 @@ class PromptPlugin {
|
|
|
40815
40475
|
}, promptEvent);
|
|
40816
40476
|
this.api.analytics.collectEvent('prompt', event, undefined, 'agentic');
|
|
40817
40477
|
}
|
|
40818
|
-
sendConversationEvent(eventType, eventPayload) {
|
|
40819
|
-
// Block event if the prompt is suspended
|
|
40820
|
-
const conv = this._.find(this.prompts, p => p.id === eventPayload.agentId);
|
|
40821
|
-
if (conv && !conv.isActive) {
|
|
40822
|
-
return;
|
|
40823
|
-
}
|
|
40824
|
-
eventPayload = this._.extend({ agentType: 'conversation' }, eventPayload);
|
|
40825
|
-
this.api.analytics.collectEvent(eventType, eventPayload, undefined, 'agentic');
|
|
40826
|
-
}
|
|
40827
40478
|
teardown() {
|
|
40828
40479
|
this._.each(this.prompts, (prompt) => prompt.teardown());
|
|
40829
40480
|
if (this.agentsCache) {
|
|
@@ -41210,10 +40861,7 @@ class WebAnalytics {
|
|
|
41210
40861
|
PluginAPI.attachEvent(PluginAPI.Events, 'eventCaptured', bind(this.addUtmToEvent, this)),
|
|
41211
40862
|
PluginAPI.attachEvent(PluginAPI.Events, 'sessionChanged', bind(this.sessionChanged, this))
|
|
41212
40863
|
];
|
|
41213
|
-
|
|
41214
|
-
if (!params || pendo._.isEmpty(params)) {
|
|
41215
|
-
params = this.extractParameters(window.location.href, document.referrer);
|
|
41216
|
-
}
|
|
40864
|
+
this.suffix = this.api.ConfigReader.get('identityStorageSuffix');
|
|
41217
40865
|
/**
|
|
41218
40866
|
* Stores UTM parameters that were present in the URL when the visitor loaded the application.
|
|
41219
40867
|
*
|
|
@@ -41222,9 +40870,12 @@ class WebAnalytics {
|
|
|
41222
40870
|
* @access public
|
|
41223
40871
|
* @label WEB_ANALYTICS_STORAGE_KEY
|
|
41224
40872
|
*/
|
|
41225
|
-
this.api.agentStorage.registry.addLocal(WEB_ANALYTICS_STORAGE_KEY);
|
|
40873
|
+
this.api.agentStorage.registry.addLocal(WEB_ANALYTICS_STORAGE_KEY, { cookieSuffix: this.suffix, isSecure: true });
|
|
40874
|
+
let params = this.loadParameters(PluginAPI.agentStorage);
|
|
40875
|
+
if (!params || pendo._.isEmpty(params)) {
|
|
40876
|
+
params = this.extractParameters(window.location.href, document.referrer);
|
|
40877
|
+
}
|
|
41226
40878
|
this.storeParameters(params, PluginAPI.agentStorage);
|
|
41227
|
-
this.suffix = this.api.ConfigReader.get('identityStorageSuffix');
|
|
41228
40879
|
}
|
|
41229
40880
|
extractParameters(url, referrer) {
|
|
41230
40881
|
const locationUrl = new URL(url);
|
|
@@ -41279,12 +40930,12 @@ class WebAnalytics {
|
|
|
41279
40930
|
}
|
|
41280
40931
|
storeParameters(params, storage) {
|
|
41281
40932
|
if (this.pendo._.size(params) > 0) {
|
|
41282
|
-
storage.write(WEB_ANALYTICS_STORAGE_KEY, JSON.stringify(params)
|
|
40933
|
+
storage.write(WEB_ANALYTICS_STORAGE_KEY, JSON.stringify(params));
|
|
41283
40934
|
}
|
|
41284
40935
|
}
|
|
41285
40936
|
loadParameters(storage) {
|
|
41286
|
-
const storedParamsJson = storage.read(WEB_ANALYTICS_STORAGE_KEY
|
|
41287
|
-
storage.read(WEB_ANALYTICS_STORAGE_KEY);
|
|
40937
|
+
const storedParamsJson = storage.read(WEB_ANALYTICS_STORAGE_KEY) ||
|
|
40938
|
+
storage.read(WEB_ANALYTICS_STORAGE_KEY, false, '');
|
|
41288
40939
|
if (storedParamsJson)
|
|
41289
40940
|
return JSON.parse(storedParamsJson);
|
|
41290
40941
|
return null;
|
|
@@ -41310,7 +40961,7 @@ class WebAnalytics {
|
|
|
41310
40961
|
sessionChanged() {
|
|
41311
40962
|
const agentStorage = this.api.agentStorage;
|
|
41312
40963
|
agentStorage.clear(WEB_ANALYTICS_STORAGE_KEY);
|
|
41313
|
-
agentStorage.clear(WEB_ANALYTICS_STORAGE_KEY, false,
|
|
40964
|
+
agentStorage.clear(WEB_ANALYTICS_STORAGE_KEY, false, '');
|
|
41314
40965
|
const params = this.extractParameters(window.location.href, document.referrer);
|
|
41315
40966
|
this.storeParameters(params, agentStorage);
|
|
41316
40967
|
}
|
|
@@ -41805,6 +41456,21 @@ function branchingGoToStep(event, step, guide, pendo) {
|
|
|
41805
41456
|
event.cancel = true;
|
|
41806
41457
|
}
|
|
41807
41458
|
|
|
41459
|
+
var DANGEROUS_URL_SCHEME = /^\s*(javascript|data|vbscript):/i;
|
|
41460
|
+
function encodeMetadataForUrl(value, urlBeforePlaceholder) {
|
|
41461
|
+
if (urlBeforePlaceholder === void 0) { urlBeforePlaceholder = ''; }
|
|
41462
|
+
var str = value === undefined || value === null ? '' : String(value);
|
|
41463
|
+
var inUrlComponent = urlBeforePlaceholder.indexOf('?') !== -1 || urlBeforePlaceholder.indexOf('#') !== -1;
|
|
41464
|
+
if (inUrlComponent) {
|
|
41465
|
+
return window.encodeURIComponent(str);
|
|
41466
|
+
}
|
|
41467
|
+
var schemeAlreadySet = urlBeforePlaceholder.indexOf(':') !== -1;
|
|
41468
|
+
if (!schemeAlreadySet && DANGEROUS_URL_SCHEME.test(str)) {
|
|
41469
|
+
return '';
|
|
41470
|
+
}
|
|
41471
|
+
return window.encodeURI(str);
|
|
41472
|
+
}
|
|
41473
|
+
|
|
41808
41474
|
const MetadataSubstitution = {
|
|
41809
41475
|
name: 'MetadataSubstitution',
|
|
41810
41476
|
script(step, guide, pendo) {
|
|
@@ -41948,8 +41614,10 @@ function substituteMetadataByTarget(data, target, mdValue, matched) {
|
|
|
41948
41614
|
? (data.getAttribute(target) || '')
|
|
41949
41615
|
: data[target];
|
|
41950
41616
|
if (target === 'href' || target === 'value') {
|
|
41951
|
-
const
|
|
41952
|
-
|
|
41617
|
+
const decodedUrl = decodeURI(current);
|
|
41618
|
+
const urlBeforePlaceholder = decodedUrl.slice(0, decodedUrl.indexOf(matched[0]));
|
|
41619
|
+
const safeValue = encodeMetadataForUrl(mdValue, urlBeforePlaceholder);
|
|
41620
|
+
data[target] = decodedUrl.replace(matched[0], safeValue);
|
|
41953
41621
|
}
|
|
41954
41622
|
else {
|
|
41955
41623
|
data[target] = current.replace(matched[0], mdValue);
|
|
@@ -53384,4 +53052,500 @@ const PredictGuides = () => {
|
|
|
53384
53052
|
};
|
|
53385
53053
|
};
|
|
53386
53054
|
|
|
53387
|
-
|
|
53055
|
+
const ASSISTANT_MESSAGE = 'assistantMessage';
|
|
53056
|
+
const CONVERSATION_ID_URL_PATTERN = 'conversationIdUrlPattern';
|
|
53057
|
+
const MESSAGE_ID_ATTR = 'messageIdAttr';
|
|
53058
|
+
const MODEL_USED_ATTR = 'modelUsedAttr';
|
|
53059
|
+
const REACTION_ACTIVE = 'reactionActive';
|
|
53060
|
+
const STREAMING_ACTIVE = 'streamingActive';
|
|
53061
|
+
const STREAMING_ACTIVE_ATTR = 'streamingActiveAttr';
|
|
53062
|
+
const THUMB_DOWN = 'thumbDown';
|
|
53063
|
+
const THUMB_UP = 'thumbUp';
|
|
53064
|
+
const TURN_CONTAINER = 'turnContainer';
|
|
53065
|
+
const TURN_ATTR = 'turnAttr';
|
|
53066
|
+
const USER_MESSAGE = 'userMessage';
|
|
53067
|
+
const CUSTOM_AGENT_ID_URL_PATTERN = 'customAgentIdUrlPattern';
|
|
53068
|
+
const CUSTOM_AGENT_NAME = 'customAgentName';
|
|
53069
|
+
const REQUIRED_SELECTORS = [
|
|
53070
|
+
ASSISTANT_MESSAGE,
|
|
53071
|
+
CONVERSATION_ID_URL_PATTERN,
|
|
53072
|
+
MESSAGE_ID_ATTR,
|
|
53073
|
+
STREAMING_ACTIVE,
|
|
53074
|
+
TURN_CONTAINER,
|
|
53075
|
+
TURN_ATTR,
|
|
53076
|
+
USER_MESSAGE
|
|
53077
|
+
];
|
|
53078
|
+
const STREAMING_END_SETTLE_MS = 500;
|
|
53079
|
+
const SUBMISSION_START_RETRY_MS = 1000;
|
|
53080
|
+
const SUBMISSION_START_MAX_ATTEMPTS = 4;
|
|
53081
|
+
class DOMConversation {
|
|
53082
|
+
// Returns the list of required selector types that are missing or empty
|
|
53083
|
+
// in the given cssSelectors config. An empty array means the config is
|
|
53084
|
+
// valid for instantiation.
|
|
53085
|
+
static validateConfig(cssSelectors) {
|
|
53086
|
+
if (!Array.isArray(cssSelectors)) {
|
|
53087
|
+
return REQUIRED_SELECTORS.slice();
|
|
53088
|
+
}
|
|
53089
|
+
const present = new Set();
|
|
53090
|
+
for (const cfg of cssSelectors) {
|
|
53091
|
+
if (!cfg || !cfg.type || !cfg.cssSelector)
|
|
53092
|
+
continue;
|
|
53093
|
+
if (cfg.type === CONVERSATION_ID_URL_PATTERN) {
|
|
53094
|
+
try {
|
|
53095
|
+
RegExp(cfg.cssSelector);
|
|
53096
|
+
}
|
|
53097
|
+
catch (e) {
|
|
53098
|
+
continue;
|
|
53099
|
+
}
|
|
53100
|
+
}
|
|
53101
|
+
present.add(cfg.type);
|
|
53102
|
+
}
|
|
53103
|
+
return REQUIRED_SELECTORS.filter((r) => !present.has(r));
|
|
53104
|
+
}
|
|
53105
|
+
static supportedPreset(preset) {
|
|
53106
|
+
return preset === 'chatgpt-full';
|
|
53107
|
+
}
|
|
53108
|
+
constructor(pendo, PluginAPI, id, privacyFilters, cssSelectors) {
|
|
53109
|
+
this.selectors = {};
|
|
53110
|
+
this.listeners = [];
|
|
53111
|
+
this.isActive = true;
|
|
53112
|
+
this.observers = [];
|
|
53113
|
+
this.wasStreaming = false;
|
|
53114
|
+
this.streamingEndTimer = null;
|
|
53115
|
+
this.submissionStartRetryTimer = null;
|
|
53116
|
+
this.requestNode = null;
|
|
53117
|
+
this.conversationIdRegex = null;
|
|
53118
|
+
this.customAgentIdRegex = null;
|
|
53119
|
+
this.lastReturnedMessageId = null;
|
|
53120
|
+
this.dom = pendo.dom;
|
|
53121
|
+
this._ = pendo._;
|
|
53122
|
+
this.api = PluginAPI;
|
|
53123
|
+
this.Sizzle = pendo.Sizzle;
|
|
53124
|
+
this.id = id;
|
|
53125
|
+
this.setFilters(privacyFilters);
|
|
53126
|
+
if (!this._.isArray(cssSelectors)) {
|
|
53127
|
+
this.api.log.error(`cssSelectors must be an array, received: ${typeof cssSelectors}`);
|
|
53128
|
+
cssSelectors = [];
|
|
53129
|
+
}
|
|
53130
|
+
this._.each(cssSelectors, (cfg) => {
|
|
53131
|
+
if (cfg && cfg.type) {
|
|
53132
|
+
this.selectors[cfg.type] = cfg.cssSelector;
|
|
53133
|
+
}
|
|
53134
|
+
});
|
|
53135
|
+
this.conversationIdRegex = new RegExp(this.selectors[CONVERSATION_ID_URL_PATTERN]);
|
|
53136
|
+
const customAgentIdUrlPattern = this.selectors[CUSTOM_AGENT_ID_URL_PATTERN];
|
|
53137
|
+
if (customAgentIdUrlPattern) { // this is optional
|
|
53138
|
+
try {
|
|
53139
|
+
this.customAgentIdRegex = new RegExp(customAgentIdUrlPattern);
|
|
53140
|
+
}
|
|
53141
|
+
catch (error) {
|
|
53142
|
+
this.api.log.error(`bad customAgentIdUrlPattern ${customAgentIdUrlPattern}`, { error });
|
|
53143
|
+
}
|
|
53144
|
+
}
|
|
53145
|
+
this.root = document.body;
|
|
53146
|
+
this.setupReactionListener();
|
|
53147
|
+
this.setupStreamingObserver();
|
|
53148
|
+
}
|
|
53149
|
+
setupReactionListener() {
|
|
53150
|
+
if (!this.findSelector(THUMB_UP) && !this.findSelector(THUMB_DOWN))
|
|
53151
|
+
return;
|
|
53152
|
+
this.reactionClickHandler = this.handleReactionClick.bind(this);
|
|
53153
|
+
this.root.addEventListener('click', this.reactionClickHandler, true);
|
|
53154
|
+
}
|
|
53155
|
+
handleReactionClick(evt) {
|
|
53156
|
+
const target = evt.target;
|
|
53157
|
+
if (!target || target.nodeType !== 1)
|
|
53158
|
+
return;
|
|
53159
|
+
const upSelector = this.findSelector(THUMB_UP);
|
|
53160
|
+
const downSelector = this.findSelector(THUMB_DOWN);
|
|
53161
|
+
const thumb = this.dom(target).closest(`${upSelector}, ${downSelector}`);
|
|
53162
|
+
if (!thumb.length)
|
|
53163
|
+
return;
|
|
53164
|
+
const isUp = this.Sizzle.matchesSelector(thumb[0], upSelector);
|
|
53165
|
+
const activeSelector = this.findSelector(REACTION_ACTIVE);
|
|
53166
|
+
const wasPressed = activeSelector && this.Sizzle.matchesSelector(thumb[0], activeSelector);
|
|
53167
|
+
const direction = isUp ? 'positive' : 'negative';
|
|
53168
|
+
const content = wasPressed ? 'unreact' : direction;
|
|
53169
|
+
const assistantNode = thumb.closest(this.findSelector(TURN_CONTAINER))
|
|
53170
|
+
.find(this.findSelector(ASSISTANT_MESSAGE));
|
|
53171
|
+
this.emit('user_reaction', {
|
|
53172
|
+
agentId: this.id,
|
|
53173
|
+
messageId: this.getMessageId(assistantNode),
|
|
53174
|
+
content,
|
|
53175
|
+
privacyFilterApplied: false
|
|
53176
|
+
});
|
|
53177
|
+
}
|
|
53178
|
+
// observe the streaming indicator
|
|
53179
|
+
setupStreamingObserver() {
|
|
53180
|
+
this.wasStreaming = this.select(STREAMING_ACTIVE).length > 0;
|
|
53181
|
+
this.stopStreamingEndTimer();
|
|
53182
|
+
const MutationObserver = getZoneSafeMethod(this._, 'MutationObserver');
|
|
53183
|
+
const observer = new MutationObserver(this.handleMutation.bind(this));
|
|
53184
|
+
const attrName = this.findSelector(STREAMING_ACTIVE_ATTR);
|
|
53185
|
+
observer.observe(this.root, {
|
|
53186
|
+
childList: true,
|
|
53187
|
+
subtree: true,
|
|
53188
|
+
attributes: true,
|
|
53189
|
+
attributeFilter: attrName ? [attrName] : undefined
|
|
53190
|
+
});
|
|
53191
|
+
this.observers.push(observer);
|
|
53192
|
+
}
|
|
53193
|
+
// look for changes in the state of the streaming indicator.
|
|
53194
|
+
// streaming started -> a prompt has been submitted
|
|
53195
|
+
// streaming ended -> got a response
|
|
53196
|
+
// we don't send the prompt immediately when it's submitted because it might not have a
|
|
53197
|
+
// conversation id yet.
|
|
53198
|
+
// we don't send the agent_response immediately when the streaming indicator is cleared
|
|
53199
|
+
// because it clears slightly before the final text chunk is committed to the DOM so the
|
|
53200
|
+
// end-handling is deferred by a short settle window
|
|
53201
|
+
handleMutation() {
|
|
53202
|
+
const isStreaming = this.select(STREAMING_ACTIVE).length > 0;
|
|
53203
|
+
if (isStreaming === this.wasStreaming)
|
|
53204
|
+
return;
|
|
53205
|
+
if (isStreaming) { // started streaming
|
|
53206
|
+
if (!this.streamingEndTimer) {
|
|
53207
|
+
this.onSubmissionStart();
|
|
53208
|
+
}
|
|
53209
|
+
else {
|
|
53210
|
+
// stream restarted during the settle window (multi-phase response).
|
|
53211
|
+
// treat as a continuation of the prior request, not a new submission.
|
|
53212
|
+
// we'll handle it next time streaming stops
|
|
53213
|
+
this.stopStreamingEndTimer();
|
|
53214
|
+
}
|
|
53215
|
+
}
|
|
53216
|
+
else { // finished streaming
|
|
53217
|
+
this.startStreamingEndTimer();
|
|
53218
|
+
}
|
|
53219
|
+
this.wasStreaming = isStreaming;
|
|
53220
|
+
}
|
|
53221
|
+
onSubmissionStart(attempt = 1) {
|
|
53222
|
+
this.requestNode = this.findLastRequestNode();
|
|
53223
|
+
if (this.requestNode) {
|
|
53224
|
+
this.handleUserMessage(this.requestNode);
|
|
53225
|
+
return;
|
|
53226
|
+
}
|
|
53227
|
+
// URL hasn't flipped yet to include the conversation id on a fresh chat
|
|
53228
|
+
// retry, if that still fails, onSubmissionEnd handles it
|
|
53229
|
+
if (attempt < SUBMISSION_START_MAX_ATTEMPTS) {
|
|
53230
|
+
this.startSubmissionStartRetryTimer(attempt + 1);
|
|
53231
|
+
}
|
|
53232
|
+
}
|
|
53233
|
+
onSubmissionEnd() {
|
|
53234
|
+
this.stopSubmissionStartRetryTimer();
|
|
53235
|
+
let requestNode = this.requestNode;
|
|
53236
|
+
this.requestNode = null;
|
|
53237
|
+
if (!requestNode) {
|
|
53238
|
+
// onSubmissionStart didn't send, send it now
|
|
53239
|
+
requestNode = this.findLastRequestNode();
|
|
53240
|
+
if (!requestNode)
|
|
53241
|
+
return;
|
|
53242
|
+
this.handleUserMessage(requestNode);
|
|
53243
|
+
}
|
|
53244
|
+
const requestTurn = this.findTurnNumber(requestNode);
|
|
53245
|
+
if (requestTurn === null)
|
|
53246
|
+
return;
|
|
53247
|
+
const assistantNodes = this.select(ASSISTANT_MESSAGE);
|
|
53248
|
+
const toEmit = [];
|
|
53249
|
+
for (let i = assistantNodes.length - 1; i >= 0; i--) {
|
|
53250
|
+
const n = this.findTurnNumber(this.dom(assistantNodes[i]));
|
|
53251
|
+
if (n === null)
|
|
53252
|
+
continue;
|
|
53253
|
+
if (n <= requestTurn)
|
|
53254
|
+
break;
|
|
53255
|
+
toEmit.push(assistantNodes[i]);
|
|
53256
|
+
}
|
|
53257
|
+
// emit in chronological order (reverse of collection order)
|
|
53258
|
+
for (let i = toEmit.length - 1; i >= 0; i--) {
|
|
53259
|
+
this.handleAssistantMessage(this.dom(toEmit[i]));
|
|
53260
|
+
}
|
|
53261
|
+
}
|
|
53262
|
+
extractContent(node) {
|
|
53263
|
+
const rawContent = node.text().trim();
|
|
53264
|
+
const content = this.applyPrivacyFilter(rawContent);
|
|
53265
|
+
return { content, privacyFilterApplied: content !== rawContent };
|
|
53266
|
+
}
|
|
53267
|
+
getMessageId(node) {
|
|
53268
|
+
return node.attr(this.findSelector(MESSAGE_ID_ATTR));
|
|
53269
|
+
}
|
|
53270
|
+
handleUserMessage(node) {
|
|
53271
|
+
this.emit('prompt', Object.assign({ agentId: this.id, messageId: this.getMessageId(node) }, this.extractContent(node)));
|
|
53272
|
+
}
|
|
53273
|
+
handleAssistantMessage(node) {
|
|
53274
|
+
const modelUsedAttr = this.findSelector(MODEL_USED_ATTR);
|
|
53275
|
+
const modelUsed = modelUsedAttr && node.attr(modelUsedAttr);
|
|
53276
|
+
this.emit('agent_response', Object.assign({ agentId: this.id, messageId: this.getMessageId(node), agentModelsUsed: modelUsed ? [modelUsed] : [] }, this.extractContent(node)));
|
|
53277
|
+
}
|
|
53278
|
+
findLastRequestNode() {
|
|
53279
|
+
if (!this.getConversationId())
|
|
53280
|
+
return null;
|
|
53281
|
+
const node = this._.last(this.select(USER_MESSAGE));
|
|
53282
|
+
if (!node)
|
|
53283
|
+
return null;
|
|
53284
|
+
const $node = this.dom(node);
|
|
53285
|
+
const messageId = this.getMessageId($node);
|
|
53286
|
+
if (!messageId)
|
|
53287
|
+
return null;
|
|
53288
|
+
if (messageId === this.lastReturnedMessageId)
|
|
53289
|
+
return null;
|
|
53290
|
+
this.lastReturnedMessageId = messageId;
|
|
53291
|
+
return $node;
|
|
53292
|
+
}
|
|
53293
|
+
findTurnNumber(node) {
|
|
53294
|
+
const turnSelector = this.findSelector(TURN_CONTAINER);
|
|
53295
|
+
const turnAttr = this.findSelector(TURN_ATTR);
|
|
53296
|
+
const turn = node.closest(turnSelector).attr(turnAttr);
|
|
53297
|
+
if (!turn)
|
|
53298
|
+
return null;
|
|
53299
|
+
const seqMatch = String(turn).match(/\d+/);
|
|
53300
|
+
return seqMatch ? parseInt(seqMatch[0], 10) : null;
|
|
53301
|
+
}
|
|
53302
|
+
findSelector(type) {
|
|
53303
|
+
return this.selectors[type];
|
|
53304
|
+
}
|
|
53305
|
+
select(type) {
|
|
53306
|
+
return this.dom(this.findSelector(type), this.root);
|
|
53307
|
+
}
|
|
53308
|
+
getConversationId() {
|
|
53309
|
+
const m = location.href.match(this.conversationIdRegex);
|
|
53310
|
+
if (!m)
|
|
53311
|
+
return null;
|
|
53312
|
+
return m[1] !== undefined ? m[1] : m[0];
|
|
53313
|
+
}
|
|
53314
|
+
getCustomAgentId() {
|
|
53315
|
+
if (!this.customAgentIdRegex)
|
|
53316
|
+
return undefined;
|
|
53317
|
+
const m = location.href.match(this.customAgentIdRegex);
|
|
53318
|
+
if (!m)
|
|
53319
|
+
return undefined;
|
|
53320
|
+
return m[1] !== undefined ? m[1] : m[0];
|
|
53321
|
+
}
|
|
53322
|
+
getCustomAgentName() {
|
|
53323
|
+
if (!this.findSelector(CUSTOM_AGENT_NAME))
|
|
53324
|
+
return '';
|
|
53325
|
+
const node = this.select(CUSTOM_AGENT_NAME);
|
|
53326
|
+
if (!node.length)
|
|
53327
|
+
return '';
|
|
53328
|
+
return node.text().trim();
|
|
53329
|
+
}
|
|
53330
|
+
addCustomAgentInfo(eventPayload) {
|
|
53331
|
+
const customAgentId = this.getCustomAgentId();
|
|
53332
|
+
if (!customAgentId)
|
|
53333
|
+
return;
|
|
53334
|
+
eventPayload.customAgentId = customAgentId;
|
|
53335
|
+
const customAgentName = this.getCustomAgentName();
|
|
53336
|
+
if (customAgentName) {
|
|
53337
|
+
eventPayload.customAgentName = customAgentName;
|
|
53338
|
+
}
|
|
53339
|
+
}
|
|
53340
|
+
setFilters(candidateFilter) {
|
|
53341
|
+
if (!candidateFilter) {
|
|
53342
|
+
this.privacyFilters = null;
|
|
53343
|
+
return null;
|
|
53344
|
+
}
|
|
53345
|
+
try {
|
|
53346
|
+
this.privacyFilters = new RegExp(candidateFilter, 'gmi');
|
|
53347
|
+
}
|
|
53348
|
+
catch (e) {
|
|
53349
|
+
this.privacyFilters = null;
|
|
53350
|
+
this.api.log.error(e);
|
|
53351
|
+
}
|
|
53352
|
+
return this.privacyFilters;
|
|
53353
|
+
}
|
|
53354
|
+
applyPrivacyFilter(candidateValue, filters = null) {
|
|
53355
|
+
const filtersToUse = filters || this.privacyFilters;
|
|
53356
|
+
if (!filtersToUse || !this._.isRegExp(filtersToUse))
|
|
53357
|
+
return candidateValue;
|
|
53358
|
+
return candidateValue.replace(filtersToUse, 'redacted');
|
|
53359
|
+
}
|
|
53360
|
+
onSubmit(callback) {
|
|
53361
|
+
this.listeners.push(callback);
|
|
53362
|
+
}
|
|
53363
|
+
emit(eventType, eventPayload) {
|
|
53364
|
+
eventPayload.conversationId = this.getConversationId();
|
|
53365
|
+
this.addCustomAgentInfo(eventPayload);
|
|
53366
|
+
this._.each(this.listeners, (cb) => cb(eventType, eventPayload));
|
|
53367
|
+
}
|
|
53368
|
+
startStreamingEndTimer() {
|
|
53369
|
+
this.stopStreamingEndTimer();
|
|
53370
|
+
this.streamingEndTimer = setTimeout$1(() => {
|
|
53371
|
+
this.streamingEndTimer = null;
|
|
53372
|
+
this.onSubmissionEnd();
|
|
53373
|
+
}, STREAMING_END_SETTLE_MS);
|
|
53374
|
+
}
|
|
53375
|
+
stopStreamingEndTimer() {
|
|
53376
|
+
if (!this.streamingEndTimer)
|
|
53377
|
+
return;
|
|
53378
|
+
clearTimeout(this.streamingEndTimer);
|
|
53379
|
+
this.streamingEndTimer = null;
|
|
53380
|
+
}
|
|
53381
|
+
startSubmissionStartRetryTimer(attempt) {
|
|
53382
|
+
this.stopSubmissionStartRetryTimer();
|
|
53383
|
+
this.submissionStartRetryTimer = setTimeout$1(() => {
|
|
53384
|
+
this.submissionStartRetryTimer = null;
|
|
53385
|
+
this.onSubmissionStart(attempt);
|
|
53386
|
+
}, SUBMISSION_START_RETRY_MS);
|
|
53387
|
+
}
|
|
53388
|
+
stopSubmissionStartRetryTimer() {
|
|
53389
|
+
if (!this.submissionStartRetryTimer)
|
|
53390
|
+
return;
|
|
53391
|
+
clearTimeout(this.submissionStartRetryTimer);
|
|
53392
|
+
this.submissionStartRetryTimer = null;
|
|
53393
|
+
}
|
|
53394
|
+
suspend() {
|
|
53395
|
+
this.isActive = false;
|
|
53396
|
+
}
|
|
53397
|
+
resume() {
|
|
53398
|
+
this.isActive = true;
|
|
53399
|
+
}
|
|
53400
|
+
teardown() {
|
|
53401
|
+
this._.each(this.observers, (o) => o && o.disconnect && o.disconnect());
|
|
53402
|
+
this.observers = [];
|
|
53403
|
+
if (this.reactionClickHandler && this.root) {
|
|
53404
|
+
this.root.removeEventListener('click', this.reactionClickHandler, true);
|
|
53405
|
+
this.reactionClickHandler = null;
|
|
53406
|
+
}
|
|
53407
|
+
this.stopStreamingEndTimer();
|
|
53408
|
+
this.stopSubmissionStartRetryTimer();
|
|
53409
|
+
this.listeners = [];
|
|
53410
|
+
}
|
|
53411
|
+
}
|
|
53412
|
+
|
|
53413
|
+
/*
|
|
53414
|
+
* Conversation Analytics Plugin
|
|
53415
|
+
*
|
|
53416
|
+
* Independent plugin that tracks AI-agent conversations rendered in the DOM.
|
|
53417
|
+
*
|
|
53418
|
+
* The sibling built-in PromptAnalytics plugin owns the network + DOMPrompt AI-agents
|
|
53419
|
+
*/
|
|
53420
|
+
function ConversationAnalytics() {
|
|
53421
|
+
let pendo;
|
|
53422
|
+
let api;
|
|
53423
|
+
let _;
|
|
53424
|
+
let log;
|
|
53425
|
+
let conversations = [];
|
|
53426
|
+
let agentsCache = new Map();
|
|
53427
|
+
const CONFIG_NAME = 'aiAgents';
|
|
53428
|
+
return {
|
|
53429
|
+
name: 'ConversationAnalytics',
|
|
53430
|
+
initialize,
|
|
53431
|
+
teardown,
|
|
53432
|
+
onConfigLoaded,
|
|
53433
|
+
observeConversation,
|
|
53434
|
+
sendConversationEvent,
|
|
53435
|
+
reEvaluatePageRules
|
|
53436
|
+
};
|
|
53437
|
+
function initialize(pendoGlobal, PluginAPI) {
|
|
53438
|
+
var _a;
|
|
53439
|
+
pendo = pendoGlobal;
|
|
53440
|
+
api = PluginAPI;
|
|
53441
|
+
_ = pendo._;
|
|
53442
|
+
log = api.log;
|
|
53443
|
+
const { ConfigReader } = api;
|
|
53444
|
+
ConfigReader.addOption(CONFIG_NAME, [
|
|
53445
|
+
ConfigReader.sources.PENDO_CONFIG_SRC,
|
|
53446
|
+
ConfigReader.sources.SNIPPET_SRC
|
|
53447
|
+
]);
|
|
53448
|
+
onConfigLoaded(ConfigReader.get(CONFIG_NAME));
|
|
53449
|
+
// Listen for URL changes to re-evaluate page rules
|
|
53450
|
+
// Listening on guidesLoaded since normalizedUrl comes back from the guides playload
|
|
53451
|
+
(_a = api.Events.guidesLoaded) === null || _a === void 0 ? void 0 : _a.on(reEvaluatePageRules);
|
|
53452
|
+
}
|
|
53453
|
+
// Test if the current URL matches the page rule for an agent. The raw matcher lives in core
|
|
53454
|
+
// and is reached through PluginAPI (independent plugins cannot import it directly); it falls
|
|
53455
|
+
// back to the normalized URL internally when no URL is passed.
|
|
53456
|
+
function testPageRule(pageRule, agentId = 'unknown') {
|
|
53457
|
+
// Convert //*/agent pattern to work with full URLs
|
|
53458
|
+
let fixedPageRule = pageRule;
|
|
53459
|
+
if (Array.isArray(pageRule)) {
|
|
53460
|
+
const validRules = _.filter(pageRule, rule => rule && rule.trim() !== '');
|
|
53461
|
+
fixedPageRule = _.map(validRules, rule => rule.startsWith('//*/') ? rule.substring(1) : rule);
|
|
53462
|
+
}
|
|
53463
|
+
else if (typeof pageRule === 'string' && pageRule.startsWith('//*/')) {
|
|
53464
|
+
fixedPageRule = pageRule.substring(1);
|
|
53465
|
+
}
|
|
53466
|
+
return api.util.testPageRule(fixedPageRule, undefined, {
|
|
53467
|
+
logger: log,
|
|
53468
|
+
context: 'agent',
|
|
53469
|
+
id: agentId
|
|
53470
|
+
});
|
|
53471
|
+
}
|
|
53472
|
+
function reEvaluatePageRules() {
|
|
53473
|
+
_.each(conversations, conversation => {
|
|
53474
|
+
const agent = findAgentById(conversation.id);
|
|
53475
|
+
const shouldBeActive = agent && testPageRule(agent.pageRule, agent.id);
|
|
53476
|
+
if (shouldBeActive) {
|
|
53477
|
+
conversation.resume();
|
|
53478
|
+
}
|
|
53479
|
+
else {
|
|
53480
|
+
conversation.suspend();
|
|
53481
|
+
}
|
|
53482
|
+
});
|
|
53483
|
+
const currentlyActiveIds = _.map(_.filter(conversations, conversation => conversation.isActive), conversation => conversation.id);
|
|
53484
|
+
const allAgents = api.ConfigReader.get(CONFIG_NAME) || [];
|
|
53485
|
+
_.each(allAgents, aiAgent => {
|
|
53486
|
+
if (!isConversationAgent(aiAgent)) {
|
|
53487
|
+
return;
|
|
53488
|
+
}
|
|
53489
|
+
const isCurrentlyActive = _.contains(currentlyActiveIds, aiAgent.id);
|
|
53490
|
+
const shouldBeActive = testPageRule(aiAgent.pageRule, aiAgent.id);
|
|
53491
|
+
if (shouldBeActive && !isCurrentlyActive) {
|
|
53492
|
+
observeConversation(aiAgent);
|
|
53493
|
+
}
|
|
53494
|
+
});
|
|
53495
|
+
}
|
|
53496
|
+
function findAgentById(agentId) {
|
|
53497
|
+
return agentsCache.get(agentId);
|
|
53498
|
+
}
|
|
53499
|
+
// Only conversation agents belong to this plugin; the rest are owned
|
|
53500
|
+
// by the built-in PromptAnalytics plugin.
|
|
53501
|
+
function isConversationAgent(agent) {
|
|
53502
|
+
return DOMConversation.supportedPreset(agent.preset);
|
|
53503
|
+
}
|
|
53504
|
+
function onConfigLoaded(aiAgents = []) {
|
|
53505
|
+
if (!aiAgents || !_.isArray(aiAgents) || aiAgents.length === 0) {
|
|
53506
|
+
return;
|
|
53507
|
+
}
|
|
53508
|
+
agentsCache.clear();
|
|
53509
|
+
_.each(aiAgents, aiAgent => {
|
|
53510
|
+
if (!isConversationAgent(aiAgent)) {
|
|
53511
|
+
return;
|
|
53512
|
+
}
|
|
53513
|
+
agentsCache.set(aiAgent.id, aiAgent);
|
|
53514
|
+
if (testPageRule(aiAgent.pageRule, aiAgent.id)) {
|
|
53515
|
+
observeConversation(aiAgent);
|
|
53516
|
+
}
|
|
53517
|
+
});
|
|
53518
|
+
}
|
|
53519
|
+
function observeConversation(config) {
|
|
53520
|
+
const { id, cssSelectors, privacyFilters } = config;
|
|
53521
|
+
// Check if this agent is already being tracked
|
|
53522
|
+
const existing = _.find(conversations, conversation => conversation.id === id);
|
|
53523
|
+
if (existing) {
|
|
53524
|
+
return;
|
|
53525
|
+
}
|
|
53526
|
+
const missing = DOMConversation.validateConfig(cssSelectors);
|
|
53527
|
+
if (missing.length) {
|
|
53528
|
+
log.error('aiAgent config missing required selectors', { agentId: id, missing });
|
|
53529
|
+
return;
|
|
53530
|
+
}
|
|
53531
|
+
const conversation = new DOMConversation(pendo, api, id, privacyFilters, cssSelectors);
|
|
53532
|
+
conversation.onSubmit(sendConversationEvent);
|
|
53533
|
+
conversations.push(conversation);
|
|
53534
|
+
}
|
|
53535
|
+
function sendConversationEvent(eventType, eventPayload) {
|
|
53536
|
+
// Block event if the conversation is suspended
|
|
53537
|
+
const conversation = _.find(conversations, c => c.id === eventPayload.agentId);
|
|
53538
|
+
if (conversation && !conversation.isActive) {
|
|
53539
|
+
return;
|
|
53540
|
+
}
|
|
53541
|
+
eventPayload = _.extend({ agentType: 'conversation' }, eventPayload);
|
|
53542
|
+
api.analytics.collectEvent(eventType, eventPayload, undefined, 'agentic');
|
|
53543
|
+
}
|
|
53544
|
+
function teardown() {
|
|
53545
|
+
_.each(conversations, conversation => conversation.teardown());
|
|
53546
|
+
conversations = [];
|
|
53547
|
+
agentsCache.clear();
|
|
53548
|
+
}
|
|
53549
|
+
}
|
|
53550
|
+
|
|
53551
|
+
export { ConsoleCapture, ConversationAnalytics, Feedback, GuideMarkdown, NetworkCapture, PredictGuides, createReplayPlugin as Replay, TextCapture, VocPortal, loadAgent };
|