@coveo/quantic 3.40.0 → 3.41.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 +77 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerBody/__tests__/quanticGeneratedAnswerBody.test.js +341 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerBody/quanticGeneratedAnswerBody.js +148 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerBody/quanticGeneratedAnswerBody.js-meta.xml +5 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerBody/templates/answer.css +3 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerBody/templates/answer.html +53 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerBody/templates/cannotAnswer.html +7 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerBody/templates/error.html +7 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/__tests__/quanticGeneratedAnswerStreamOfThought.test.js +348 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/quanticGeneratedAnswerStreamOfThought.css +17 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/quanticGeneratedAnswerStreamOfThought.js +163 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/quanticGeneratedAnswerStreamOfThought.js-meta.xml +5 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/templates/collapsedSummary.css +1 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/templates/collapsedSummary.html +32 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/templates/streamOfThought.css +1 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/templates/streamOfThought.html +65 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerThread/__tests__/quanticGeneratedAnswerThread.test.js +285 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerThread/quanticGeneratedAnswerThread.css +47 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerThread/quanticGeneratedAnswerThread.html +67 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerThread/quanticGeneratedAnswerThread.js +93 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerThread/quanticGeneratedAnswerThread.js-meta.xml +5 -0
- package/force-app/main/default/lwc/quanticThreadItem/quanticThreadItem.css +0 -4
- package/force-app/main/default/lwc/quanticThreadItem/quanticThreadItem.html +1 -1
- package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +6 -6
- package/force-app/main/default/staticresources/coveoheadless/definitions/api/commerce/common/pagination.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/core/generated-answer/headless-core-generated-answer.d.ts +1 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/core/generated-answer/headless-core-interactive-citation.d.ts +3 -3
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/generated-answer/interactive-citation-analytics-client.d.ts +3 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/analytics/analytics-utils.d.ts +3 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/generated-answer/generated-answer-analytics-actions.d.ts +2 -0
- package/force-app/main/default/staticresources/coveoheadless/headless.js +17 -17
- package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +7 -7
- package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +14 -14
- package/package.json +2 -2
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import {resolveSteps} from '../quanticGeneratedAnswerStreamOfThought.js';
|
|
2
|
+
import {buildCreateTestComponent, cleanup, flushPromises} from 'c/testUtils';
|
|
3
|
+
import QuanticGeneratedAnswerStreamOfThought from '../quanticGeneratedAnswerStreamOfThought';
|
|
4
|
+
|
|
5
|
+
const selectors = {
|
|
6
|
+
stepItem: '[data-testid="step-item"]',
|
|
7
|
+
spinner: '[data-testid="spinner"]',
|
|
8
|
+
checkmark: '[data-testid="checkmark"]',
|
|
9
|
+
stepLabel: '[data-testid="step-label"]',
|
|
10
|
+
collapseButton: '[data-testid="collapse-button"]',
|
|
11
|
+
collapsedSummary: '[data-testid="collapsed-summary"]',
|
|
12
|
+
collapsedSummaryLabel: '[data-testid="collapsed-summary-label"]',
|
|
13
|
+
chevronUp: '[data-testid="chevron-up"]',
|
|
14
|
+
chevronDown: '[data-testid="chevron-down"]',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const createTestComponent = buildCreateTestComponent(
|
|
18
|
+
QuanticGeneratedAnswerStreamOfThought,
|
|
19
|
+
'c-quantic-generated-answer-stream-of-thought',
|
|
20
|
+
{agentSteps: [], isStreaming: false}
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
describe('#resolveSteps', () => {
|
|
24
|
+
it('should return empty array for empty input', () => {
|
|
25
|
+
expect(resolveSteps([])).toEqual([]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should resolve a full sequence correctly', () => {
|
|
29
|
+
expect(
|
|
30
|
+
resolveSteps([
|
|
31
|
+
{name: 'thinking', status: 'completed', startedAt: 0},
|
|
32
|
+
{name: 'searching', status: 'completed', startedAt: 10},
|
|
33
|
+
{name: 'thinking', status: 'completed', startedAt: 20},
|
|
34
|
+
{name: 'answering', status: 'active', startedAt: 30},
|
|
35
|
+
])
|
|
36
|
+
).toEqual([
|
|
37
|
+
{name: 'thinking-before-search', status: 'completed'},
|
|
38
|
+
{name: 'searching', status: 'completed'},
|
|
39
|
+
{name: 'thinking-after-search', status: 'completed'},
|
|
40
|
+
{name: 'answering', status: 'active'},
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should handle repeated searching steps', () => {
|
|
45
|
+
expect(
|
|
46
|
+
resolveSteps([
|
|
47
|
+
{name: 'thinking', status: 'completed', startedAt: 0},
|
|
48
|
+
{name: 'searching', status: 'completed', startedAt: 10},
|
|
49
|
+
{name: 'thinking', status: 'completed', startedAt: 20},
|
|
50
|
+
{name: 'searching', status: 'completed', startedAt: 30},
|
|
51
|
+
{name: 'thinking', status: 'active', startedAt: 40},
|
|
52
|
+
])
|
|
53
|
+
).toEqual([
|
|
54
|
+
{name: 'thinking-before-search', status: 'completed'},
|
|
55
|
+
{name: 'searching', status: 'completed'},
|
|
56
|
+
{name: 'thinking-after-search', status: 'completed'},
|
|
57
|
+
{name: 'searching', status: 'completed'},
|
|
58
|
+
{name: 'thinking-after-search', status: 'active'},
|
|
59
|
+
]);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('quantic generated answer stream of thought component', () => {
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
cleanup();
|
|
66
|
+
});
|
|
67
|
+
describe('during streaming', () => {
|
|
68
|
+
it('should render nothing when there are no steps', async () => {
|
|
69
|
+
const element = createTestComponent({
|
|
70
|
+
agentSteps: [],
|
|
71
|
+
isStreaming: true,
|
|
72
|
+
});
|
|
73
|
+
await flushPromises();
|
|
74
|
+
|
|
75
|
+
expect(element.shadowRoot.firstChild).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should show all steps progressively', async () => {
|
|
79
|
+
const element = createTestComponent({
|
|
80
|
+
agentSteps: [
|
|
81
|
+
{name: 'thinking', status: 'completed'},
|
|
82
|
+
{name: 'searching', status: 'active'},
|
|
83
|
+
],
|
|
84
|
+
isStreaming: true,
|
|
85
|
+
});
|
|
86
|
+
await flushPromises();
|
|
87
|
+
|
|
88
|
+
const stepItems = element.shadowRoot.querySelectorAll(selectors.stepItem);
|
|
89
|
+
expect(stepItems).toHaveLength(2);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should render steps in the correct order', async () => {
|
|
93
|
+
const element = createTestComponent({
|
|
94
|
+
agentSteps: [
|
|
95
|
+
{name: 'thinking', status: 'completed', startedAt: 0},
|
|
96
|
+
{name: 'searching', status: 'completed', startedAt: 10},
|
|
97
|
+
{name: 'thinking', status: 'completed', startedAt: 20},
|
|
98
|
+
{name: 'searching', status: 'completed', startedAt: 30},
|
|
99
|
+
{name: 'thinking', status: 'completed', startedAt: 40},
|
|
100
|
+
{name: 'answering', status: 'active', startedAt: 30},
|
|
101
|
+
],
|
|
102
|
+
isStreaming: true,
|
|
103
|
+
});
|
|
104
|
+
await flushPromises();
|
|
105
|
+
|
|
106
|
+
const stepItems = element.shadowRoot.querySelectorAll(selectors.stepItem);
|
|
107
|
+
const names = Array.from(stepItems).map((el) =>
|
|
108
|
+
el.getAttribute('data-step-name')
|
|
109
|
+
);
|
|
110
|
+
expect(names).toEqual([
|
|
111
|
+
'thinking-before-search',
|
|
112
|
+
'searching',
|
|
113
|
+
'thinking-after-search',
|
|
114
|
+
'searching',
|
|
115
|
+
'thinking-after-search',
|
|
116
|
+
'answering',
|
|
117
|
+
]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should show checkmarks for completed steps and spinners for active steps', async () => {
|
|
121
|
+
const element = createTestComponent({
|
|
122
|
+
agentSteps: [
|
|
123
|
+
{name: 'thinking', status: 'completed'},
|
|
124
|
+
{name: 'searching', status: 'active'},
|
|
125
|
+
],
|
|
126
|
+
isStreaming: true,
|
|
127
|
+
});
|
|
128
|
+
await flushPromises();
|
|
129
|
+
|
|
130
|
+
const checkmarks = element.shadowRoot.querySelectorAll(
|
|
131
|
+
selectors.checkmark
|
|
132
|
+
);
|
|
133
|
+
const spinners = element.shadowRoot.querySelectorAll(selectors.spinner);
|
|
134
|
+
expect(checkmarks).toHaveLength(1);
|
|
135
|
+
expect(spinners).toHaveLength(1);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should not show a collapse button', async () => {
|
|
139
|
+
const element = createTestComponent({
|
|
140
|
+
agentSteps: [{name: 'thinking', status: 'active'}],
|
|
141
|
+
isStreaming: true,
|
|
142
|
+
});
|
|
143
|
+
await flushPromises();
|
|
144
|
+
|
|
145
|
+
const collapseButton = element.shadowRoot.querySelector(
|
|
146
|
+
selectors.collapseButton
|
|
147
|
+
);
|
|
148
|
+
const collapsedSummary = element.shadowRoot.querySelector(
|
|
149
|
+
selectors.collapsedSummary
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
expect(collapseButton).toBeNull();
|
|
153
|
+
expect(collapsedSummary).toBeNull();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('after streaming completes', () => {
|
|
158
|
+
const multipleSteps = [
|
|
159
|
+
{name: 'thinking', status: 'completed'},
|
|
160
|
+
{name: 'searching', status: 'completed'},
|
|
161
|
+
{name: 'thinking', status: 'completed'},
|
|
162
|
+
{name: 'answering', status: 'active'},
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
it('should auto-collapse to show only the last step', async () => {
|
|
166
|
+
const element = createTestComponent({
|
|
167
|
+
agentSteps: multipleSteps,
|
|
168
|
+
isStreaming: true,
|
|
169
|
+
});
|
|
170
|
+
await flushPromises();
|
|
171
|
+
let collapsedSummary = element.shadowRoot.querySelector(
|
|
172
|
+
selectors.collapsedSummary
|
|
173
|
+
);
|
|
174
|
+
let stepItems = element.shadowRoot.querySelectorAll(selectors.stepItem);
|
|
175
|
+
|
|
176
|
+
expect(collapsedSummary).toBeNull();
|
|
177
|
+
expect(stepItems).toHaveLength(multipleSteps.length);
|
|
178
|
+
|
|
179
|
+
element.isStreaming = false;
|
|
180
|
+
element.agentSteps = [
|
|
181
|
+
{name: 'thinking', status: 'completed'},
|
|
182
|
+
{name: 'searching', status: 'completed'},
|
|
183
|
+
{name: 'thinking', status: 'completed'},
|
|
184
|
+
{name: 'answering', status: 'completed'},
|
|
185
|
+
];
|
|
186
|
+
await flushPromises();
|
|
187
|
+
|
|
188
|
+
collapsedSummary = element.shadowRoot.querySelector(
|
|
189
|
+
selectors.collapsedSummary
|
|
190
|
+
);
|
|
191
|
+
stepItems = element.shadowRoot.querySelectorAll(selectors.stepItem);
|
|
192
|
+
const summaryLabel = element.shadowRoot.querySelector(
|
|
193
|
+
selectors.collapsedSummaryLabel
|
|
194
|
+
);
|
|
195
|
+
const checkmark = collapsedSummary.querySelector(selectors.checkmark);
|
|
196
|
+
|
|
197
|
+
expect(collapsedSummary).not.toBeNull();
|
|
198
|
+
expect(summaryLabel).not.toBeNull();
|
|
199
|
+
expect(checkmark).not.toBeNull();
|
|
200
|
+
expect(stepItems).toHaveLength(0);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should have aria-expanded set to false when collapsed', async () => {
|
|
204
|
+
const element = createTestComponent({
|
|
205
|
+
agentSteps: multipleSteps,
|
|
206
|
+
isStreaming: false,
|
|
207
|
+
});
|
|
208
|
+
await flushPromises();
|
|
209
|
+
|
|
210
|
+
const collapsedSummary = element.shadowRoot.querySelector(
|
|
211
|
+
selectors.collapsedSummary
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect(collapsedSummary).not.toBeNull();
|
|
215
|
+
expect(collapsedSummary.getAttribute('aria-expanded')).toBe('false');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should render nothing when there are no steps and not streaming', async () => {
|
|
219
|
+
const element = createTestComponent({
|
|
220
|
+
agentSteps: [],
|
|
221
|
+
isStreaming: false,
|
|
222
|
+
});
|
|
223
|
+
await flushPromises();
|
|
224
|
+
|
|
225
|
+
expect(element.shadowRoot.firstChild).toBeNull();
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('expand/collapse interaction', () => {
|
|
230
|
+
const multipleSteps = [
|
|
231
|
+
{name: 'thinking', status: 'completed'},
|
|
232
|
+
{name: 'searching', status: 'completed'},
|
|
233
|
+
{name: 'thinking', status: 'completed'},
|
|
234
|
+
{name: 'answering', status: 'completed'},
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
it('should expand to show all steps and collapse button when collapsed summary row is clicked', async () => {
|
|
238
|
+
const element = createTestComponent({
|
|
239
|
+
agentSteps: multipleSteps,
|
|
240
|
+
isStreaming: false,
|
|
241
|
+
});
|
|
242
|
+
await flushPromises();
|
|
243
|
+
|
|
244
|
+
const collapsedSummary = element.shadowRoot.querySelector(
|
|
245
|
+
selectors.collapsedSummary
|
|
246
|
+
);
|
|
247
|
+
expect(collapsedSummary).not.toBeNull();
|
|
248
|
+
|
|
249
|
+
collapsedSummary.click();
|
|
250
|
+
await flushPromises();
|
|
251
|
+
|
|
252
|
+
const stepItems = element.shadowRoot.querySelectorAll(selectors.stepItem);
|
|
253
|
+
const collapseButton = element.shadowRoot.querySelector(
|
|
254
|
+
selectors.collapseButton
|
|
255
|
+
);
|
|
256
|
+
expect(stepItems).toHaveLength(multipleSteps.length);
|
|
257
|
+
expect(collapseButton).not.toBeNull();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should collapse back when collapse button is clicked', async () => {
|
|
261
|
+
const element = createTestComponent({
|
|
262
|
+
agentSteps: multipleSteps,
|
|
263
|
+
isStreaming: false,
|
|
264
|
+
});
|
|
265
|
+
await flushPromises();
|
|
266
|
+
|
|
267
|
+
const collapsedSummary = element.shadowRoot.querySelector(
|
|
268
|
+
selectors.collapsedSummary
|
|
269
|
+
);
|
|
270
|
+
collapsedSummary.click();
|
|
271
|
+
await flushPromises();
|
|
272
|
+
|
|
273
|
+
const collapseButton = element.shadowRoot.querySelector(
|
|
274
|
+
selectors.collapseButton
|
|
275
|
+
);
|
|
276
|
+
expect(collapseButton).not.toBeNull();
|
|
277
|
+
|
|
278
|
+
collapseButton.click();
|
|
279
|
+
await flushPromises();
|
|
280
|
+
|
|
281
|
+
expect(
|
|
282
|
+
element.shadowRoot.querySelector(selectors.collapsedSummary)
|
|
283
|
+
).not.toBeNull();
|
|
284
|
+
expect(
|
|
285
|
+
element.shadowRoot.querySelectorAll(selectors.stepItem)
|
|
286
|
+
).toHaveLength(0);
|
|
287
|
+
expect(
|
|
288
|
+
element.shadowRoot.querySelector(selectors.collapseButton)
|
|
289
|
+
).toBeNull();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should set aria-expanded to true when expanded', async () => {
|
|
293
|
+
const element = createTestComponent({
|
|
294
|
+
agentSteps: multipleSteps,
|
|
295
|
+
isStreaming: false,
|
|
296
|
+
});
|
|
297
|
+
await flushPromises();
|
|
298
|
+
|
|
299
|
+
const collapsedSummary = element.shadowRoot.querySelector(
|
|
300
|
+
selectors.collapsedSummary
|
|
301
|
+
);
|
|
302
|
+
collapsedSummary.click();
|
|
303
|
+
await flushPromises();
|
|
304
|
+
|
|
305
|
+
const collapseButton = element.shadowRoot.querySelector(
|
|
306
|
+
selectors.collapseButton
|
|
307
|
+
);
|
|
308
|
+
expect(collapseButton).not.toBeNull();
|
|
309
|
+
expect(collapseButton.getAttribute('aria-expanded')).toBe('true');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should set aria-expanded to false when collapsed', async () => {
|
|
313
|
+
const element = createTestComponent({
|
|
314
|
+
agentSteps: multipleSteps,
|
|
315
|
+
isStreaming: false,
|
|
316
|
+
});
|
|
317
|
+
await flushPromises();
|
|
318
|
+
|
|
319
|
+
const collapsedSummary = element.shadowRoot.querySelector(
|
|
320
|
+
selectors.collapsedSummary
|
|
321
|
+
);
|
|
322
|
+
expect(collapsedSummary).not.toBeNull();
|
|
323
|
+
expect(collapsedSummary.getAttribute('aria-expanded')).toBe('false');
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe('single answering step', () => {
|
|
328
|
+
it('should show the step without a collapse button when only answering exists', async () => {
|
|
329
|
+
const element = createTestComponent({
|
|
330
|
+
agentSteps: [{name: 'answering', status: 'completed'}],
|
|
331
|
+
isStreaming: false,
|
|
332
|
+
});
|
|
333
|
+
await flushPromises();
|
|
334
|
+
|
|
335
|
+
const stepItems = element.shadowRoot.querySelectorAll(selectors.stepItem);
|
|
336
|
+
const collapseButton = element.shadowRoot.querySelector(
|
|
337
|
+
selectors.collapseButton
|
|
338
|
+
);
|
|
339
|
+
const collapsedSummary = element.shadowRoot.querySelector(
|
|
340
|
+
selectors.collapsedSummary
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
expect(stepItems).toHaveLength(1);
|
|
344
|
+
expect(collapseButton).toBeNull();
|
|
345
|
+
expect(collapsedSummary).toBeNull();
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
.stream-of-thought__collapsed-summary,
|
|
2
|
+
.stream-of-thought__collapse-button {
|
|
3
|
+
border: none;
|
|
4
|
+
background: transparent;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.stream-of-thought__collapsed-summary:focus-visible,
|
|
8
|
+
.stream-of-thought__collapse-button:focus-visible {
|
|
9
|
+
border-radius: var(--slds-g-sizing-border-4, 0.25rem);
|
|
10
|
+
outline: 2px solid var(--lwc-brandPrimary, #1b96ff);
|
|
11
|
+
outline-offset: 2px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.stream-of-thought__spinner-container {
|
|
15
|
+
width: 0.8rem;
|
|
16
|
+
height: 0.8rem;
|
|
17
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import analyzingQuestion from '@salesforce/label/c.quantic_AgentGenerationStepAnalyzingQuestion';
|
|
2
|
+
import analyzingQuestionCompleted from '@salesforce/label/c.quantic_AgentGenerationStepAnalyzingQuestionCompleted';
|
|
3
|
+
import search from '@salesforce/label/c.quantic_AgentGenerationStepSearch';
|
|
4
|
+
import searchCompleted from '@salesforce/label/c.quantic_AgentGenerationStepSearchCompleted';
|
|
5
|
+
import analyzingResults from '@salesforce/label/c.quantic_AgentGenerationStepAnalyzingResults';
|
|
6
|
+
import analyzingResultsCompleted from '@salesforce/label/c.quantic_AgentGenerationStepAnalyzingResultsCompleted';
|
|
7
|
+
import answering from '@salesforce/label/c.quantic_AgentGenerationStepAnswering';
|
|
8
|
+
import answeringCompleted from '@salesforce/label/c.quantic_AgentGenerationStepAnsweringCompleted';
|
|
9
|
+
import collapseButton from '@salesforce/label/c.quantic_CollapseButton';
|
|
10
|
+
import loadingLabel from '@salesforce/label/c.quantic_Loading';
|
|
11
|
+
import {LightningElement, api} from 'lwc';
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
import streamOfThoughtTemplate from './templates/streamOfThought.html';
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
import collapsedSummaryTemplate from './templates/collapsedSummary.html';
|
|
16
|
+
|
|
17
|
+
/** @typedef {import("coveo").GenerationStep} GenerationStep */
|
|
18
|
+
/** @typedef {'thinking-before-search'|'searching'|'thinking-after-search'|'answering'} ResolvedStepName */
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} ResolvedStep
|
|
21
|
+
* @property {ResolvedStepName} name
|
|
22
|
+
* @property {'active'|'completed'} status
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** @type {Record<string, {active: string, completed: string}>} */
|
|
26
|
+
const STEP_LABEL_KEYS = {
|
|
27
|
+
'thinking-before-search': {
|
|
28
|
+
active: analyzingQuestion,
|
|
29
|
+
completed: analyzingQuestionCompleted,
|
|
30
|
+
},
|
|
31
|
+
searching: {
|
|
32
|
+
active: search,
|
|
33
|
+
completed: searchCompleted,
|
|
34
|
+
},
|
|
35
|
+
'thinking-after-search': {
|
|
36
|
+
active: analyzingResults,
|
|
37
|
+
completed: analyzingResultsCompleted,
|
|
38
|
+
},
|
|
39
|
+
answering: {
|
|
40
|
+
active: answering,
|
|
41
|
+
completed: answeringCompleted,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Maps an array of raw generation steps to resolved steps with a normalized type.
|
|
47
|
+
* @param {GenerationStep[]} steps
|
|
48
|
+
* @returns {ResolvedStep[]}
|
|
49
|
+
*/
|
|
50
|
+
export function resolveSteps(steps) {
|
|
51
|
+
let searchWasPerformed = false;
|
|
52
|
+
return steps.map((step) => {
|
|
53
|
+
/** @type {ResolvedStepName} */
|
|
54
|
+
let name;
|
|
55
|
+
if (step.name === 'searching') {
|
|
56
|
+
searchWasPerformed = true;
|
|
57
|
+
name = 'searching';
|
|
58
|
+
} else if (step.name === 'answering') {
|
|
59
|
+
name = 'answering';
|
|
60
|
+
} else {
|
|
61
|
+
name = searchWasPerformed
|
|
62
|
+
? 'thinking-after-search'
|
|
63
|
+
: 'thinking-before-search';
|
|
64
|
+
}
|
|
65
|
+
return {name, status: step.status};
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The `QuanticGeneratedAnswerStreamOfThought` component displays a timeline of agent reasoning
|
|
71
|
+
* steps during answer generation, showing real-time progress and completion status.
|
|
72
|
+
* @category Internal
|
|
73
|
+
* @example
|
|
74
|
+
* <c-quantic-generated-answer-stream-of-thought agent-steps={agentSteps} is-streaming={isStreaming}></c-quantic-generated-answer-stream-of-thought>
|
|
75
|
+
*/
|
|
76
|
+
export default class QuanticGeneratedAnswerStreamOfThought extends LightningElement {
|
|
77
|
+
/**
|
|
78
|
+
* Array of raw generation steps from the headless engine state.
|
|
79
|
+
* @api
|
|
80
|
+
* @type {GenerationStep[]}
|
|
81
|
+
* @default []
|
|
82
|
+
*/
|
|
83
|
+
@api agentSteps = [];
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Whether answer generation is currently active.
|
|
87
|
+
* @api
|
|
88
|
+
* @type {boolean}
|
|
89
|
+
* @default false
|
|
90
|
+
*/
|
|
91
|
+
@api
|
|
92
|
+
get isStreaming() {
|
|
93
|
+
return this._isStreaming;
|
|
94
|
+
}
|
|
95
|
+
set isStreaming(value) {
|
|
96
|
+
this._isStreaming = value;
|
|
97
|
+
this._expanded = value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** @type {boolean} */
|
|
101
|
+
_isStreaming = false;
|
|
102
|
+
/** @type {boolean} */
|
|
103
|
+
_expanded = true;
|
|
104
|
+
|
|
105
|
+
labels = {
|
|
106
|
+
collapse: collapseButton,
|
|
107
|
+
loading: loadingLabel,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
handleToggle = () => {
|
|
111
|
+
this._expanded = !this._expanded;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** @returns {Array<{key: number, isActive: boolean, label: string}>} */
|
|
115
|
+
get stepsToDisplay() {
|
|
116
|
+
return this.steps.map((step, index) => {
|
|
117
|
+
const labelKey = STEP_LABEL_KEYS[step.name][step.status];
|
|
118
|
+
return {
|
|
119
|
+
key: index,
|
|
120
|
+
name: step.name,
|
|
121
|
+
isActive: step.status === 'active',
|
|
122
|
+
label: labelKey,
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** @returns {ResolvedStep[]} */
|
|
128
|
+
get steps() {
|
|
129
|
+
return resolveSteps(this.agentSteps ?? []);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** @returns {boolean} */
|
|
133
|
+
get hasSteps() {
|
|
134
|
+
return this.steps.length > 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** @returns {boolean} */
|
|
138
|
+
get isCollapsible() {
|
|
139
|
+
return this.steps.length > 1;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** @returns {boolean} */
|
|
143
|
+
get shouldShowCollapsedSummary() {
|
|
144
|
+
return !this._isStreaming && !this._expanded && this.isCollapsible;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** @returns {boolean} */
|
|
148
|
+
get shouldShowCollapseButton() {
|
|
149
|
+
return !this._isStreaming && this._expanded && this.isCollapsible;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** @returns {string} */
|
|
153
|
+
get collapsedSummaryLabel() {
|
|
154
|
+
const lastStep = this.steps[this.steps.length - 1];
|
|
155
|
+
const labelKey = STEP_LABEL_KEYS[lastStep.name].completed;
|
|
156
|
+
return labelKey;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
render() {
|
|
160
|
+
if (this.shouldShowCollapsedSummary) return collapsedSummaryTemplate;
|
|
161
|
+
return streamOfThoughtTemplate;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import '../quanticGeneratedAnswerStreamOfThought.css';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
data-testid="collapsed-summary"
|
|
4
|
+
class="stream-of-thought__collapsed-summary slds-grid slds-grid_vertical-align-center slds-p-around_none slds-m-vertical_xxx-small"
|
|
5
|
+
type="button"
|
|
6
|
+
aria-expanded={_expanded}
|
|
7
|
+
onclick={handleToggle}
|
|
8
|
+
>
|
|
9
|
+
<span
|
|
10
|
+
class="slds-m-right_x-small slds-grid slds-grid_align-center"
|
|
11
|
+
data-testid="checkmark"
|
|
12
|
+
>
|
|
13
|
+
<lightning-icon
|
|
14
|
+
icon-name="utility:check"
|
|
15
|
+
size="xx-small"
|
|
16
|
+
variant="success"
|
|
17
|
+
aria-hidden="true"
|
|
18
|
+
></lightning-icon>
|
|
19
|
+
</span>
|
|
20
|
+
<span
|
|
21
|
+
class="slds-text-body_small slds-text-color_weak slds-m-right_x-small"
|
|
22
|
+
data-testid="collapsed-summary-label"
|
|
23
|
+
>{collapsedSummaryLabel}</span
|
|
24
|
+
>
|
|
25
|
+
<lightning-icon
|
|
26
|
+
data-testid="chevron-down"
|
|
27
|
+
icon-name="utility:chevrondown"
|
|
28
|
+
size="xx-small"
|
|
29
|
+
aria-hidden="true"
|
|
30
|
+
></lightning-icon>
|
|
31
|
+
</button>
|
|
32
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import '../quanticGeneratedAnswerStreamOfThought.css';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<template lwc:if={hasSteps}>
|
|
3
|
+
<div class="slds-grid slds-grid_vertical">
|
|
4
|
+
<template for:each={stepsToDisplay} for:item="step">
|
|
5
|
+
<div
|
|
6
|
+
key={step.key}
|
|
7
|
+
data-testid="step-item"
|
|
8
|
+
data-step-name={step.name}
|
|
9
|
+
class="slds-grid slds-grid_vertical-align-center slds-m-vertical_xxx-small"
|
|
10
|
+
>
|
|
11
|
+
<template lwc:if={step.isActive}>
|
|
12
|
+
<span
|
|
13
|
+
class="slds-is-relative slds-m-right_x-small slds-grid slds-grid_align-center stream-of-thought__spinner-container"
|
|
14
|
+
aria-hidden="true"
|
|
15
|
+
data-testid="spinner"
|
|
16
|
+
>
|
|
17
|
+
<lightning-spinner
|
|
18
|
+
alternative-text={labels.loading}
|
|
19
|
+
size="xx-small"
|
|
20
|
+
></lightning-spinner>
|
|
21
|
+
</span>
|
|
22
|
+
</template>
|
|
23
|
+
<template lwc:else>
|
|
24
|
+
<span
|
|
25
|
+
class="slds-m-right_x-small slds-grid slds-grid_align-center"
|
|
26
|
+
data-testid="checkmark"
|
|
27
|
+
>
|
|
28
|
+
<lightning-icon
|
|
29
|
+
icon-name="utility:check"
|
|
30
|
+
size="xx-small"
|
|
31
|
+
variant="success"
|
|
32
|
+
aria-hidden="true"
|
|
33
|
+
></lightning-icon>
|
|
34
|
+
</span>
|
|
35
|
+
</template>
|
|
36
|
+
<span
|
|
37
|
+
class="slds-text-body_small slds-text-color_weak"
|
|
38
|
+
data-testid="step-label"
|
|
39
|
+
>{step.label}</span
|
|
40
|
+
>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
43
|
+
<template lwc:if={shouldShowCollapseButton}>
|
|
44
|
+
<button
|
|
45
|
+
data-testid="collapse-button"
|
|
46
|
+
class="stream-of-thought__collapse-button slds-grid slds-grid_vertical-align-center slds-p-around_none"
|
|
47
|
+
type="button"
|
|
48
|
+
aria-expanded={_expanded}
|
|
49
|
+
onclick={handleToggle}
|
|
50
|
+
>
|
|
51
|
+
<span
|
|
52
|
+
class="slds-text-body_small slds-text-color_weak slds-m-right_xx-small"
|
|
53
|
+
>{labels.collapse}</span
|
|
54
|
+
>
|
|
55
|
+
<lightning-icon
|
|
56
|
+
data-testid="chevron-up"
|
|
57
|
+
icon-name="utility:chevronup"
|
|
58
|
+
size="xx-small"
|
|
59
|
+
aria-hidden="true"
|
|
60
|
+
></lightning-icon>
|
|
61
|
+
</button>
|
|
62
|
+
</template>
|
|
63
|
+
</div>
|
|
64
|
+
</template>
|
|
65
|
+
</template>
|