@coveo/quantic 3.41.0 → 3.42.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.
- package/docs/out/quantic-docs.json +9 -0
- package/force-app/main/default/labels/CustomLabels.labels-meta.xml +14 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/__tests__/quanticGeneratedAnswer.test.js +271 -22
- package/force-app/main/default/lwc/quanticGeneratedAnswer/quanticGeneratedAnswer.js +92 -31
- package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/conversation.css +7 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/conversation.html +63 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.css +0 -11
- package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.html +2 -2
- package/force-app/main/default/lwc/quanticGeneratedAnswerBody/__tests__/quanticGeneratedAnswerBody.test.js +39 -1
- package/force-app/main/default/lwc/quanticGeneratedAnswerBody/quanticGeneratedAnswerBody.js +8 -4
- package/force-app/main/default/lwc/quanticGeneratedAnswerBody/templates/answer.html +30 -24
- package/force-app/main/default/lwc/quanticGeneratedAnswerCopyToClipboard/__tests__/quanticGeneratedAnswerCopyToClipboard.test.js +5 -1
- package/force-app/main/default/lwc/quanticGeneratedAnswerCopyToClipboard/quanticGeneratedAnswerCopyToClipboard.js +9 -2
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/__tests__/quanticGeneratedAnswerFollowUpInput.test.js +109 -55
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.css +59 -11
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.html +14 -13
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.js +40 -2
- package/force-app/main/default/lwc/quanticSourceCitations/__tests__/quanticSourceCitations.test.js +22 -2
- package/force-app/main/default/lwc/quanticSourceCitations/quanticSourceCitations.js +7 -0
- package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +15 -15
- package/force-app/main/default/staticresources/coveoheadless/definitions/commerce.index.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/sub-controller/headless-sub-controller.d.ts +3 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/recent-queries-list/headless-recent-queries-list.d.ts +13 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/search/did-you-mean/headless-did-you-mean.d.ts +10 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/generated-answer/headless-generated-answer.d.ts +2 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/follow-up-answers/follow-up-answers-actions.d.ts +32 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/generated-answer/generated-answer-actions.d.ts +26 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/generated-answer/generated-answer-state.d.ts +19 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/index.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/ssr/commerce/controllers/did-you-mean/headless-did-you-mean.ssr.d.ts +4 -3
- package/force-app/main/default/staticresources/coveoheadless/definitions/ssr-commerce-next.index.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/ssr-commerce.index.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/ssr-next/commerce/controllers/did-you-mean/headless-did-you-mean.ssr.d.ts +4 -3
- package/force-app/main/default/staticresources/coveoheadless/headless.js +18 -18
- 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/package.json +2 -2
|
@@ -1031,6 +1031,15 @@
|
|
|
1031
1031
|
"defaultValue": "{undefined}",
|
|
1032
1032
|
"type": "string"
|
|
1033
1033
|
},
|
|
1034
|
+
{
|
|
1035
|
+
"name": "agentId",
|
|
1036
|
+
"attribute": "agent-id",
|
|
1037
|
+
"access": "@api",
|
|
1038
|
+
"description": "<p>The unique identifier of the agent to use to generate the answer.</p>",
|
|
1039
|
+
"required": false,
|
|
1040
|
+
"defaultValue": "{undefined}",
|
|
1041
|
+
"type": "string"
|
|
1042
|
+
},
|
|
1034
1043
|
{
|
|
1035
1044
|
"name": "maxCollapsedHeight",
|
|
1036
1045
|
"attribute": "max-collapsed-height",
|
|
@@ -1715,4 +1715,18 @@
|
|
|
1715
1715
|
<protected>false</protected>
|
|
1716
1716
|
<shortDescription>Answer generated</shortDescription>
|
|
1717
1717
|
</labels>
|
|
1718
|
+
<labels>
|
|
1719
|
+
<fullName>quantic_Like</fullName>
|
|
1720
|
+
<value>Like</value>
|
|
1721
|
+
<language>en_US</language>
|
|
1722
|
+
<protected>false</protected>
|
|
1723
|
+
<shortDescription>Like</shortDescription>
|
|
1724
|
+
</labels>
|
|
1725
|
+
<labels>
|
|
1726
|
+
<fullName>quantic_Dislike</fullName>
|
|
1727
|
+
<value>Dislike</value>
|
|
1728
|
+
<language>en_US</language>
|
|
1729
|
+
<protected>false</protected>
|
|
1730
|
+
<shortDescription>Dislike</shortDescription>
|
|
1731
|
+
</labels>
|
|
1718
1732
|
</CustomLabels>
|
package/force-app/main/default/lwc/quanticGeneratedAnswer/__tests__/quanticGeneratedAnswer.test.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// @ts-ignore
|
|
3
3
|
import {createElement} from 'lwc';
|
|
4
4
|
import QuanticGeneratedAnswer from 'c/quanticGeneratedAnswer';
|
|
5
|
+
import FeedbackModalQna from 'c/quanticFeedbackModalQna';
|
|
5
6
|
import * as mockHeadlessLoader from 'c/quanticHeadlessLoader';
|
|
6
7
|
|
|
7
8
|
let mockAnswerHeight = 250;
|
|
@@ -25,6 +26,12 @@ const exampleCitations = [
|
|
|
25
26
|
const exampleEngineId = 'example engine id';
|
|
26
27
|
const exampleAnswerId = 'example answer id';
|
|
27
28
|
jest.mock('c/quanticHeadlessLoader');
|
|
29
|
+
jest.mock('c/quanticFeedbackModalQna', () => ({
|
|
30
|
+
__esModule: true,
|
|
31
|
+
default: {
|
|
32
|
+
open: jest.fn(() => Promise.resolve()),
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
28
35
|
jest.mock('c/quanticUtils', () => ({
|
|
29
36
|
AriaLiveRegion: jest.fn(() => ({
|
|
30
37
|
dispatchMessage: jest.fn(),
|
|
@@ -95,6 +102,9 @@ const selectors = {
|
|
|
95
102
|
generatedAnswerNoAnswerMessage:
|
|
96
103
|
'[data-testid="generated-answer__no-answer-message"]',
|
|
97
104
|
generatedAnswerCitations: 'c-quantic-source-citations',
|
|
105
|
+
generatedAnswerThread: 'c-quantic-generated-answer-thread',
|
|
106
|
+
generatedAnswerFollowUpInput: 'c-quantic-generated-answer-follow-up-input',
|
|
107
|
+
generatedAnswerFeedback: 'c-quantic-feedback',
|
|
98
108
|
loadingSpinner: 'lightning-spinner',
|
|
99
109
|
};
|
|
100
110
|
|
|
@@ -103,7 +113,10 @@ const initialSearchStatusState = {
|
|
|
103
113
|
};
|
|
104
114
|
let searchStatusState = initialSearchStatusState;
|
|
105
115
|
|
|
106
|
-
const initialGeneratedAnswerState = {
|
|
116
|
+
const initialGeneratedAnswerState = {
|
|
117
|
+
isVisible: true,
|
|
118
|
+
cannotAnswer: false,
|
|
119
|
+
};
|
|
107
120
|
let generatedAnswerState = initialGeneratedAnswerState;
|
|
108
121
|
|
|
109
122
|
const functionsMocks = {
|
|
@@ -111,6 +124,10 @@ const functionsMocks = {
|
|
|
111
124
|
state: generatedAnswerState,
|
|
112
125
|
subscribe: functionsMocks.generatedAnswerStateSubscriber,
|
|
113
126
|
retry: functionsMocks.retry,
|
|
127
|
+
askFollowUp: functionsMocks.askFollowUp,
|
|
128
|
+
like: functionsMocks.like,
|
|
129
|
+
dislike: functionsMocks.dislike,
|
|
130
|
+
closeFeedbackModal: functionsMocks.closeFeedbackModal,
|
|
114
131
|
})),
|
|
115
132
|
buildSearchStatus: jest.fn(() => ({
|
|
116
133
|
state: searchStatusState,
|
|
@@ -127,6 +144,10 @@ const functionsMocks = {
|
|
|
127
144
|
generatedAnswerStateUnsubscriber: jest.fn(),
|
|
128
145
|
searchStatusStateUnsubscriber: jest.fn(),
|
|
129
146
|
retry: jest.fn(),
|
|
147
|
+
askFollowUp: jest.fn(),
|
|
148
|
+
like: jest.fn(),
|
|
149
|
+
dislike: jest.fn(),
|
|
150
|
+
closeFeedbackModal: jest.fn(),
|
|
130
151
|
};
|
|
131
152
|
|
|
132
153
|
/**
|
|
@@ -145,8 +166,10 @@ function flushPromises() {
|
|
|
145
166
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
146
167
|
}
|
|
147
168
|
|
|
169
|
+
const exampleQuery = 'example query';
|
|
148
170
|
const exampleEngine = {
|
|
149
171
|
id: 'dummy engine',
|
|
172
|
+
state: {query: {q: exampleQuery}},
|
|
150
173
|
};
|
|
151
174
|
let isInitialized = false;
|
|
152
175
|
const defaultAnswerHeight = 250;
|
|
@@ -339,6 +362,36 @@ describe('c-quantic-generated-answer', () => {
|
|
|
339
362
|
);
|
|
340
363
|
});
|
|
341
364
|
});
|
|
365
|
+
|
|
366
|
+
describe('when the agent id property is passed to the component', () => {
|
|
367
|
+
it('should initialize the controller with the correct agent id value', async () => {
|
|
368
|
+
const exampleAgentId = 'example agent id';
|
|
369
|
+
createTestComponent({
|
|
370
|
+
...defaultOptions,
|
|
371
|
+
agentId: exampleAgentId,
|
|
372
|
+
});
|
|
373
|
+
await flushPromises();
|
|
374
|
+
|
|
375
|
+
expect(functionsMocks.buildGeneratedAnswer).toHaveBeenCalledTimes(1);
|
|
376
|
+
expect(functionsMocks.buildGeneratedAnswer).toHaveBeenCalledWith(
|
|
377
|
+
exampleEngine,
|
|
378
|
+
expect.objectContaining({agentId: exampleAgentId})
|
|
379
|
+
);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
describe('when the agent id property is not passed to the component', () => {
|
|
384
|
+
it('should initialize the controller without the agent id value', async () => {
|
|
385
|
+
createTestComponent();
|
|
386
|
+
await flushPromises();
|
|
387
|
+
|
|
388
|
+
expect(functionsMocks.buildGeneratedAnswer).toHaveBeenCalledTimes(1);
|
|
389
|
+
expect(functionsMocks.buildGeneratedAnswer).toHaveBeenCalledWith(
|
|
390
|
+
exampleEngine,
|
|
391
|
+
expect.not.objectContaining({agentId: expect.any(String)})
|
|
392
|
+
);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
342
395
|
});
|
|
343
396
|
|
|
344
397
|
describe('the rendering of the generated answer', () => {
|
|
@@ -792,25 +845,119 @@ describe('c-quantic-generated-answer', () => {
|
|
|
792
845
|
});
|
|
793
846
|
|
|
794
847
|
describe('when follow-ups are enabled', () => {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
848
|
+
const exampleAgentId = 'example agent id';
|
|
849
|
+
const exampleFollowUpAnswers = [
|
|
850
|
+
{
|
|
851
|
+
answer: 'follow-up answer',
|
|
852
|
+
answerId: 'answer-id-2',
|
|
853
|
+
question: 'follow-up question',
|
|
854
|
+
},
|
|
855
|
+
];
|
|
856
|
+
const followUpOptions = {...defaultOptions, agentId: exampleAgentId};
|
|
857
|
+
|
|
858
|
+
beforeEach(() => {
|
|
859
|
+
generatedAnswerState = {
|
|
860
|
+
...initialGeneratedAnswerState,
|
|
861
|
+
answer: exampleAnswer,
|
|
862
|
+
answerId: 'answer-id-1',
|
|
863
|
+
followUpAnswers: {
|
|
864
|
+
isEnabled: true,
|
|
865
|
+
followUpAnswers: exampleFollowUpAnswers,
|
|
866
|
+
},
|
|
867
|
+
};
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
it('should render the conversation thread and follow-up input instead of the single answer body', async () => {
|
|
871
|
+
const element = createTestComponent(followUpOptions);
|
|
872
|
+
await flushPromises();
|
|
873
|
+
|
|
874
|
+
const thread = element.shadowRoot.querySelector(
|
|
875
|
+
selectors.generatedAnswerThread
|
|
876
|
+
);
|
|
877
|
+
const followUpInput = element.shadowRoot.querySelector(
|
|
878
|
+
selectors.generatedAnswerFollowUpInput
|
|
879
|
+
);
|
|
880
|
+
const singleAnswerBody = element.shadowRoot.querySelector(
|
|
881
|
+
selectors.generatedAnswerBody
|
|
882
|
+
);
|
|
883
|
+
|
|
884
|
+
expect(thread).not.toBeNull();
|
|
885
|
+
expect(followUpInput).not.toBeNull();
|
|
886
|
+
expect(singleAnswerBody).toBeNull();
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
it('should display the scrollable container', async () => {
|
|
890
|
+
const element = createTestComponent(followUpOptions);
|
|
891
|
+
await flushPromises();
|
|
892
|
+
|
|
893
|
+
const scrollableContainer = element.shadowRoot.querySelector(
|
|
894
|
+
'.generated-answer__content--scrollable'
|
|
895
|
+
);
|
|
896
|
+
|
|
897
|
+
expect(scrollableContainer).not.toBeNull();
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
it('should pass the engine, citation anchoring and the combined initial and follow-up answers to the thread', async () => {
|
|
798
901
|
const element = createTestComponent({
|
|
799
|
-
...
|
|
800
|
-
|
|
902
|
+
...followUpOptions,
|
|
903
|
+
disableCitationAnchoring: true,
|
|
801
904
|
});
|
|
802
905
|
await flushPromises();
|
|
803
906
|
|
|
804
|
-
const
|
|
805
|
-
selectors.
|
|
907
|
+
const thread = element.shadowRoot.querySelector(
|
|
908
|
+
selectors.generatedAnswerThread
|
|
806
909
|
);
|
|
807
910
|
|
|
808
|
-
expect(
|
|
809
|
-
expect(
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
911
|
+
expect(thread.engineId).toBe(exampleEngineId);
|
|
912
|
+
expect(thread.disableCitationAnchoring).toBe(true);
|
|
913
|
+
expect(thread.generatedAnswers).toEqual([
|
|
914
|
+
expect.objectContaining({
|
|
915
|
+
answer: exampleAnswer,
|
|
916
|
+
question: exampleQuery,
|
|
917
|
+
}),
|
|
918
|
+
...exampleFollowUpAnswers,
|
|
919
|
+
]);
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
it('should ask a follow-up question when the follow-up input emits a submit event', async () => {
|
|
923
|
+
const element = createTestComponent(followUpOptions);
|
|
924
|
+
await flushPromises();
|
|
925
|
+
|
|
926
|
+
const followUpInput = element.shadowRoot.querySelector(
|
|
927
|
+
selectors.generatedAnswerFollowUpInput
|
|
928
|
+
);
|
|
929
|
+
const exampleFollowUpQuestion = 'example follow-up question';
|
|
930
|
+
followUpInput.dispatchEvent(
|
|
931
|
+
new CustomEvent('quantic__submitfollowup', {
|
|
932
|
+
detail: {value: exampleFollowUpQuestion},
|
|
933
|
+
})
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
expect(functionsMocks.askFollowUp).toHaveBeenCalledTimes(1);
|
|
937
|
+
expect(functionsMocks.askFollowUp).toHaveBeenCalledWith(
|
|
938
|
+
exampleFollowUpQuestion
|
|
939
|
+
);
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
it('should disable the follow-up submit button while an answer is being generated', async () => {
|
|
943
|
+
generatedAnswerState = {...generatedAnswerState, isStreaming: true};
|
|
944
|
+
const element = createTestComponent(followUpOptions);
|
|
945
|
+
await flushPromises();
|
|
946
|
+
|
|
947
|
+
const followUpInput = element.shadowRoot.querySelector(
|
|
948
|
+
selectors.generatedAnswerFollowUpInput
|
|
949
|
+
);
|
|
950
|
+
|
|
951
|
+
expect(followUpInput.submitButtonDisabled).toBe(true);
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
it('should ignore the collapsible feature and not render the single answer collapse toggle', async () => {
|
|
955
|
+
mockAnswerHeight = defaultAnswerHeight + 100;
|
|
956
|
+
const element = createTestComponent({
|
|
957
|
+
...followUpOptions,
|
|
958
|
+
collapsible: true,
|
|
959
|
+
});
|
|
960
|
+
await flushPromises();
|
|
814
961
|
|
|
815
962
|
const generatedAnswerCollapseToggle =
|
|
816
963
|
element.shadowRoot.querySelector(
|
|
@@ -822,20 +969,19 @@ describe('c-quantic-generated-answer', () => {
|
|
|
822
969
|
});
|
|
823
970
|
|
|
824
971
|
describe('when follow-ups are not enabled', () => {
|
|
825
|
-
it('should
|
|
972
|
+
it('should render the single answer body instead of the conversation thread', async () => {
|
|
826
973
|
const element = createTestComponent();
|
|
827
974
|
await flushPromises();
|
|
828
975
|
|
|
829
|
-
const
|
|
976
|
+
const singleAnswerBody = element.shadowRoot.querySelector(
|
|
830
977
|
selectors.generatedAnswerBody
|
|
831
978
|
);
|
|
979
|
+
const thread = element.shadowRoot.querySelector(
|
|
980
|
+
selectors.generatedAnswerThread
|
|
981
|
+
);
|
|
832
982
|
|
|
833
|
-
expect(
|
|
834
|
-
expect(
|
|
835
|
-
generatedAnswerBody.classList.contains(
|
|
836
|
-
'generated-answer__content--scrollable'
|
|
837
|
-
)
|
|
838
|
-
).toBe(false);
|
|
983
|
+
expect(singleAnswerBody).not.toBeNull();
|
|
984
|
+
expect(thread).toBeNull();
|
|
839
985
|
});
|
|
840
986
|
});
|
|
841
987
|
});
|
|
@@ -1238,4 +1384,107 @@ describe('c-quantic-generated-answer', () => {
|
|
|
1238
1384
|
});
|
|
1239
1385
|
});
|
|
1240
1386
|
});
|
|
1387
|
+
|
|
1388
|
+
describe('the generated answer feedback', () => {
|
|
1389
|
+
const exampleAnswer = 'answer generated successfully';
|
|
1390
|
+
const exampleAgentId = 'example agent id';
|
|
1391
|
+
|
|
1392
|
+
describe.each([
|
|
1393
|
+
{
|
|
1394
|
+
action: 'like',
|
|
1395
|
+
feedbackEvent: 'quantic__like',
|
|
1396
|
+
threadEvent: 'quantic__generatedanswerlike',
|
|
1397
|
+
controllerMethod: 'like',
|
|
1398
|
+
},
|
|
1399
|
+
{
|
|
1400
|
+
action: 'dislike',
|
|
1401
|
+
feedbackEvent: 'quantic__dislike',
|
|
1402
|
+
threadEvent: 'quantic__generatedanswerdislike',
|
|
1403
|
+
controllerMethod: 'dislike',
|
|
1404
|
+
},
|
|
1405
|
+
])(
|
|
1406
|
+
'when the user clicks the $action button',
|
|
1407
|
+
({feedbackEvent, threadEvent, controllerMethod}) => {
|
|
1408
|
+
describe('when follow-ups are not enabled', () => {
|
|
1409
|
+
beforeEach(() => {
|
|
1410
|
+
generatedAnswerState = {
|
|
1411
|
+
...initialGeneratedAnswerState,
|
|
1412
|
+
isStreaming: false,
|
|
1413
|
+
answer: exampleAnswer,
|
|
1414
|
+
answerContentFormat: 'text/markdown',
|
|
1415
|
+
answerId: exampleAnswerId,
|
|
1416
|
+
citations: exampleCitations,
|
|
1417
|
+
};
|
|
1418
|
+
mockSuccessfulHeadlessInitialization();
|
|
1419
|
+
prepareHeadlessState();
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
afterAll(() => {
|
|
1423
|
+
generatedAnswerState = initialGeneratedAnswerState;
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1426
|
+
it('should call the controller and open the feedback modal', async () => {
|
|
1427
|
+
const element = createTestComponent();
|
|
1428
|
+
await flushPromises();
|
|
1429
|
+
|
|
1430
|
+
const feedback = element.shadowRoot.querySelector(
|
|
1431
|
+
selectors.generatedAnswerFeedback
|
|
1432
|
+
);
|
|
1433
|
+
feedback.dispatchEvent(
|
|
1434
|
+
new CustomEvent(feedbackEvent, {
|
|
1435
|
+
detail: {answerId: exampleAnswerId},
|
|
1436
|
+
})
|
|
1437
|
+
);
|
|
1438
|
+
await flushPromises();
|
|
1439
|
+
|
|
1440
|
+
expect(functionsMocks[controllerMethod]).toHaveBeenCalledTimes(1);
|
|
1441
|
+
expect(functionsMocks[controllerMethod]).toHaveBeenCalledWith(
|
|
1442
|
+
exampleAnswerId
|
|
1443
|
+
);
|
|
1444
|
+
expect(FeedbackModalQna.open).toHaveBeenCalledTimes(1);
|
|
1445
|
+
});
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
describe('when follow-ups are enabled', () => {
|
|
1449
|
+
beforeEach(() => {
|
|
1450
|
+
generatedAnswerState = {
|
|
1451
|
+
...initialGeneratedAnswerState,
|
|
1452
|
+
answer: exampleAnswer,
|
|
1453
|
+
followUpAnswers: {isEnabled: true, followUpAnswers: []},
|
|
1454
|
+
};
|
|
1455
|
+
mockSuccessfulHeadlessInitialization();
|
|
1456
|
+
prepareHeadlessState();
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
afterAll(() => {
|
|
1460
|
+
generatedAnswerState = initialGeneratedAnswerState;
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
it('should call the controller without opening the feedback modal', async () => {
|
|
1464
|
+
const element = createTestComponent({
|
|
1465
|
+
...defaultOptions,
|
|
1466
|
+
agentId: exampleAgentId,
|
|
1467
|
+
});
|
|
1468
|
+
await flushPromises();
|
|
1469
|
+
|
|
1470
|
+
const thread = element.shadowRoot.querySelector(
|
|
1471
|
+
selectors.generatedAnswerThread
|
|
1472
|
+
);
|
|
1473
|
+
thread.dispatchEvent(
|
|
1474
|
+
new CustomEvent(threadEvent, {
|
|
1475
|
+
detail: {answerId: exampleAnswerId},
|
|
1476
|
+
})
|
|
1477
|
+
);
|
|
1478
|
+
await flushPromises();
|
|
1479
|
+
|
|
1480
|
+
expect(functionsMocks[controllerMethod]).toHaveBeenCalledTimes(1);
|
|
1481
|
+
expect(functionsMocks[controllerMethod]).toHaveBeenCalledWith(
|
|
1482
|
+
exampleAnswerId
|
|
1483
|
+
);
|
|
1484
|
+
expect(FeedbackModalQna.open).not.toHaveBeenCalled();
|
|
1485
|
+
});
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
);
|
|
1489
|
+
});
|
|
1241
1490
|
});
|
|
@@ -35,9 +35,12 @@ import generatedAnswerTemplate from './templates/generatedAnswer.html';
|
|
|
35
35
|
import loadingTemplate from './templates/loading.html';
|
|
36
36
|
// @ts-ignore
|
|
37
37
|
import retryPromptTemplate from './templates/retryPrompt.html';
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
import conversationTemplate from './templates/conversation.html';
|
|
38
40
|
|
|
39
41
|
/** @typedef {import("coveo").SearchEngine} SearchEngine */
|
|
40
42
|
/** @typedef {import("coveo").GeneratedAnswer} GeneratedAnswer */
|
|
43
|
+
/** @typedef {import("coveo").GeneratedAnswerWithFollowUps} GeneratedAnswerWithFollowUps */
|
|
41
44
|
/** @typedef {import("coveo").GeneratedAnswerState} GeneratedAnswerState */
|
|
42
45
|
/** @typedef {import("coveo").GeneratedAnswerCitation} GeneratedAnswerCitation */
|
|
43
46
|
/** @typedef { 'neutral' | 'liked' | 'disliked'} FeedbackState */
|
|
@@ -100,6 +103,13 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
100
103
|
* @default {undefined}
|
|
101
104
|
*/
|
|
102
105
|
@api answerConfigurationId;
|
|
106
|
+
/**
|
|
107
|
+
* The unique identifier of the agent to use to generate the answer.
|
|
108
|
+
* @api
|
|
109
|
+
* @type {string}
|
|
110
|
+
* @default {undefined}
|
|
111
|
+
*/
|
|
112
|
+
@api agentId;
|
|
103
113
|
/**
|
|
104
114
|
* The maximum height (in px units) of the generated answer when it is collapsed.
|
|
105
115
|
* @api
|
|
@@ -155,7 +165,7 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
155
165
|
noGeneratedAnswer,
|
|
156
166
|
};
|
|
157
167
|
|
|
158
|
-
/** @type {GeneratedAnswer} */
|
|
168
|
+
/** @type { GeneratedAnswer | GeneratedAnswerWithFollowUps} */
|
|
159
169
|
generatedAnswer;
|
|
160
170
|
/** @type {GeneratedAnswerState} */
|
|
161
171
|
state;
|
|
@@ -172,13 +182,7 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
172
182
|
/** @type {boolean} */
|
|
173
183
|
hasInitializationError = false;
|
|
174
184
|
/** @type {boolean} */
|
|
175
|
-
_areFollowUpsEnabled = false;
|
|
176
|
-
/** @type {boolean} */
|
|
177
185
|
_exceedsMaximumHeight = false;
|
|
178
|
-
/** @type {boolean} */
|
|
179
|
-
_liked = false;
|
|
180
|
-
/** @type {boolean} */
|
|
181
|
-
_disliked = false;
|
|
182
186
|
/** @type {number} */
|
|
183
187
|
_maxCollapsedHeight = DEFAULT_COLLAPSED_HEIGHT;
|
|
184
188
|
|
|
@@ -207,6 +211,7 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
207
211
|
* @param {SearchEngine} engine
|
|
208
212
|
*/
|
|
209
213
|
initialize = (engine) => {
|
|
214
|
+
this.engine = engine;
|
|
210
215
|
this.ariaLiveMessage = AriaLiveRegion('GeneratedAnswer', this);
|
|
211
216
|
this.headless = getHeadlessBundle(this.engineId);
|
|
212
217
|
this.generatedAnswer = this.buildHeadlessGeneratedAnswerController(engine);
|
|
@@ -238,6 +243,9 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
238
243
|
...(this.answerConfigurationId && {
|
|
239
244
|
answerConfigurationId: this.answerConfigurationId,
|
|
240
245
|
}),
|
|
246
|
+
...(this.agentId?.trim() && {
|
|
247
|
+
agentId: this.agentId.trim(),
|
|
248
|
+
}),
|
|
241
249
|
fieldsToIncludeInCitations: this.citationFields,
|
|
242
250
|
});
|
|
243
251
|
}
|
|
@@ -316,7 +324,7 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
316
324
|
* @param {string} id
|
|
317
325
|
* @param {number} citationHoverTimeMs
|
|
318
326
|
*/
|
|
319
|
-
|
|
327
|
+
logCitationHover = (id, citationHoverTimeMs) => {
|
|
320
328
|
this.generatedAnswer.logCitationHover(id, citationHoverTimeMs);
|
|
321
329
|
};
|
|
322
330
|
|
|
@@ -326,12 +334,8 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
326
334
|
*/
|
|
327
335
|
async handleLike(event) {
|
|
328
336
|
event.stopPropagation();
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
this._disliked = false;
|
|
332
|
-
this.generatedAnswer.like?.();
|
|
333
|
-
}
|
|
334
|
-
if (!this.feedbackSubmitted) {
|
|
337
|
+
this.generatedAnswer.like?.(event.detail?.answerId);
|
|
338
|
+
if (!this.feedbackSubmitted && !this.areFollowUpsEnabled) {
|
|
335
339
|
// @ts-ignore
|
|
336
340
|
await FeedbackModalQna.open({
|
|
337
341
|
size: 'small',
|
|
@@ -344,18 +348,28 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
344
348
|
}
|
|
345
349
|
}
|
|
346
350
|
|
|
351
|
+
/**
|
|
352
|
+
* handles hovering over a citation.
|
|
353
|
+
* @param {CustomEvent} event
|
|
354
|
+
*/
|
|
355
|
+
handleCitationHover(event) {
|
|
356
|
+
event.stopPropagation();
|
|
357
|
+
const {citationId, citationHoverTimeMs, answerId} = event.detail;
|
|
358
|
+
this.generatedAnswer.logCitationHover(
|
|
359
|
+
citationId,
|
|
360
|
+
citationHoverTimeMs,
|
|
361
|
+
answerId
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
347
365
|
/**
|
|
348
366
|
* handles disliking the generated answer.
|
|
349
367
|
* @param {CustomEvent} event
|
|
350
368
|
*/
|
|
351
369
|
async handleDislike(event) {
|
|
352
370
|
event.stopPropagation();
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
this._liked = false;
|
|
356
|
-
this.generatedAnswer.dislike?.();
|
|
357
|
-
}
|
|
358
|
-
if (!this.feedbackSubmitted) {
|
|
371
|
+
this.generatedAnswer.dislike?.(event.detail?.answerId);
|
|
372
|
+
if (!this.feedbackSubmitted && !this.areFollowUpsEnabled) {
|
|
359
373
|
// @ts-ignore
|
|
360
374
|
await FeedbackModalQna.open({
|
|
361
375
|
size: 'small',
|
|
@@ -381,9 +395,13 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
381
395
|
this.generatedAnswer.retry();
|
|
382
396
|
}
|
|
383
397
|
|
|
398
|
+
/**
|
|
399
|
+
* handles copying the generated answer to the clipboard.
|
|
400
|
+
* @param {CustomEvent} event
|
|
401
|
+
*/
|
|
384
402
|
handleGeneratedAnswerCopyToClipboard = (event) => {
|
|
385
403
|
event.stopPropagation();
|
|
386
|
-
this.generatedAnswer.logCopyToClipboard();
|
|
404
|
+
this.generatedAnswer.logCopyToClipboard(event.detail?.answerId);
|
|
387
405
|
};
|
|
388
406
|
|
|
389
407
|
handleGeneratedAnswerToggle = (event) => {
|
|
@@ -487,9 +505,37 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
487
505
|
return this.state.isVisible;
|
|
488
506
|
}
|
|
489
507
|
|
|
508
|
+
/**
|
|
509
|
+
* Returns the generated answer controller narrowed to `GeneratedAnswerWithFollowUps`, or `null` if follow-ups are not available.
|
|
510
|
+
* @returns {GeneratedAnswerWithFollowUps | null}
|
|
511
|
+
*/
|
|
512
|
+
get generateAnswerWithFollowUps() {
|
|
513
|
+
if (
|
|
514
|
+
!this.agentId ||
|
|
515
|
+
!this.generatedAnswer ||
|
|
516
|
+
!('askFollowUp' in this.generatedAnswer)
|
|
517
|
+
) {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
return /** @type {GeneratedAnswerWithFollowUps} */ (this.generatedAnswer);
|
|
521
|
+
}
|
|
522
|
+
|
|
490
523
|
get areFollowUpsEnabled() {
|
|
491
|
-
|
|
492
|
-
|
|
524
|
+
return (
|
|
525
|
+
this.generateAnswerWithFollowUps?.state.followUpAnswers?.isEnabled ===
|
|
526
|
+
true
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
get allGeneratedAnswers() {
|
|
531
|
+
const initialAnswer = {
|
|
532
|
+
...this.state,
|
|
533
|
+
question: this.engine?.state.query?.q ?? '',
|
|
534
|
+
};
|
|
535
|
+
const followUpAnswers =
|
|
536
|
+
this.generateAnswerWithFollowUps?.state.followUpAnswers
|
|
537
|
+
?.followUpAnswers ?? [];
|
|
538
|
+
return [initialAnswer, ...followUpAnswers];
|
|
493
539
|
}
|
|
494
540
|
|
|
495
541
|
get isCollapsibleEnabled() {
|
|
@@ -516,14 +562,6 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
516
562
|
return `generated-answer__answer ${collapsedStateClass}`;
|
|
517
563
|
}
|
|
518
564
|
|
|
519
|
-
get contentSectionClass() {
|
|
520
|
-
const baseClass =
|
|
521
|
-
'generated-answer__content slds-p-top_medium slds-p-horizontal_large';
|
|
522
|
-
return this.areFollowUpsEnabled
|
|
523
|
-
? `${baseClass} generated-answer__content--scrollable`
|
|
524
|
-
: baseClass;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
565
|
get hasRetryableError() {
|
|
528
566
|
return !this?.searchStatusState?.hasError && this.state?.error?.isRetryable;
|
|
529
567
|
}
|
|
@@ -618,6 +656,17 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
618
656
|
return this.state?.isLoading;
|
|
619
657
|
}
|
|
620
658
|
|
|
659
|
+
get isAnswerGenerationOngoing() {
|
|
660
|
+
const initialAnswerPending = this.isStreaming || this.isLoading;
|
|
661
|
+
const followUpAnswers =
|
|
662
|
+
this.generateAnswerWithFollowUps?.state?.followUpAnswers
|
|
663
|
+
?.followUpAnswers ?? [];
|
|
664
|
+
return (
|
|
665
|
+
initialAnswerPending ||
|
|
666
|
+
followUpAnswers.some((answer) => answer.isStreaming || answer.isLoading)
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
|
|
621
670
|
get isManualAnswerGeneration() {
|
|
622
671
|
return this.state?.answerGenerationMode === 'manual';
|
|
623
672
|
}
|
|
@@ -629,6 +678,15 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
629
678
|
this.hasInitializationError = true;
|
|
630
679
|
}
|
|
631
680
|
|
|
681
|
+
/**
|
|
682
|
+
* Handles the submission of a follow-up question from the `QuanticGeneratedAnswerFollowUpInput` component.
|
|
683
|
+
* @param {CustomEvent} event - The custom event containing the follow-up question details.
|
|
684
|
+
* @returns {void}
|
|
685
|
+
*/
|
|
686
|
+
handleFollowUpSubmit = (event) => {
|
|
687
|
+
this.generateAnswerWithFollowUps?.askFollowUp(event.detail.value);
|
|
688
|
+
};
|
|
689
|
+
|
|
632
690
|
render() {
|
|
633
691
|
if (this.hasInitializationError) {
|
|
634
692
|
return errorTemplate;
|
|
@@ -643,6 +701,9 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
643
701
|
if (this.hasRetryableError) {
|
|
644
702
|
return retryPromptTemplate;
|
|
645
703
|
}
|
|
704
|
+
if (this.areFollowUpsEnabled) {
|
|
705
|
+
return conversationTemplate;
|
|
706
|
+
}
|
|
646
707
|
return generatedAnswerTemplate;
|
|
647
708
|
}
|
|
648
709
|
}
|