Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a45b3e7
added labels for steps
SimonMilord Feb 24, 2026
af8ce5d
added render agent generation steps component
SimonMilord Feb 24, 2026
2c4d0cd
renamed animation
SimonMilord Feb 24, 2026
bb2591f
simplified logic
SimonMilord Feb 24, 2026
2344147
improvements;
SimonMilord Feb 24, 2026
f257323
added logic to display the steps minimum the length of the animation
SimonMilord Feb 25, 2026
14c9d73
improvements to tests
SimonMilord Feb 25, 2026
43163f3
refactor build steps
SimonMilord Feb 25, 2026
bdd440d
marked component as internal
SimonMilord Feb 25, 2026
fc70014
copilot feedback
SimonMilord Feb 26, 2026
18a382b
Merge branch 'main' into SFINT-6647
SimonMilord Feb 26, 2026
c1b4bc1
improvements
SimonMilord Mar 2, 2026
87747b7
Merge branch 'SFINT-6647' of https://github.com/coveo/ui-kit into SFI…
SimonMilord Mar 2, 2026
820af09
Merge branch 'main' into SFINT-6647
SimonMilord Mar 2, 2026
e0a545a
updated the headless imports
SimonMilord Mar 2, 2026
3d619c2
simplified the component to handle less scenarios and edge cases
SimonMilord Mar 2, 2026
d18988a
integrated the steps component inside the atomic-answer-content
SimonMilord Mar 2, 2026
ec2df20
improvements, made animation a bit smoother, fixed alignment of title…
SimonMilord Mar 3, 2026
cd1d76a
Merge branch 'main' into SFINT-6647
SimonMilord Mar 3, 2026
e697f1d
fix tests
SimonMilord Mar 4, 2026
b8b974b
Merge branch 'SFINT-6647' of https://github.com/coveo/ui-kit into SFI…
SimonMilord Mar 4, 2026
7b833c8
Merge branch 'main' into SFINT-6647
SimonMilord Mar 4, 2026
0c66bab
fix tests
SimonMilord Mar 4, 2026
4b5e519
Merge branch 'SFINT-6647' of https://github.com/coveo/ui-kit into SFI…
SimonMilord Mar 4, 2026
4e4440e
Merge branch 'main' into SFINT-6647
SimonMilord Mar 5, 2026
72f0642
etienne feedback
SimonMilord Mar 5, 2026
9384865
Merge branch 'SFINT-6647' of https://github.com/coveo/ui-kit into SFI…
SimonMilord Mar 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {beforeAll, beforeEach, describe, expect, it, vi} from 'vitest';
import {renderInAtomicSearchInterface} from '@/vitest-utils/testing-helpers/fixtures/atomic/search/atomic-search-interface-fixture';
import {createTestI18n} from '@/vitest-utils/testing-helpers/i18n-utils';
import {renderGeneratedContentContainer} from '../generated-content-container';
import {renderAgentGenerationSteps} from '../render-agent-generation-steps';
import {renderFeedbackAndCopyButtons} from '../render-feedback-and-copy-buttons';
import {renderSourceCitations} from '../source-citations';
import type {
Expand All @@ -19,6 +20,9 @@ vi.mock('../generated-content-container', () => ({
() => (slot?: unknown) => html`${slot ?? ''}`
),
}));
vi.mock('../render-agent-generation-steps', () => ({
renderAgentGenerationSteps: vi.fn(() => html``),
}));
vi.mock('../source-citations', () => ({
renderSourceCitations: vi.fn(() => (slot?: unknown) => html`${slot ?? ''}`),
}));
Expand All @@ -33,6 +37,7 @@ describe('atomic-answer-content', () => {
answerId: 'answer-id',
answerContentFormat: 'text/markdown',
citations: [],
generationSteps: [],
isStreaming: false,
liked: false,
disliked: false,
Expand Down Expand Up @@ -98,17 +103,99 @@ describe('atomic-answer-content', () => {
});
});

