@coveo/quantic 3.42.1 → 3.43.1

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.
Files changed (32) hide show
  1. package/force-app/main/default/labels/CustomLabels.labels-meta.xml +21 -0
  2. package/force-app/main/default/lwc/quanticGeneratedAnswer/__tests__/quanticGeneratedAnswer.test.js +145 -60
  3. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/agentData.ts +248 -0
  4. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/fixture-agent.ts +77 -0
  5. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/fixture.ts +74 -65
  6. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/pageObject.ts +157 -15
  7. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/quanticGeneratedAnswer.e2e.ts +1 -5
  8. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/quanticGeneratedAnswerAgent.e2e.ts +233 -0
  9. package/force-app/main/default/lwc/quanticGeneratedAnswer/quanticGeneratedAnswer.js +12 -108
  10. package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/conversation.html +1 -1
  11. package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.html +8 -47
  12. package/force-app/main/default/lwc/quanticGeneratedAnswerContent/__tests__/quanticGeneratedAnswerContent.test.js +27 -0
  13. package/force-app/main/default/lwc/quanticGeneratedAnswerContent/quanticGeneratedAnswerContent.js +8 -1
  14. package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/__tests__/quanticGeneratedAnswerFollowUpInput.test.js +242 -10
  15. package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.css +2 -2
  16. package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.html +16 -1
  17. package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.js +47 -4
  18. package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/__tests__/quanticGeneratedAnswerStreamOfThought.test.js +95 -41
  19. package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/__tests__/stepsUtils.test.js +118 -0
  20. package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/quanticGeneratedAnswerStreamOfThought.js +21 -35
  21. package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/stepsUtils.js +66 -0
  22. package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +15 -15
  23. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/product-listing/facets/headless-product-listing-facet-options.d.ts +4 -39
  24. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/search/facets/headless-search-facet-options.d.ts +4 -39
  25. package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/facets/facet-set/facet-set-selector.d.ts +3 -9
  26. package/force-app/main/default/staticresources/coveoheadless/definitions/features/facets/facet-search-set/category/category-facet-search-state-selector.d.ts +1 -27
  27. package/force-app/main/default/staticresources/coveoheadless/definitions/features/facets/facet-search-set/specific/specific-facet-search-state-selector.d.ts +1 -27
  28. package/force-app/main/default/staticresources/coveoheadless/headless.js +17 -17
  29. package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +16 -16
  30. package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +14 -14
  31. package/force-app/main/default/staticresources/dompurify/purify.min.js +2 -2
  32. package/package.json +7 -6
@@ -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
+ });
@@ -1,23 +1,14 @@
1
1
  import answerGenerated from '@salesforce/label/c.quantic_AnswerGenerated';
2
2
  import couldNotGenerateAnAnswer from '@salesforce/label/c.quantic_CouldNotGenerateAnAnswer';
3
- import feedback from '@salesforce/label/c.quantic_Feedback';
4
3
  import feedbackHelpUsImprove from '@salesforce/label/c.quantic_FeedbackHelpUsImprove';
5
4
  import generatedAnswerForYou from '@salesforce/label/c.quantic_GeneratedAnswerForYou';
6
5
  import generatedAnswerIsHidden from '@salesforce/label/c.quantic_GeneratedAnswerIsHidden';
7
6
  import showLess from '@salesforce/label/c.quantic_GeneratedAnswerShowLess';
8
7
  import showMore from '@salesforce/label/c.quantic_GeneratedAnswerShowMore';
9
8
  import generatingAnswer from '@salesforce/label/c.quantic_GeneratingAnswer';
10
- import harmful from '@salesforce/label/c.quantic_Harmful';
11
- import inaccurate from '@salesforce/label/c.quantic_Inaccurate';
12
- import irrelevant from '@salesforce/label/c.quantic_Irrelevant';
13
9
  import loading from '@salesforce/label/c.quantic_Loading';
14
- import other from '@salesforce/label/c.quantic_Other';
15
- import outOfDate from '@salesforce/label/c.quantic_OutOfDate';
16
10
  import rgaDisclaimer from '@salesforce/label/c.quantic_RGADisclaimer';
