[ACS-8382] Blurring the AI answer section before getting response from backend (#3948)

This commit is contained in:
jacekpluta
2024-07-31 10:24:16 +02:00
committed by Aleksander Sklorz
parent 5bfa638684
commit 26393fee9c
5 changed files with 107 additions and 13 deletions

View File

@@ -18,7 +18,7 @@
<div <div
class="aca-search-ai-response-container" class="aca-search-ai-response-container"
[class.aca-search-ai-response-container-error]="hasAnsweringError"> [class.aca-search-ai-response-container-error]="hasAnsweringError">
<ng-container *ngIf="!loading"> <ng-container *ngIf="!loading else skeleton">
<div <div
class="aca-search-ai-response-container-body" class="aca-search-ai-response-container-body"
*ngIf="!hasAnsweringError"> *ngIf="!hasAnsweringError">
@@ -105,3 +105,9 @@
*ngIf="hasError"> *ngIf="hasError">
</adf-empty-content> </adf-empty-content>
</aca-page-layout> </aca-page-layout>
<ng-template #skeleton>
<div class="adf-skeleton"></div>
<div class="adf-skeleton"></div>
<div class="adf-skeleton adf-skeleton-half"></div>
</ng-template>

View File

@@ -29,16 +29,58 @@
} }
.aca-search-ai-response-container { .aca-search-ai-response-container {
padding: 18px 20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border: 1px solid var(--adf-card-view-border-color); border: 1px solid var(--adf-card-view-border-color);
border-radius: 12px; border-radius: 12px;
margin: 16px 0 75px; margin: 16px 0 75px;
padding: 14px 40px 36px 35px;
&-references-container-header {
padding-left: 8px;
}
.adf-skeleton {
position: relative;
background-image: linear-gradient(
to left,
var(--theme-light-grey-1-color) 0%,
var(--theme-light-grey-2-color) 20%,
var(--theme-light-grey-3-color) 40%,
var(--theme-light-grey-1-color) 100%
);
background-size: 200%;
display: inline-block;
height: 1em;
overflow: hidden;
width: 100%;
margin-bottom: 0.5rem;
border-radius: 0.25rem;
&-half {
width: 50%;
margin-bottom: 8px;
}
&::after {
position: absolute;
inset: 0;
transform: translateX(-100%);
background-image: linear-gradient(90deg, rgba(white, 0) 0, rgba(white, 0.2) 20%, rgba(white, 0.5) 60%, rgba(white, 0));
animation: shimmer 2s infinite;
content: '';
}
@keyframes shimmer {
100% {
transform: translateX(100%);
}
}
}
&-error { &-error {
border-color: var(--adf-error-color); border-color: var(--adf-error-color);
padding: 21px 19px 27px 18px; padding: 32px 18px;
&-message { &-message {
display: flex; display: flex;
@@ -58,12 +100,8 @@
} }
&-body { &-body {
width: 100%;
&-response { &-response {
margin: 17px 0; margin-bottom: 17px;
padding-left: 6px;
padding-right: 5px;
overflow-wrap: break-word; overflow-wrap: break-word;
&-action { &-action {

View File

@@ -25,11 +25,13 @@
import { TestBed, ComponentFixture } from '@angular/core/testing'; import { TestBed, ComponentFixture } from '@angular/core/testing';
import { SearchAiResultsComponent } from './search-ai-results.component'; import { SearchAiResultsComponent } from './search-ai-results.component';
import { ActivatedRoute, Params } from '@angular/router'; import { ActivatedRoute, Params } from '@angular/router';
import { Subject } from 'rxjs'; import { of, Subject } from 'rxjs';
import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatSnackBarModule } from '@angular/material/snack-bar';
import { UserPreferencesService } from '@alfresco/adf-core'; import { UserPreferencesService } from '@alfresco/adf-core';
import { AppTestingModule } from '../../../../testing/app-testing.module';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { AppTestingModule } from '../../../../testing/app-testing.module';
import { MatIconTestingModule } from '@angular/material/icon/testing';
import { SearchAiService } from '@alfresco/adf-content-services';
describe('SearchAiResultsComponent', () => { describe('SearchAiResultsComponent', () => {
const knowledgeRetrievalNodes = '{"isEmpty":"false","nodes":[{"entry":{"id": "someId","isFolder":"true"}}]}'; const knowledgeRetrievalNodes = '{"isEmpty":"false","nodes":[{"entry":{"id": "someId","isFolder":"true"}}]}';
@@ -40,7 +42,7 @@ describe('SearchAiResultsComponent', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [AppTestingModule, SearchAiResultsComponent, MatSnackBarModule, MatDialogModule], imports: [AppTestingModule, SearchAiResultsComponent, MatSnackBarModule, MatDialogModule, MatIconTestingModule],
providers: [ providers: [
{ {
provide: ActivatedRoute, provide: ActivatedRoute,
@@ -99,4 +101,47 @@ describe('SearchAiResultsComponent', () => {
expect(component.hasError).toBeTrue(); expect(component.hasError).toBeTrue();
}); });
}); });
describe('skeleton loader', () => {
let searchAiService: SearchAiService;
const getSkeletonElementsLength = (): number => {
return fixture.nativeElement.querySelectorAll('.adf-skeleton').length;
};
beforeEach(() => {
searchAiService = TestBed.inject(SearchAiService);
spyOn(userPreferencesService, 'get').and.returnValue(knowledgeRetrievalNodes);
});
it('should display skeleton when loading is true', () => {
mockQueryParams.next({ query: 'test', agentId: 'agentId1' });
component.performAiSearch();
fixture.detectChanges();
expect(component.loading).toBeTrue();
expect(getSkeletonElementsLength()).toBe(3);
});
it('should not display skeleton when loading is false', () => {
mockQueryParams.next({ query: 'test', agentId: 'agentId1' });
spyOn(searchAiService, 'ask').and.returnValue(of({ question: 'test', questionId: 'testId', restrictionQuery: '' }));
spyOn(searchAiService, 'getAnswer').and.returnValue(
of({
list: {
entries: [],
pagination: { hasMoreItems: false, maxItems: 0, totalItems: 0, skipCount: 0 }
}
})
);
component.performAiSearch();
fixture.detectChanges();
expect(component.loading).toBeFalse();
expect(getSkeletonElementsLength()).toBe(0);
});
});
}); });

View File

@@ -130,7 +130,6 @@ export class SearchAiResultsComponent extends PageComponent implements OnInit, O
this._agentId = params.agentId; this._agentId = params.agentId;
this._searchQuery = params.query ? decodeURIComponent(params.query) : ''; this._searchQuery = params.query ? decodeURIComponent(params.query) : '';
this._selectedNodesState = JSON.parse(this.userPreferencesService.get('knowledgeRetrievalNodes')); this._selectedNodesState = JSON.parse(this.userPreferencesService.get('knowledgeRetrievalNodes'));
if (!this.searchQuery || !this._selectedNodesState?.nodes?.length || !this.agentId) { if (!this.searchQuery || !this._selectedNodesState?.nodes?.length || !this.agentId) {
this._hasError = true; this._hasError = true;
return; return;

View File

@@ -49,6 +49,9 @@ $search-highlight-background-color: #ffd180;
$info-snackbar-background: #1f74db; $info-snackbar-background: #1f74db;
$text-light-color: rgba(33, 35, 40, 0.7); $text-light-color: rgba(33, 35, 40, 0.7);
$card-background-grey-color: rgb(248, 248, 248); $card-background-grey-color: rgb(248, 248, 248);
$light-grey-1: #d5d5d5;
$light-grey-2: #d9d9d9;
$light-grey-3: #dedede;
// CSS Variables // CSS Variables
$defaults: ( $defaults: (
@@ -102,7 +105,10 @@ $defaults: (
--theme-secondary-text: $secondary-text, --theme-secondary-text: $secondary-text,
--theme-search-highlight-background-color: $search-highlight-background-color, --theme-search-highlight-background-color: $search-highlight-background-color,
--theme-text-light-color: $text-light-color, --theme-text-light-color: $text-light-color,
--theme-card-background-grey-color: $card-background-grey-color --theme-card-background-grey-color: $card-background-grey-color,
--theme-light-grey-1-color: $light-grey-1,
--theme-light-grey-2-color: $light-grey-2,
--theme-light-grey-3-color: $light-grey-3
); );
// propagates SCSS variables into the CSS variables scope // propagates SCSS variables into the CSS variables scope