[ACS-8210] Agent basic details popup

This commit is contained in:
Aleksander Sklorz
2024-07-10 13:32:57 +02:00
committed by Jacek Pluta
parent 17a7697e5e
commit 88dd1e76ed
10 changed files with 185 additions and 41 deletions

View File

@@ -607,6 +607,7 @@
"KNOWLEDGE_RETRIEVAL": {
"SEARCH": {
"RESULTS_PAGE": {
"AGENT": "Agent",
"QUERY_INPUT_PLACEHOLDER": "Would you like to ask anything else?",
"REFERENCED_DOCUMENTS_HEADER": "Referenced documents",
"REGENERATION_BUTTON_LABEL": "Regenerate",

View File

@@ -26,7 +26,9 @@
[value]="agent">
<div class="aca-agents-button-menu-list-agent-content">
<adf-avatar [initials]="initialsByAgentId[agent.id]"></adf-avatar>
<span class="aca-agents-button-menu-list-agent-content-name">
{{ agent.name }}
</span>
</div>
</mat-list-option>
</mat-selection-list>

View File

@@ -45,7 +45,14 @@ aca-agents-button.aca-agents-button {
&-content {
display: flex;
align-items: baseline;
align-items: center;
&-name {
width: 120px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
adf-avatar {

View File

@@ -33,7 +33,7 @@ import { takeUntil } from 'rxjs/operators';
import { MatMenuModule } from '@angular/material/menu';
import { MatListModule, MatSelectionListChange } from '@angular/material/list';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { Agent } from '@alfresco/js-api';
import { AgentWithAvatar } from '@alfresco/js-api';
import { AgentService, SearchAiService } from '@alfresco/adf-content-services';
@Component({
@@ -50,12 +50,12 @@ export class AgentsButtonComponent implements OnInit, OnDestroy {
data: { trigger: string };
private selectedNodesState: SelectionState;
private _agents: Agent[] = [];
private _agents: AgentWithAvatar[] = [];
private onDestroy$ = new Subject<void>();
private _disabled = true;
private _initialsByAgentId: { [key: string]: string } = {};
get agents(): Agent[] {
get agents(): AgentWithAvatar[] {
return this._agents;
}
@@ -71,8 +71,8 @@ export class AgentsButtonComponent implements OnInit, OnDestroy {
private store: Store<AppStore>,
private notificationService: NotificationService,
private searchAiService: SearchAiService,
private translateService: TranslateService,
private agentService: AgentService
private agentService: AgentService,
private translateService: TranslateService
) {}
ngOnInit(): void {
@@ -82,12 +82,13 @@ export class AgentsButtonComponent implements OnInit, OnDestroy {
.subscribe((selection) => {
this.selectedNodesState = selection;
});
this.agentService
.getAgents()
.pipe(takeUntil(this.onDestroy$))
.subscribe(
(paging) => {
this._agents = paging.list.entries.map((agentEntry) => agentEntry.entry);
(agents: AgentWithAvatar[]) => {
this._agents = agents;
if (this.agents.length) {
this._initialsByAgentId = this.agents.reduce((initials, agent) => {
const words = agent.name.split(' ').filter((word) => !word.match(/[^a-zA-Z]+/g));

View File

@@ -8,7 +8,7 @@
[initials]="initialsByAgentId[agentControl.value.id]"
size="26px">
</adf-avatar>
{{ agentControl.value.name }}
{{ agentControl.value?.name }}
</mat-select-trigger>
<mat-option
*ngFor="let agent of agents"
@@ -16,7 +16,7 @@
class="aca-search-ai-input-agent-select-options-option">
<div class="aca-search-ai-input-agent-select-options-option-content">
<adf-avatar [initials]="initialsByAgentId[agent.id]"></adf-avatar>
{{ agent.name }}
<span class="aca-search-ai-input-agent-select-options-option-content-text">{{ agent.name }}</span>
</div>
</mat-option>
</mat-select>

View File

@@ -49,7 +49,7 @@ aca-search-ai-input {
&-displayed-value {
display: flex;
align-items: baseline;
align-items: center;
}
adf-avatar {
@@ -71,6 +71,13 @@ aca-search-ai-input {
&-content {
display: flex;
align-items: baseline;
&-text {
width: 120px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
adf-avatar {

View File

@@ -38,7 +38,7 @@ import { AiSearchByTermPayload, AppStore, getAppSelection, SearchByTermAiAction
import { takeUntil } from 'rxjs/operators';
import { SelectionState } from '@alfresco/adf-extensions';
import { MatSelectModule } from '@angular/material/select';
import { Agent } from '@alfresco/js-api';
import { AgentWithAvatar } from '@alfresco/js-api';
import { AgentService, SearchAiService } from '@alfresco/adf-content-services';
@Component({
@@ -75,18 +75,18 @@ export class SearchAiInputComponent implements OnInit, OnDestroy {
private readonly storedNodesKey = 'knowledgeRetrievalNodes';
private _agentControl = new FormControl<Agent>(null);
private _agents: Agent[] = [];
private _agentControl = new FormControl<AgentWithAvatar>(null);
private _agents: AgentWithAvatar[] = [];
private onDestroy$ = new Subject<void>();
private selectedNodesState: SelectionState;
private _queryControl = new FormControl('');
private _initialsByAgentId: { [key: string]: string } = {};
get agentControl(): FormControl<Agent> {
get agentControl(): FormControl<AgentWithAvatar> {
return this._agentControl;
}
get agents(): Agent[] {
get agents(): AgentWithAvatar[] {
return this._agents;
}
@@ -103,8 +103,8 @@ export class SearchAiInputComponent implements OnInit, OnDestroy {
private searchAiService: SearchAiService,
private notificationService: NotificationService,
private agentService: AgentService,
private translateService: TranslateService,
private userPreferencesService: UserPreferencesService
private userPreferencesService: UserPreferencesService,
private translateService: TranslateService
) {}
ngOnInit(): void {
@@ -118,13 +118,14 @@ export class SearchAiInputComponent implements OnInit, OnDestroy {
} else {
this.selectedNodesState = JSON.parse(this.userPreferencesService.get(this.storedNodesKey));
}
this.agentService
.getAgents()
.pipe(takeUntil(this.onDestroy$))
.subscribe(
(paging) => {
this._agents = paging.list.entries.map((agentEntry) => agentEntry.entry);
this.agentControl.setValue(this.agents.find((agent) => agent.id === this.agentId));
(agents: AgentWithAvatar[]) => {
this._agents = agents;
this.agentControl.setValue(agents.find((agent) => agent.id === this.agentId));
this._initialsByAgentId = this.agents.reduce((initials, agent) => {
const words = agent.name.split(' ').filter((word) => !word.match(/[^a-zA-Z]+/g));
initials[agent.id] = `${words[0][0]}${words[1][0] || ''}`;

View File

@@ -18,6 +18,26 @@
<div
class="aca-search-ai-response-container"
[class.aca-search-ai-response-container-error]="hasAnsweringError">
<div class="aca-search-ai-response-container-body-agent">
<adf-avatar
src="{{ agent?.avatar }}"
size="26px">
</adf-avatar>
<span class="aca-search-ai-response-container-body-agent-name">{{ agent?.name }}</span>
<mat-card class="aca-search-ai-response-container-body-agent-hover-card">
<mat-card-title class="aca-search-ai-response-container-body-agent-hover-card-title">
<adf-avatar
src="{{ agent?.avatar }}"
size="26px">
</adf-avatar>
<span [matTooltipPosition]="'right'" matTooltip="{{ agent?.name }}">{{ agent?.name }}</span>
</mat-card-title>
<mat-card-content class="aca-search-ai-response-container-body-agent-hover-card-content">
<div>{{ agent?.description }}</div>
</mat-card-content>
</mat-card>
</div>
<mat-divider class="aca-search-ai-response-container-body-divider"></mat-divider>
<ng-container *ngIf="!loading">
<div
class="aca-search-ai-response-container-body"

View File

@@ -52,8 +52,89 @@
&-body {
width: 100%;
&-agent {
display: flex;
align-items: center;
position: relative;
height: 32px;
padding: 8px 0 4px 4px;
width: fit-content;
img {
margin-right: 6px;
border-radius: 50%;
height: 32px;
width: 32px;
min-width: 32px;
min-height: 32px;
display: inline-block;
}
&-name {
font-weight: 500;
}
&-hover-card {
display: none;
width: 315px;
height: fit-content;
margin-top: 20px;
position: absolute;
top: 16px;
left: -16px;
z-index: 1;
border-radius: 12px;
&:hover {
display: block;
}
&-title:has(img) {
display: flex;
align-items: center;
font-size: 20px;
font-weight: 500;
img {
height: 50px;
width: 50px;
min-width: 50px;
min-height: 50px;
margin-right: 12px;
}
span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 8px;
}
}
&-content {
padding-top: 4px;
padding-right: 8px;
display: flex;
color: var(--theme-heading-color);
max-height: 150px;
overflow-y: auto;
cursor: default;
text-align: justify;
text-justify: inter-word;
}
}
&:hover {
cursor: pointer;
.aca-search-ai-response-container-body-agent-hover-card {
display: block;
}
}
}
&-response {
margin-bottom: 17px;
margin: 17px 0;
padding-left: 6px;
padding-right: 5px;
overflow-wrap: break-word;
@@ -85,7 +166,7 @@
padding-left: 8px;
&-header {
margin-top: 8px;
margin-top: 16px;
color: var(--theme-text-light-color);
font-weight: 400;
margin-bottom: 3px;

View File

@@ -23,20 +23,29 @@
*/
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { ActivatedRoute } from '@angular/router';
import { PageComponent, PageLayoutComponent, ToolbarActionComponent, ToolbarComponent } from '@alfresco/aca-shared';
import { finalize, switchMap, takeUntil } from 'rxjs/operators';
import { ClipboardService, EmptyContentComponent, ThumbnailService, ToolbarModule, UserPreferencesService } from '@alfresco/adf-core';
import { AiAnswer, Node } from '@alfresco/js-api';
import {
AvatarComponent,
ClipboardService,
EmptyContentComponent,
ThumbnailService,
ToolbarModule,
UserPreferencesService
} from '@alfresco/adf-core';
import { AgentWithAvatar, AiAnswer, Node } from '@alfresco/js-api';
import { CommonModule } from '@angular/common';
import { SearchAiInputContainerComponent } from '../search-ai-input-container/search-ai-input-container.component';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { NodesApiService } from '@alfresco/adf-content-services';
import { forkJoin } from 'rxjs';
import { AgentService, NodesApiService, SearchAiService } from '@alfresco/adf-content-services';
import { forkJoin, of } from 'rxjs';
import { SelectionState } from '@alfresco/adf-extensions';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatListModule } from '@angular/material/list';
import { MatCardModule } from '@angular/material/card';
import { MatTooltipModule } from '@angular/material/tooltip';
@Component({
standalone: true,
@@ -51,7 +60,10 @@ import { MatListModule } from '@angular/material/list';
MatIconModule,
MatButtonModule,
MatListModule,
EmptyContentComponent
EmptyContentComponent,
MatCardModule,
AvatarComponent,
MatTooltipModule
],
selector: 'aca-search-ai-results',
templateUrl: './search-ai-results.component.html',
@@ -70,6 +82,8 @@ export class SearchAiResultsComponent extends PageComponent implements OnInit, O
private _searchQuery = '';
private _queryAnswer: AiAnswer;
agent: AgentWithAvatar;
get agentId(): string {
return this._agentId;
}
@@ -108,21 +122,31 @@ export class SearchAiResultsComponent extends PageComponent implements OnInit, O
private thumbnailService: ThumbnailService,
private nodesApiService: NodesApiService,
private userPreferencesService: UserPreferencesService,
private translateService: TranslateService
private translateService: TranslateService,
private agentService: AgentService,
protected searchAiService: SearchAiService
) {
super();
}
ngOnInit(): void {
this.route.queryParams.pipe(takeUntil(this.onDestroy$)).subscribe((params: Params) => {
this.route.queryParams
.pipe(
switchMap((params) => {
this._agentId = params.agentId;
this._searchQuery = params.query ? decodeURIComponent(params.query) : '';
this.selectedNodesState = JSON.parse(this.userPreferencesService.get('knowledgeRetrievalNodes'));
if (!this.searchQuery || !this.selectedNodesState?.nodes?.length || !this.agentId) {
this._hasError = true;
return;
return of([]);
}
this.performAiSearch();
return this.agentService.agentsList$;
}),
takeUntil(this.onDestroy$)
)
.subscribe((agents: AgentWithAvatar[]) => {
this.agent = agents.find((agent) => agent.id === this._agentId);
});
super.ngOnInit();
}