@coveo/quantic 3.42.0 → 3.43.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/force-app/main/default/labels/CustomLabels.labels-meta.xml +21 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/agentData.ts +248 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/fixture-agent.ts +77 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/fixture.ts +65 -59
- package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/pageObject.ts +158 -13
- package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/quanticGeneratedAnswer.e2e.ts +1 -5
- package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/quanticGeneratedAnswerAgent.e2e.ts +233 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/conversation.html +1 -1
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/__tests__/quanticGeneratedAnswerFollowUpInput.test.js +242 -10
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.css +2 -2
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.html +16 -1
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.js +47 -4
- package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/__tests__/quanticGeneratedAnswerStreamOfThought.test.js +95 -41
- package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/__tests__/stepsUtils.test.js +118 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/quanticGeneratedAnswerStreamOfThought.js +21 -35
- package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/stepsUtils.js +66 -0
- package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +15 -15
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/product-listing/facets/headless-product-listing-facet-options.d.ts +4 -39
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/search/facets/headless-search-facet-options.d.ts +4 -39
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/facets/facet-set/facet-set-selector.d.ts +3 -9
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/facets/facet-search-set/category/category-facet-search-state-selector.d.ts +1 -27
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/facets/facet-search-set/specific/specific-facet-search-state-selector.d.ts +1 -27
- package/force-app/main/default/staticresources/coveoheadless/headless.js +17 -17
- package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +17 -17
- package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +15 -15
- package/force-app/main/default/staticresources/dompurify/purify.min.js +2 -2
- package/package.json +5 -5
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from '../../../../../../playwright/utils/analyticsMode';
|
|
6
6
|
import {AnalyticsObject} from '../../../../../../playwright/page-object/analytics';
|
|
7
7
|
import {isRgaEvaluationRequest} from '../../../../../../playwright/utils/requests';
|
|
8
|
+
import type {AgentMessage} from './agentData';
|
|
8
9
|
|
|
9
10
|
const minimumCitationTooltipDisplayDurationMs = 1500;
|
|
10
11
|
const removeUnknownFields = (object: Record<string, unknown>) => {
|
|
@@ -24,32 +25,45 @@ const facetElementsSelectors = {
|
|
|
24
25
|
},
|
|
25
26
|
};
|
|
26
27
|
|
|
28
|
+
export interface GeneratedAnswerObjectConfig {
|
|
29
|
+
answerApiEnabled?: boolean;
|
|
30
|
+
headAnswerRequestRegex?: RegExp;
|
|
31
|
+
agentFollowUpRequestRegex?: RegExp;
|
|
32
|
+
isAgent?: boolean;
|
|
33
|
+
withFacets?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
export class GeneratedAnswerObject {
|
|
28
37
|
private analyticsMode: AnalyticsMode;
|
|
38
|
+
private answerApiEnabled: boolean;
|
|
39
|
+
private headAnswerRequestRegex?: RegExp;
|
|
40
|
+
private agentFollowUpRequestRegex?: RegExp;
|
|
41
|
+
private withFacets: boolean;
|
|
42
|
+
private isAgent: boolean;
|
|
29
43
|
|
|
30
44
|
constructor(
|
|
31
45
|
private page: Page,
|
|
32
46
|
private streamId: string,
|
|
33
47
|
private analytics: AnalyticsObject,
|
|
34
|
-
|
|
35
|
-
private generateRequestRegex: RegExp,
|
|
36
|
-
private withFacets: boolean
|
|
48
|
+
config: GeneratedAnswerObjectConfig = {}
|
|
37
49
|
) {
|
|
38
50
|
this.page = page;
|
|
39
51
|
this.streamId = streamId;
|
|
40
52
|
this.analytics = analytics;
|
|
41
53
|
this.analyticsMode = this.analytics.analyticsMode;
|
|
42
|
-
this.answerApiEnabled = answerApiEnabled;
|
|
43
|
-
this.
|
|
44
|
-
this.
|
|
54
|
+
this.answerApiEnabled = config.answerApiEnabled ?? false;
|
|
55
|
+
this.headAnswerRequestRegex = config.headAnswerRequestRegex;
|
|
56
|
+
this.agentFollowUpRequestRegex = config.agentFollowUpRequestRegex;
|
|
57
|
+
this.withFacets = config.withFacets ?? false;
|
|
58
|
+
this.isAgent = config.isAgent ?? false;
|
|
45
59
|
}
|
|
46
60
|
|
|
47
61
|
get likeButton(): Locator {
|
|
48
|
-
return this.page.getByRole('button', {name: 'This answer was helpful'});
|
|
62
|
+
return this.page.getByRole('button', {name: this.isAgent ? 'Like' : 'This answer was helpful', exact: true});
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
get dislikeButton(): Locator {
|
|
52
|
-
return this.page.getByRole('button', {name: 'This answer was not helpful'});
|
|
66
|
+
return this.page.getByRole('button', {name: this.isAgent ? 'Dislike' : 'This answer was not helpful', exact: true});
|
|
53
67
|
}
|
|
54
68
|
|
|
55
69
|
get copyToClipboardButton(): Locator {
|
|
@@ -118,7 +132,7 @@ export class GeneratedAnswerObject {
|
|
|
118
132
|
|
|
119
133
|
get citationLink(): Locator {
|
|
120
134
|
return this.page
|
|
121
|
-
.getByTestId('generated-answer__citations')
|
|
135
|
+
.getByTestId(this.isAgent ? 'generated-answer-body__citations' : 'generated-answer__citations')
|
|
122
136
|
.locator('.citation__link');
|
|
123
137
|
}
|
|
124
138
|
|
|
@@ -126,6 +140,22 @@ export class GeneratedAnswerObject {
|
|
|
126
140
|
return this.page.getByTestId('generated-answer__answer-toggle');
|
|
127
141
|
}
|
|
128
142
|
|
|
143
|
+
get showPreviousAnswersButton(): Locator {
|
|
144
|
+
return this.page.getByTestId('show-previous-button');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
get threadItems(): Locator {
|
|
148
|
+
return this.page.locator('c-quantic-thread-item');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
get threadItemTitleButton(): Locator {
|
|
152
|
+
return this.threadItems.getByTestId('thread-item-title-button');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get generatedAnswerBody(): Locator {
|
|
156
|
+
return this.page.locator('c-quantic-generated-answer-body');
|
|
157
|
+
}
|
|
158
|
+
|
|
129
159
|
async hoverOverCitation(index: number): Promise<void> {
|
|
130
160
|
// waiting 500ms to allow the component to render completely, cause any re-rendering abort the hover action.
|
|
131
161
|
await this.page.waitForTimeout(500);
|
|
@@ -166,10 +196,18 @@ export class GeneratedAnswerObject {
|
|
|
166
196
|
await this.likeButton.click();
|
|
167
197
|
}
|
|
168
198
|
|
|
199
|
+
async clickNthLikeButton(answerIndex: number): Promise<void> {
|
|
200
|
+
await this.likeButton.nth(answerIndex).click();
|
|
201
|
+
}
|
|
202
|
+
|
|
169
203
|
async clickDislikeButton(): Promise<void> {
|
|
170
204
|
await this.dislikeButton.click();
|
|
171
205
|
}
|
|
172
206
|
|
|
207
|
+
async clickNthDislikeButton(answerIndex: number): Promise<void> {
|
|
208
|
+
await this.dislikeButton.nth(answerIndex).click();
|
|
209
|
+
}
|
|
210
|
+
|
|
173
211
|
async clickCopyToClipboardButton(): Promise<void> {
|
|
174
212
|
await this.copyToClipboardButton.click();
|
|
175
213
|
}
|
|
@@ -178,8 +216,26 @@ export class GeneratedAnswerObject {
|
|
|
178
216
|
await this.toggleButton.click();
|
|
179
217
|
}
|
|
180
218
|
|
|
181
|
-
async
|
|
182
|
-
|
|
219
|
+
async clickShowPreviousAnswersButton(): Promise<void> {
|
|
220
|
+
await this.showPreviousAnswersButton.click();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async clickNthThreadItem(index: number): Promise<void> {
|
|
224
|
+
await this.threadItemTitleButton.nth(index).click();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async waitForHeadAnswerRequest(): Promise<Request> {
|
|
228
|
+
if (!this.headAnswerRequestRegex) {
|
|
229
|
+
throw new Error('headAnswerRequestRegex is not configured');
|
|
230
|
+
}
|
|
231
|
+
return this.page.waitForRequest(this.headAnswerRequestRegex);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async waitForAgentFollowUpRequest(): Promise<Request> {
|
|
235
|
+
if (!this.agentFollowUpRequestRegex) {
|
|
236
|
+
throw new Error('agentFollowUpRequestRegex is not configured');
|
|
237
|
+
}
|
|
238
|
+
return this.page.waitForRequest(this.agentFollowUpRequestRegex);
|
|
183
239
|
}
|
|
184
240
|
|
|
185
241
|
async waitForStreamEndAnalytics(): Promise<Request | boolean> {
|
|
@@ -200,6 +256,12 @@ export class GeneratedAnswerObject {
|
|
|
200
256
|
}
|
|
201
257
|
|
|
202
258
|
async waitForLikeGeneratedAnswerAnalytics(): Promise<Request> {
|
|
259
|
+
return this.waitForLikeGeneratedAnswerAnalyticsForId(this.streamId);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async waitForLikeGeneratedAnswerAnalyticsForId(
|
|
263
|
+
answerId: string
|
|
264
|
+
): Promise<Request> {
|
|
203
265
|
if (this.analyticsMode === AnalyticsModeEnum.legacy) {
|
|
204
266
|
return this.analytics.waitForCustomUaAnalytics(
|
|
205
267
|
{
|
|
@@ -207,7 +269,7 @@ export class GeneratedAnswerObject {
|
|
|
207
269
|
eventValue: 'likeGeneratedAnswer',
|
|
208
270
|
},
|
|
209
271
|
(event) =>
|
|
210
|
-
event?.customData?.generativeQuestionAnsweringId ===
|
|
272
|
+
event?.customData?.generativeQuestionAnsweringId === answerId
|
|
211
273
|
);
|
|
212
274
|
}
|
|
213
275
|
return this.analytics.waitForEventProtocolAnalytics(
|
|
@@ -217,6 +279,12 @@ export class GeneratedAnswerObject {
|
|
|
217
279
|
}
|
|
218
280
|
|
|
219
281
|
async waitForDislikeGeneratedAnswerAnalytics(): Promise<Request> {
|
|
282
|
+
return this.waitForDislikeGeneratedAnswerAnalyticsForId(this.streamId);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async waitForDislikeGeneratedAnswerAnalyticsForId(
|
|
286
|
+
answerId: string
|
|
287
|
+
): Promise<Request> {
|
|
220
288
|
if (this.analyticsMode === AnalyticsModeEnum.legacy) {
|
|
221
289
|
return this.analytics.waitForCustomUaAnalytics(
|
|
222
290
|
{
|
|
@@ -224,7 +292,7 @@ export class GeneratedAnswerObject {
|
|
|
224
292
|
eventValue: 'dislikeGeneratedAnswer',
|
|
225
293
|
},
|
|
226
294
|
(event) =>
|
|
227
|
-
event?.customData?.generativeQuestionAnsweringId ===
|
|
295
|
+
event?.customData?.generativeQuestionAnsweringId === answerId
|
|
228
296
|
);
|
|
229
297
|
}
|
|
230
298
|
return this.analytics.waitForEventProtocolAnalytics(
|
|
@@ -446,6 +514,83 @@ export class GeneratedAnswerObject {
|
|
|
446
514
|
});
|
|
447
515
|
}
|
|
448
516
|
|
|
517
|
+
async mockAgentAnswerResponse(
|
|
518
|
+
body: Array<AgentMessage>
|
|
519
|
+
) {
|
|
520
|
+
await this.page.route('**/agents/*/answer', (route) => {
|
|
521
|
+
let bodyText = '';
|
|
522
|
+
body.forEach((data) => {
|
|
523
|
+
bodyText += `data:${JSON.stringify(data)} \n\n`;
|
|
524
|
+
});
|
|
525
|
+
route.fulfill({
|
|
526
|
+
status: 200,
|
|
527
|
+
body: bodyText,
|
|
528
|
+
headers: {'content-type': 'text/event-stream'},
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async mockAgentFollowUpResponse(
|
|
534
|
+
bodies: Array<Array<AgentMessage>>
|
|
535
|
+
) {
|
|
536
|
+
let callCount = 0;
|
|
537
|
+
await this.page.route('**/agents/*/follow-up', (route) => {
|
|
538
|
+
// After all provided response bodies are used, keep returning the last one
|
|
539
|
+
// so any additional intercepted follow-up requests still receive a mocked stream.
|
|
540
|
+
const streams = bodies[Math.min(callCount, bodies.length - 1)];
|
|
541
|
+
callCount++;
|
|
542
|
+
|
|
543
|
+
let bodyText = '';
|
|
544
|
+
streams.forEach((data) => {
|
|
545
|
+
bodyText += `data:${JSON.stringify(data)} \n\n`;
|
|
546
|
+
});
|
|
547
|
+
route.fulfill({
|
|
548
|
+
status: 200,
|
|
549
|
+
body: bodyText,
|
|
550
|
+
headers: {'content-type': 'text/event-stream'},
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
get followUpInput(): Locator {
|
|
556
|
+
return this.page.locator(
|
|
557
|
+
'c-quantic-generated-answer-follow-up-input textarea'
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
get followUpSubmitButton(): Locator {
|
|
562
|
+
return this.page
|
|
563
|
+
.locator('c-quantic-generated-answer-follow-up-input')
|
|
564
|
+
.getByRole('button', {name: 'Submit follow-up'});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async typeFollowUpQuestion(question: string): Promise<void> {
|
|
568
|
+
await this.followUpInput.fill(question);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async submitFollowUp(): Promise<void> {
|
|
572
|
+
await this.followUpSubmitButton.click();
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
async waitForFollowUpStreamEndAnalytics(
|
|
576
|
+
followUpAnswerId: string
|
|
577
|
+
): Promise<Request | boolean> {
|
|
578
|
+
if (this.analyticsMode === AnalyticsModeEnum.legacy) {
|
|
579
|
+
return this.analytics.waitForCustomUaAnalytics(
|
|
580
|
+
{
|
|
581
|
+
eventType: 'generatedAnswer',
|
|
582
|
+
eventValue: 'generatedAnswerStreamEnd',
|
|
583
|
+
},
|
|
584
|
+
(event) =>
|
|
585
|
+
event?.customData?.generativeQuestionAnsweringId === followUpAnswerId
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
return this.analytics.waitForEventProtocolAnalytics(
|
|
589
|
+
'Rga.AnswerReceived',
|
|
590
|
+
(event) => event.answerGenerated === true
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
449
594
|
streamEndAnalyticRequestPromise!: Promise<boolean | Request>;
|
|
450
595
|
generateRequestPromise!: Promise<Request>;
|
|
451
596
|
}
|
|
@@ -14,10 +14,6 @@ useCaseTestCases.forEach((useCase) => {
|
|
|
14
14
|
let test = fixtures[useCase.value];
|
|
15
15
|
|
|
16
16
|
test.describe(`quantic generated answer ${useCase.label}`, () => {
|
|
17
|
-
test.use({
|
|
18
|
-
genQaData,
|
|
19
|
-
});
|
|
20
|
-
|
|
21
17
|
analyticsModeTest.forEach((analytics) => {
|
|
22
18
|
test.describe(analytics.label, () => {
|
|
23
19
|
test.use({
|
|
@@ -309,7 +305,7 @@ useCaseTestCases.forEach((useCase) => {
|
|
|
309
305
|
await generatedAnswer.streamEndAnalyticRequestPromise;
|
|
310
306
|
|
|
311
307
|
const generateRequestPromise =
|
|
312
|
-
generatedAnswer.
|
|
308
|
+
generatedAnswer.waitForHeadAnswerRequest();
|
|
313
309
|
|
|
314
310
|
const streamEndAnalyticRequestPromise =
|
|
315
311
|
generatedAnswer.waitForStreamEndAnalytics();
|
package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/quanticGeneratedAnswerAgent.e2e.ts
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import {testAgent, expect} from './fixture-agent';
|
|
2
|
+
import agentData from './agentData';
|
|
3
|
+
import {
|
|
4
|
+
analyticsModeTest,
|
|
5
|
+
AnalyticsModeEnum,
|
|
6
|
+
} from '../../../../../../playwright/utils/analyticsMode';
|
|
7
|
+
|
|
8
|
+
const exampleAgentId = '5fd0b5ea-d368-488e-bdeb-f6221ec0fb98';
|
|
9
|
+
|
|
10
|
+
analyticsModeTest.forEach((analytics) => {
|
|
11
|
+
testAgent.describe(`quantic generated answer - agent - ${analytics.label}`, () => {
|
|
12
|
+
testAgent.use({
|
|
13
|
+
analyticsMode: analytics.mode,
|
|
14
|
+
options: {agentId: exampleAgentId},
|
|
15
|
+
permissions: ['clipboard-read', 'clipboard-write'],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (analytics.mode !== AnalyticsModeEnum.legacy) {
|
|
19
|
+
testAgent.fixme(true, 'Agent next analytics mode not yet implemented');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
testAgent(
|
|
24
|
+
'should handle multiple answer generation, feedback, clipboard, citation, and follow-up interactions',
|
|
25
|
+
async ({generatedAnswer, page}) => {
|
|
26
|
+
await testAgent.step(
|
|
27
|
+
'should send a stream end analytics event when the first answer is generated',
|
|
28
|
+
async () => {
|
|
29
|
+
await generatedAnswer.streamEndAnalyticRequestPromise;
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
await testAgent.step(
|
|
34
|
+
'should send like analytics event when liking the first answer',
|
|
35
|
+
async () => {
|
|
36
|
+
const likeAnalyticRequestPromise =
|
|
37
|
+
generatedAnswer.waitForLikeGeneratedAnswerAnalytics();
|
|
38
|
+
await generatedAnswer.clickLikeButton();
|
|
39
|
+
await likeAnalyticRequestPromise;
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
await testAgent.step(
|
|
44
|
+
'should send dislike analytics event when disliking the first answer',
|
|
45
|
+
async () => {
|
|
46
|
+
const dislikeAnalyticRequestPromise =
|
|
47
|
+
generatedAnswer.waitForDislikeGeneratedAnswerAnalytics();
|
|
48
|
+
await generatedAnswer.clickDislikeButton();
|
|
49
|
+
await dislikeAnalyticRequestPromise;
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
await testAgent.step(
|
|
54
|
+
'should send a copy to clipboard analytics event and update the clipboard with the first answer content',
|
|
55
|
+
async () => {
|
|
56
|
+
const analyticRequestPromise =
|
|
57
|
+
generatedAnswer.waitForCopyToClipboardAnalytics();
|
|
58
|
+
await generatedAnswer.clickCopyToClipboardButton();
|
|
59
|
+
await analyticRequestPromise;
|
|
60
|
+
|
|
61
|
+
const clipboardContent = await page.evaluate(() =>
|
|
62
|
+
navigator.clipboard.readText()
|
|
63
|
+
);
|
|
64
|
+
expect(clipboardContent).toContain('Testing in Coveo');
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
await testAgent.step(
|
|
69
|
+
'should log citation hover analytics when hovering over a citation from the first answer',
|
|
70
|
+
async () => {
|
|
71
|
+
const citationIndex = 0;
|
|
72
|
+
const {id, permanentid} = agentData.citations[citationIndex];
|
|
73
|
+
const citationHoverAnalyticRequestPromise =
|
|
74
|
+
generatedAnswer.waitForSourceHoverAnalytics({
|
|
75
|
+
citationId: id,
|
|
76
|
+
permanentId: permanentid,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await generatedAnswer.hoverOverCitation(citationIndex);
|
|
80
|
+
await citationHoverAnalyticRequestPromise;
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
await testAgent.step(
|
|
85
|
+
'should log citation click analytics when clicking on a citation from the first answer',
|
|
86
|
+
async () => {
|
|
87
|
+
const citationIndex = 0;
|
|
88
|
+
const {id, title, source, uri, clickUri, permanentid} =
|
|
89
|
+
agentData.citations[citationIndex];
|
|
90
|
+
const citationClickAnalyticRequestPromise =
|
|
91
|
+
generatedAnswer.waitForCitationClickAnalytics({
|
|
92
|
+
documentTitle: title,
|
|
93
|
+
sourceName: source,
|
|
94
|
+
documentPosition: citationIndex + 1,
|
|
95
|
+
documentUri: uri,
|
|
96
|
+
documentUrl: clickUri,
|
|
97
|
+
citationId: id,
|
|
98
|
+
contentIDKey: 'permanentid',
|
|
99
|
+
contentIDValue: permanentid,
|
|
100
|
+
});
|
|
101
|
+
await generatedAnswer.clickOnCitation(citationIndex);
|
|
102
|
+
await citationClickAnalyticRequestPromise;
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
await testAgent.step(
|
|
107
|
+
'FIRST FOLLOW-UP: should send the follow-up request with the correct conversationId and conversationToken',
|
|
108
|
+
async () => {
|
|
109
|
+
const followUpRequestPromise =
|
|
110
|
+
generatedAnswer.waitForAgentFollowUpRequest();
|
|
111
|
+
|
|
112
|
+
await generatedAnswer.typeFollowUpQuestion('follow-up question');
|
|
113
|
+
await generatedAnswer.submitFollowUp();
|
|
114
|
+
|
|
115
|
+
const followUpRequest = await followUpRequestPromise;
|
|
116
|
+
const followUpRequestBody = followUpRequest.postDataJSON();
|
|
117
|
+
expect(followUpRequestBody.conversationId).toBe(
|
|
118
|
+
agentData.conversationId
|
|
119
|
+
);
|
|
120
|
+
expect(followUpRequestBody.conversationToken).toBe(
|
|
121
|
+
agentData.conversationToken
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
await testAgent.step(
|
|
127
|
+
'should send stream end analytics with the right answerId for the follow-up',
|
|
128
|
+
async () => {
|
|
129
|
+
const followUpStreamEndPromise =
|
|
130
|
+
generatedAnswer.waitForFollowUpStreamEndAnalytics(
|
|
131
|
+
agentData.answerId2
|
|
132
|
+
);
|
|
133
|
+
await followUpStreamEndPromise;
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
await testAgent.step(
|
|
138
|
+
'should send like analytics with the follow-up answerId when liking the first follow-up answer',
|
|
139
|
+
async () => {
|
|
140
|
+
const likeAnalyticRequestPromise =
|
|
141
|
+
generatedAnswer.waitForLikeGeneratedAnswerAnalyticsForId(
|
|
142
|
+
agentData.answerId2
|
|
143
|
+
);
|
|
144
|
+
// Should be the first like button, since the previous answers are collapsed and the follow-up answer is the only one visible.
|
|
145
|
+
await generatedAnswer.clickNthLikeButton(0);
|
|
146
|
+
await likeAnalyticRequestPromise;
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
await testAgent.step(
|
|
151
|
+
'SECOND FOLLOW-UP: should send the second follow-up request with the correct conversationId and conversationToken',
|
|
152
|
+
async () => {
|
|
153
|
+
const followUpRequestPromise =
|
|
154
|
+
generatedAnswer.waitForAgentFollowUpRequest();
|
|
155
|
+
|
|
156
|
+
await generatedAnswer.typeFollowUpQuestion(
|
|
157
|
+
'second follow-up question'
|
|
158
|
+
);
|
|
159
|
+
await generatedAnswer.submitFollowUp();
|
|
160
|
+
|
|
161
|
+
const followUpRequest = await followUpRequestPromise;
|
|
162
|
+
const followUpRequestBody = followUpRequest.postDataJSON();
|
|
163
|
+
expect(followUpRequestBody.conversationId).toBe(
|
|
164
|
+
agentData.conversationId
|
|
165
|
+
);
|
|
166
|
+
expect(followUpRequestBody.conversationToken).toBe(
|
|
167
|
+
agentData.conversationToken
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
await testAgent.step(
|
|
173
|
+
'should send stream end analytics with the right answerId for the second follow-up',
|
|
174
|
+
async () => {
|
|
175
|
+
const followUpStreamEndPromise =
|
|
176
|
+
generatedAnswer.waitForFollowUpStreamEndAnalytics(
|
|
177
|
+
agentData.answerId3
|
|
178
|
+
);
|
|
179
|
+
await followUpStreamEndPromise;
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
await testAgent.step(
|
|
184
|
+
'should send dislike analytics with the right answerId for the second follow-up when disliking the second follow-up answer',
|
|
185
|
+
async () => {
|
|
186
|
+
const dislikeAnalyticRequestPromise =
|
|
187
|
+
generatedAnswer.waitForDislikeGeneratedAnswerAnalyticsForId(
|
|
188
|
+
agentData.answerId3
|
|
189
|
+
);
|
|
190
|
+
// Should be the first dislike button, since the previous answers are collapsed and the second follow-up answer is the only one visible.
|
|
191
|
+
await generatedAnswer.clickNthDislikeButton(0);
|
|
192
|
+
await dislikeAnalyticRequestPromise;
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
await testAgent.step(
|
|
197
|
+
'should expand previous answers when clicking on the "Show previous answers" button',
|
|
198
|
+
async () => {
|
|
199
|
+
await generatedAnswer.clickShowPreviousAnswersButton();
|
|
200
|
+
await expect(generatedAnswer.threadItems).toHaveCount(3);
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
await testAgent.step(
|
|
205
|
+
'should expand the first answer when clicking on the toggle button of the first answer',
|
|
206
|
+
async () => {
|
|
207
|
+
await generatedAnswer.clickNthThreadItem(0);
|
|
208
|
+
await expect(generatedAnswer.generatedAnswerBody.nth(0)).toBeVisible();
|
|
209
|
+
await expect(generatedAnswer.generatedAnswerBody.nth(1)).not.toBeVisible();
|
|
210
|
+
await expect(generatedAnswer.generatedAnswerBody.nth(2)).toBeVisible();
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
await testAgent.step(
|
|
215
|
+
'should allow to like the first answer, sending the correct analytics and not affecting the other answers',
|
|
216
|
+
async () => {
|
|
217
|
+
const likeAnalyticRequestPromise =
|
|
218
|
+
generatedAnswer.waitForLikeGeneratedAnswerAnalyticsForId(
|
|
219
|
+
agentData.answerId1
|
|
220
|
+
);
|
|
221
|
+
await generatedAnswer.clickNthLikeButton(0);
|
|
222
|
+
await likeAnalyticRequestPromise;
|
|
223
|
+
await expect(generatedAnswer.likeButton.nth(0)).toHaveAttribute('aria-pressed', 'true');
|
|
224
|
+
await expect(generatedAnswer.dislikeButton.nth(0)).toHaveAttribute('aria-pressed', 'false');
|
|
225
|
+
// Only two answers are visible, so only 2 like/dislike buttons visible.
|
|
226
|
+
await expect(generatedAnswer.dislikeButton.nth(1)).toHaveAttribute('aria-pressed', 'true');
|
|
227
|
+
await expect(generatedAnswer.likeButton.nth(1)).toHaveAttribute('aria-pressed', 'false');
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
>
|
|
44
44
|
</c-quantic-generated-answer-thread>
|
|
45
45
|
</div>
|
|
46
|
-
<div class="slds-p-horizontal_large slds-p-vertical_medium">
|
|
46
|
+
<div class="slds-border_top slds-p-horizontal_large slds-p-vertical_medium">
|
|
47
47
|
<c-quantic-generated-answer-follow-up-input
|
|
48
48
|
onquantic__submitfollowup={handleFollowUpSubmit}
|
|
49
49
|
submit-button-disabled={isAnswerGenerationOngoing}
|