17
- import thisAnswerWasHelpful from '@salesforce/label/c.quantic_ThisAnswerWasHelpful';
18
- import thisAnswerWasNotHelpful from '@salesforce/label/c.quantic_ThisAnswerWasNotHelpful';
19
11
  import tryAgain from '@salesforce/label/c.quantic_TryAgain';
20
- import whyGeneratedAnswerWasNotHelpful from '@salesforce/label/c.quantic_WhyGeneratedAnswerWasNotHelpful';
21
12
  import noGeneratedAnswer from '@salesforce/label/c.quantic_NoGeneratedAnswer';
22
13
  import FeedbackModalQna from 'c/quanticFeedbackModalQna';
23
14
  import {
@@ -25,7 +16,7 @@ import {
25
16
  initializeWithHeadless,
26
17
  getHeadlessBundle,
27
18
  } from 'c/quanticHeadlessLoader';
28
- import {AriaLiveRegion, I18nUtils, getAbsoluteHeight} from 'c/quanticUtils';
19
+ import {AriaLiveRegion, I18nUtils} from 'c/quanticUtils';
29
20
  import {LightningElement, api} from 'lwc';
30
21
  // @ts-ignore
31
22
  import errorTemplate from './templates/errorTemplate.html';
@@ -42,15 +33,9 @@ import conversationTemplate from './templates/conversation.html';
42
33
  /** @typedef {import("coveo").GeneratedAnswer} GeneratedAnswer */
43
34
  /** @typedef {import("coveo").GeneratedAnswerWithFollowUps} GeneratedAnswerWithFollowUps */
44
35
  /** @typedef {import("coveo").GeneratedAnswerState} GeneratedAnswerState */
45
- /** @typedef {import("coveo").GeneratedAnswerCitation} GeneratedAnswerCitation */
46
- /** @typedef { 'neutral' | 'liked' | 'disliked'} FeedbackState */
47
36
  /** @typedef {import("coveo").SearchStatus} SearchStatus */
48
37
  /** @typedef {import("coveo").SearchStatusState} SearchStatusState */
49
38
 
50
- const FEEDBACK_LIKED_STATE = 'liked';
51
- const FEEDBACK_DISLIKED_STATE = 'disliked';
52
- const FEEDBACK_NEUTRAL_STATE = 'neutral';
53
-
54
39
  const DEFAULT_COLLAPSED_HEIGHT = 250;
55
40
  const MAX_VALID_COLLAPSED_HEIGHT = 500;
56
41
  const MIN_VALID_COLLAPSED_HEIGHT = 150;
@@ -143,18 +128,9 @@ export default class QuanticGeneratedAnswer extends LightningElement {
143
128
 
144
129
  labels = {
145
130
  generatedAnswerForYou,
146
- thisAnswerWasNotHelpful,
147
- thisAnswerWasHelpful,
148
131
  tryAgain,
149
132
  couldNotGenerateAnAnswer,
150
- other,
151
- harmful,
152
- irrelevant,
153
- inaccurate,
154
- outOfDate,
155
133
  feedbackHelpUsImprove,
156
- feedback,
157
- whyGeneratedAnswerWasNotHelpful,
158
134
  generatingAnswer,
159
135
  generatedAnswerIsHidden,
160
136
  answerGenerated,
@@ -169,8 +145,6 @@ export default class QuanticGeneratedAnswer extends LightningElement {
169
145
  generatedAnswer;
170
146
  /** @type {GeneratedAnswerState} */
171
147
  state;
172
- /** @type {FeedbackState} */
173
- feedbackState = 'neutral';
174
148
  /** @type {SearchStatus} */
175
149
  searchStatus;
176
150
  /** @type {SearchStatusState} */
@@ -185,6 +159,8 @@ export default class QuanticGeneratedAnswer extends LightningElement {
185
159
  _exceedsMaximumHeight = false;
186
160
  /** @type {number} */
187
161
  _maxCollapsedHeight = DEFAULT_COLLAPSED_HEIGHT;
162
+ /** @type {number} */
163
+ generatedAnswerHeight = 0;
188
164
 
189
165
  connectedCallback() {
190
166
  registerComponentForInit(this, this.engineId);
@@ -266,7 +242,6 @@ export default class QuanticGeneratedAnswer extends LightningElement {
266
242
 
267
243
  updateState() {
268
244
  this.state = this.generatedAnswer.state;
269
- this.updateFeedbackState();
270
245
  this.ariaLiveMessage.dispatchMessage(this.getGeneratedAnswerStatus());
271
246
 
272
247
  if (this.isCollapsibleEnabled) {
@@ -278,8 +253,8 @@ export default class QuanticGeneratedAnswer extends LightningElement {
278
253
  // If we are still streaming add a little extra height to the answer element to account for the next answer chunk.
279
254
  // This helps a lot with the jankyness of the answer fading out when the chunk is close but not yet over the max height.
280
255
  const answerElementHeight = this.isStreaming
281
- ? this.generatedAnswerElementHeight + 50
282
- : this.generatedAnswerElementHeight;
256
+ ? this.generatedAnswerHeight + 50
257
+ : this.generatedAnswerHeight;
283
258
 
284
259
  return answerElementHeight > this.maxCollapsedHeight;
285
260
  }
@@ -309,25 +284,6 @@ export default class QuanticGeneratedAnswer extends LightningElement {
309
284
  this.searchStatusState = this.searchStatus.state;
310
285
  }
311
286
 
312
- updateFeedbackState() {
313
- if (this.state?.liked) {
314
- this.feedbackState = FEEDBACK_LIKED_STATE;
315
- } else if (this.state?.disliked) {
316
- this.feedbackState = FEEDBACK_DISLIKED_STATE;
317
- } else {
318
- this.feedbackState = FEEDBACK_NEUTRAL_STATE;
319
- }
320
- }
321
-
322
- /**
323
- * handles hovering over a citation.
324
- * @param {string} id
325
- * @param {number} citationHoverTimeMs
326
- */
327
- logCitationHover = (id, citationHoverTimeMs) => {
328
- this.generatedAnswer.logCitationHover(id, citationHoverTimeMs);
329
- };
330
-
331
287
  /**
332
288
  * handles liking the generated answer.
333
289
  * @param {CustomEvent} event
@@ -420,6 +376,7 @@ export default class QuanticGeneratedAnswer extends LightningElement {
420
376
 
421
377
  handleAnswerContentUpdated = (event) => {
422
378
  event.stopPropagation();
379
+ this.generatedAnswerHeight = event.detail?.height;
423
380
  if (this.isCollapsibleEnabled) {
424
381
  this._exceedsMaximumHeight = this.isMaximumHeightExceeded();
425
382
  }
@@ -453,15 +410,6 @@ export default class QuanticGeneratedAnswer extends LightningElement {
453
410
  return this.template.querySelector('.generated-answer__answer');
454
411
  }
455
412
 
456
- /**
457
- * Returns the generated answer height.
458
- * @returns {number}
459
- */
460
- get generatedAnswerElementHeight() {
461
- // @ts-ignore
462
- return getAbsoluteHeight(this.generatedAnswerElement?.firstChild);
463
- }
464
-
465
413
  /**
466
414
  * Sets the value of the CSS variable "--maxHeight" to the value of the maxCollapsedHeight property.
467
415
  */
@@ -476,31 +424,14 @@ export default class QuanticGeneratedAnswer extends LightningElement {
476
424
  return this?.state?.answer;
477
425
  }
478
426
 
479
- get answerId() {
480
- return this?.state?.answerId;
481
- }
482
-
483
427
  get citations() {
484
428
  return this?.state?.citations;
485
429
  }
486
430
 
487
- get answerContentFormat() {
488
- return this?.state?.answerContentFormat;
489
- }
490
-
491
- get shouldDisplayCitations() {
492
- const hasCitations = !!this.citations?.length;
493
- return hasCitations && !this.isAnswerCollapsed;
494
- }
495
-
496
431
  get isStreaming() {
497
432
  return this?.state?.isStreaming;
498
433
  }
499
434
 
500
- get shouldDisplayActions() {
501
- return this.isVisible && !this.isStreaming && !this.isAnswerCollapsed;
502
- }
503
-
504
435
  get isVisible() {
505
436
  return this.state.isVisible;
506
437
  }
@@ -527,15 +458,18 @@ export default class QuanticGeneratedAnswer extends LightningElement {
527
458
  );
528
459
  }
529
460
 
530
- get allGeneratedAnswers() {
531
- const initialAnswer = {
461
+ get initialAnswer() {
462
+ return {
532
463
  ...this.state,
533
464
  question: this.engine?.state.query?.q ?? '',
534
465
  };
466
+ }
467
+
468
+ get allGeneratedAnswers() {
535
469
  const followUpAnswers =
536
470
  this.generateAnswerWithFollowUps?.state.followUpAnswers
537
471
  ?.followUpAnswers ?? [];
538
- return [initialAnswer, ...followUpAnswers];
472
+ return [this.initialAnswer, ...followUpAnswers];
539
473
  }
540
474
 
541
475
  get isCollapsibleEnabled() {
@@ -566,36 +500,6 @@ export default class QuanticGeneratedAnswer extends LightningElement {
566
500
  return !this?.searchStatusState?.hasError && this.state?.error?.isRetryable;
567
501
  }
568
502
 
569
- /**
570
- * Returns the options displayed in the Quantic Feedback Modal.
571
- */
572
- get options() {
573
- return [
574
- {
575
- label: this.labels.irrelevant,
576
- value: 'irrelevant',
577
- },
578
- {
579
- label: this.labels.inaccurate,
580
- value: 'notAccurate',
581
- },
582
- {
583
- label: this.labels.outOfDate,
584
- value: 'outOfDate',
585
- },
586
- {
587
- label: this.labels.harmful,
588
- value: 'harmful',
589
- },
590
- {
591
- label: this.labels.other,
592
- value: 'other',
593
- withDetails: true,
594
- detailsRequired: true,
595
- },
596
- ];
597
- }
598
-
599
503
  get generatedAnswerHeaderClass() {
600
504
  const headerBaseClass =
601
505
  'generated-answer__card-header slds-grid slds-grid_vertical-align-center slds-grid_align-spread slds-p-horizontal_large slds-p-vertical_small';
@@ -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}
@@ -33,63 +33,24 @@
33
33
  data-testid="generated-answer__answer"
34
34
  class={generatedAnswerClass}
35
35
  >
36
- <c-quantic-generated-answer-content
36
+ <c-quantic-generated-answer-body
37
37
  engine-id={engineId}
38
- answer-id={answerId}
39
- answer-content-format={answerContentFormat}
40
- answer={answer}
41
- is-streaming={isStreaming}
38
+ generated-answer={initialAnswer}
39
+ disable-citation-anchoring={disableCitationAnchoring}
40
+ onquantic__generatedanswerlike={handleLike}
41
+ onquantic__generatedanswerdislike={handleDislike}
42
+ onquantic__citationhover={handleCitationHover}
42
43
  onquantic__answercontentupdated={handleAnswerContentUpdated}
43
- >
44
- </c-quantic-generated-answer-content>
44
+ ></c-quantic-generated-answer-body>
45
45
  </div>
46
46
  <div class="slds-grid slds-grid_vertical">
47
- <template lwc:if={shouldDisplayCitations}>
48
- <div class="slds-size_1-of-1 slds-var-m-top_x-small">
49
- <c-quantic-source-citations
50
- engine-id={engineId}
51
- data-testid="generated-answer__citations"
52
- citations={citations}
53
- citation-hover-handler={logCitationHover}
54
- disable-citation-anchoring={disableCitationAnchoring}
55
- ></c-quantic-source-citations>
56
- </div>
57
- </template>
58
- <template lwc:if={shouldDisplayActions}>
59
- <div class="slds-size_1-of-1 slds-grid slds-grid_vertical-align-center slds-var-m-top_x-small slds-grid_align-start">
60
- <div
61
- data-testid="generated-answer__actions"
62
- class="slds-grid slds-grid_vertical-align-center"
63
- >
64
- <c-quantic-feedback
65
- state={feedbackState}
66
- onquantic__like={handleLike}
67
- onquantic__dislike={handleDislike}
68
- like-icon-name="utility:like"
69
- like-label={labels.thisAnswerWasHelpful}
70
- dislike-icon-name="utility:dislike"
71
- dislike-label={labels.thisAnswerWasNotHelpful}
72
- size="x-small"
73
- question=""
74
- hide-explain-why-button
75
- hide-labels
76
- ></c-quantic-feedback>
77
- <c-quantic-generated-answer-copy-to-clipboard
78
- data-testid="generated-answer__copy-to-clipboard"
79
- answer={answer}
80
- size="x-small"
81
- class="slds-var-m-horizontal_xx-small"
82
- ></c-quantic-generated-answer-copy-to-clipboard>
83
- </div>
84
- </div>
85
- </template>
86
47
  <template lwc:if={isAnswerCollapsed}>
87
48
  <div class="slds-grid slds-size_1-of-1 slds-wrap slds-grid_vertical-align-center slds-var-m-top_x-small">
88
49
  <div class="slds-grid slds-wrap slds-grid_vertical-align-center">
89
50
  <template lwc:if={isStreaming}>
90
51
  <div
91
52
  data-testid="generated-answer__collapse-generating-message"
92
- class="generated-answer__collapse-generating-message slds-var-m-right_small"
53
+ class="generated-answer__collapse-generating-message slds-var-m-right_small slds-var-m-bottom_medium"
93
54
  >
94
55
  {labels.generatingAnswer}...
95
56
  </div>
@@ -5,6 +5,8 @@ import {createElement} from 'lwc';
5
5
  import {loadMarkdownDependencies} from 'c/quanticUtils';
6
6
  import * as mockHeadlessLoader from 'c/quanticHeadlessLoader';
7
7
 
8
+ const mockAnswerHeight = 250;
9
+
8
10
  jest.mock('c/quanticHeadlessLoader');
9
11
  jest.mock('c/quanticUtils', () => ({
10
12
  loadMarkdownDependencies: jest.fn(
@@ -17,6 +19,7 @@ jest.mock('c/quanticUtils', () => ({
17
19
  LinkUtils: {
18
20
  bindAnalyticsToLink: jest.fn(() => jest.fn()),
19
21
  },
22
+ getAbsoluteHeight: jest.fn(() => mockAnswerHeight),
20
23
  }));
21
24
 
22
25
  const mockMarkedUse = jest.fn();
@@ -417,4 +420,28 @@ describe('c-quantic-generated-answer-content', () => {
417
420
  });
418
421
  });
419
422
  });
423
+
424
+ describe('the quantic__answercontentupdated event', () => {
425
+ beforeEach(() => {
426
+ mockSuccessfulHeadlessInitialization();
427
+ prepareHeadlessState();
428
+ });
429
+
430
+ it('should dispatch the event with the measured height when the markdown content is updated', async () => {
431
+ const element = createTestComponent({
432
+ ...defaultOptions,
433
+ answerContentFormat: 'text/markdown',
434
+ answer: 'Hello, world!',
435
+ });
436
+ const handler = jest.fn();
437
+ element.addEventListener('quantic__answercontentupdated', handler);
438
+ await flushPromises();
439
+
440
+ expect(handler).toHaveBeenCalledTimes(1);
441
+ const event = handler.mock.calls[0][0];
442
+ expect(event.detail).toEqual({height: mockAnswerHeight});
443
+ expect(event.bubbles).toBe(true);
444
+ expect(event.composed).toBe(true);
445
+ });
446
+ });
420
447
  });
@@ -7,6 +7,7 @@ import {
7
7
  transformMarkdownToHtml,
8
8
  loadMarkdownDependencies,
9
9
  LinkUtils,
10
+ getAbsoluteHeight,
10
11
  } from 'c/quanticUtils';
11
12
  import {LightningElement, api} from 'lwc';
12
13
  // @ts-ignore
@@ -171,7 +172,13 @@ export default class QuanticGeneratedAnswerContent extends LightningElement {
171
172
  else {
172
173
  answerContainer.textContent = this.answer;
173
174
  }
174
- this.dispatchEvent(new CustomEvent('quantic__answercontentupdated'));
175
+ this.dispatchEvent(
176
+ new CustomEvent('quantic__answercontentupdated', {
177
+ bubbles: true,
178
+ composed: true,
179
+ detail: {height: getAbsoluteHeight(answerContainer)},
180
+ })
181
+ );
175
182
  }
176
183
 
177
184
  /**