it('should render nothing when the answer is missing', async () => {
it('should render nothing when the answer id is missing', async () => {
await renderComponent({
generatedAnswer: {
answer: undefined,
answerId: undefined,
},
});

expect(renderAgentGenerationSteps).not.toHaveBeenCalled();
expect(renderGeneratedContentContainer).not.toHaveBeenCalled();
expect(renderFeedbackAndCopyButtons).not.toHaveBeenCalled();
Comment thread
SimonMilord marked this conversation as resolved.
});

it('should call renderAgentGenerationSteps with an empty list when generation steps are missing', async () => {
await renderComponent({
generatedAnswer: {
generationSteps: undefined,
},
});

expect(renderAgentGenerationSteps).toHaveBeenCalledWith({
props: expect.objectContaining({
i18n,
agentSteps: [],
isStreaming: false,
}),
});
});

it('should call renderAgentGenerationSteps with isStreaming false when not streaming', async () => {
const generationSteps = [
{
name: 'searching' as const,
status: 'active' as const,
startedAt: 1,
},
];

await renderComponent({
generatedAnswer: {
isStreaming: false,
generationSteps,
},
});

expect(renderAgentGenerationSteps).toHaveBeenCalledWith({
props: expect.objectContaining({
i18n,
agentSteps: generationSteps,
isStreaming: false,
}),
});
});

it('should render generation-step state when streaming without answer content', async () => {
await renderComponent({
generatedAnswer: {
answer: undefined,
answerId: 'answer-id',
isStreaming: true,
generationSteps: [
{
name: 'thinking',
status: 'active',
startedAt: 1,
},
],
},
});

expect(renderAgentGenerationSteps).toHaveBeenCalledWith({
Comment thread
SimonMilord marked this conversation as resolved.
Comment thread
SimonMilord marked this conversation as resolved.
Comment thread
SimonMilord marked this conversation as resolved.
props: expect.objectContaining({
i18n,
Comment thread
SimonMilord marked this conversation as resolved.
isStreaming: true,
agentSteps: [
{
name: 'thinking',
status: 'active',
startedAt: 1,
},
],
}),
});
expect(renderGeneratedContentContainer).toHaveBeenCalledWith({
props: expect.objectContaining({
answer: undefined,
answerContentFormat: 'text/markdown',
isStreaming: true,
}),
});
expect(renderFeedbackAndCopyButtons).not.toHaveBeenCalled();
});

it('should render the error template when the generated answer has an error', async () => {
const {element} = await renderComponent({
generatedAnswer: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import type {
import type {i18n} from 'i18next';
import {html, LitElement, type TemplateResult} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
import {when} from 'lit/directives/when.js';
import atomicGeneratedAnswerStyles from '@/src/components/search/atomic-generated-answer/atomic-generated-answer.tw.css.js';
import {withTailwindStyles} from '@/src/decorators/with-tailwind-styles';
import {renderGeneratedContentContainer} from '../generated-content-container';
import {renderAgentGenerationSteps} from '../render-agent-generation-steps';
import {renderFeedbackAndCopyButtons} from '../render-feedback-and-copy-buttons';
import {renderSourceCitations} from '../source-citations';

Expand Down Expand Up @@ -86,6 +88,7 @@ export class AtomicAnswerContent extends LitElement {
answer,
answerContentFormat,
isStreaming,
generationSteps,
citations = [],
answerId,
error,
Expand All @@ -95,51 +98,65 @@ export class AtomicAnswerContent extends LitElement {
return this.renderError();
}

if (!answer || !answerId) {
if (!answerId) {
return html``;
}

const shouldRenderFeedbackAndCopyButtons = Boolean(answer) && !isStreaming;

return html`
<div>
${renderAgentGenerationSteps({
props: {
i18n: this.i18n,
agentSteps: generationSteps ?? [],
isStreaming: Boolean(isStreaming),
},
})}
Comment thread
SimonMilord marked this conversation as resolved.
<div>
${renderGeneratedContentContainer({
props: {
answer,
answerContentFormat,
isStreaming: Boolean(isStreaming),
},
})(
html`
${renderSourceCitations({
props: {
label: this.i18n.t('citations'),
isVisible: citations.length > 0,
},
})(html`${this.renderCitations(citations)}`)}
`
)}
})(html`
${renderSourceCitations({
props: {
label: this.i18n.t('citations'),
isVisible: citations.length > 0,
Comment thread
SimonMilord marked this conversation as resolved.
},
})(html`${this.renderCitations(citations)}`)}
`)}
</div>
${when(shouldRenderFeedbackAndCopyButtons, () =>
this.renderFeedbackAndCopyButtons(answerId)
)}
</div>
`;
}

<div class="mt-4" part="feedback-and-copy-buttons">
${renderFeedbackAndCopyButtons({
props: {
i18n: this.i18n,
generatedAnswerActionsState: {
liked: this.generatedAnswer.liked,
disliked: this.generatedAnswer.disliked,
isStreaming: this.generatedAnswer.isStreaming,
isLoading: this.generatedAnswer.isLoading,
answer: this.generatedAnswer.answer,
},
copied: this.copyState === 'success',
copyError: this.copyState === 'error',
getCopyToClipboardTooltip: () => this.getCopyToClipboardTooltip(),
onClickLike: () => this.onClickLike(answerId),
onClickDislike: () => this.onClickDislike(answerId),
onCopyToClipboard: () => this.copyToClipboard(),
private renderFeedbackAndCopyButtons(answerId: string) {
return html`
<div class="mt-4" part="feedback-and-copy-buttons">
${renderFeedbackAndCopyButtons({
props: {
i18n: this.i18n,
generatedAnswerActionsState: {
liked: this.generatedAnswer.liked,
disliked: this.generatedAnswer.disliked,
isStreaming: this.generatedAnswer.isStreaming,
isLoading: this.generatedAnswer.isLoading,
answer: this.generatedAnswer.answer,
},
})}
</div>
copied: this.copyState === 'success',
copyError: this.copyState === 'error',
getCopyToClipboardTooltip: () => this.getCopyToClipboardTooltip(),
onClickLike: () => this.onClickLike(answerId),
onClickDislike: () => this.onClickDislike(answerId),
onCopyToClipboard: () => this.copyToClipboard(),
},
})}
</div>
`;
}
Expand Down
Loading
Loading