mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-09-17 14:21:14 +00:00
[ACS-8202] basic flow getting ai response for one or more selected files (#3936)
* ACS-8202 Added animated icon * ACS-8202 Added search ai input * ACS-8202 Added AI search results page * ACS-8202 Allow to run knowledge retrieval on files inside library, shared, favourites and recent files * ACS-8202 Hide icon when selected more than 100 files or non text files * ACS-8202 Display notification when too many files are selected * ACS-8202 Added agents dropdown * ACS-8202 Styles for AI response * ACS-8202 Applied design changes * ACS-8202 Added query card to Knowledge retrieval page results * ACS-8202 Fixed search collapsing when opened results page * ACS-8202 Changed placeholder in input for results page, wrapping text and scrolling for results page * ACS-8202 Display snackbar with messages when conditions are not met * ACS-8202 Disallow run knowledge retrieval for libraries, leave input when click on x button * ACS-8202 Renaming files * ACS-8202 Trigger ai input by selecting agent instead of clicking on button * ACS-8202 Reverted triggering showing input by selecting option from select * ACS-8202 Display dropdown with agents by clicking on button * ACS-8202 Structural changes - services and agents button component * ACS-8202 Removed part for examples from search page * ACS-8202 Simplified html for search page * ACS-8202 Refactored html and styles for search page, translations for search page * ACS-8202 More html and styles refactoring * ACS-8202 Formatting html * ACS-8202 Removed references to angular material classes * ACS-8202 Added data automation id attributes * ACS-8202 Load agents from backend, formatting html for agents button component and adding data automation ids to that component * ACS-8202 Correction after rebase * ACS-8202 Set agent for input based on selected agent from dropdown for agents button * ACS-8202 Hide agent button for libraries pages and use translations for warnings when clicked on agents button * ACS-8202 Pass agent id to search results page * ACS-8202 Used form control instead of ngmodel for search query * ACS-8202 Moved search ai service and search ai input state to ADF * ACS-8202 Results page ts clean up * ACS-8202 Used ask and getAnswer functions from search ai service * ACS-8202 Cleaning of search ai navigation service * ACS-8202 Small clean ups * ACS-8202 Renamed sources to references * ACS-8202 Fixed asking next question from results page * ACS-8202 Added possibility to use knowledge retrieval from search results page * ACS-8202 Fixed issue with selecting the same agent after previously closing input on search results page * ACS-8202 Disallowed using knowledge retrieval on trash page * ACS-8202 Hide toggling knowledge retrieval for tasks and processes, fixed displaying ask button for favorites page * ACS-8202 Removed redundant image and function * ACS-8202 Renamed breadcrumbTemplate to header * ACS-8202 Removed redundant code, added some comments, made some fields as private * ACS-8202 Display error message on search page * ACS-8202 Accessibility changes * ACS-8202 Small correction * ACS-8202 Addressed comments * ACS-8202 Displayed correct initials * ACS-8202 Removed redundant imports * ACS-8202 Change css value * ACS-8202 Removed icon animation * ACS-8202 Removed icon animation
This commit is contained in:
committed by
Aleksander Sklorz
parent
796a3c7861
commit
067b54b230
@@ -128,6 +128,16 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"icons": [
|
||||
{
|
||||
"id": "adf:three_magic_stars_ai",
|
||||
"value": "./assets/images/three-magic-stars-ai.svg"
|
||||
},
|
||||
{
|
||||
"id": "adf:colored-stars-ai",
|
||||
"value": "./assets/images/colored-stars-ai.svg"
|
||||
}
|
||||
],
|
||||
"create": [
|
||||
{
|
||||
"id": "app.create.folder",
|
||||
@@ -676,6 +686,22 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "app.toolbar.ai.search",
|
||||
"order": 0,
|
||||
"title": "KNOWLEDGE_RETRIEVAL.SEARCH.AGENTS_BUTTON.TITLE",
|
||||
"component": "app.toolbar.ai.agents-button",
|
||||
"type": "custom",
|
||||
"rules": {
|
||||
"visible": "app.selection.displayedKnowledgeRetrievalButton"
|
||||
},
|
||||
"actions": {
|
||||
"click": "app.action.toggle-ai-search-input.execute"
|
||||
},
|
||||
"data": {
|
||||
"trigger": "TOGGLE_AI_SEARCH_INPUT"
|
||||
}
|
||||
}
|
||||
],
|
||||
"contextMenu": [
|
||||
|
@@ -609,5 +609,33 @@
|
||||
"BOOKS-24PX": "file library",
|
||||
"BASELINE-LOCK-24PX": "locked file"
|
||||
}
|
||||
},
|
||||
"KNOWLEDGE_RETRIEVAL": {
|
||||
"SEARCH": {
|
||||
"RESULTS_PAGE": {
|
||||
"QUERY_INPUT_PLACEHOLDER": "Would you like to ask anything else?",
|
||||
"REFERENCED_DOCUMENTS_HEADER": "Referenced documents",
|
||||
"REGENERATION_BUTTON_LABEL": "Regenerate",
|
||||
"COPY_BUTTON_LABEL": "Copy",
|
||||
"LIKE_BUTTON_LABEL": "Like",
|
||||
"DISLIKE_BUTTON_LABEL": "Dislike",
|
||||
"COPY_MESSAGE": "Copied response to clipboard"
|
||||
},
|
||||
"AGENTS_BUTTON": {
|
||||
"LABEL": "Ask Agent",
|
||||
"TITLE": "Knowledge Retrieval"
|
||||
},
|
||||
"SEARCH_INPUT": {
|
||||
"ASK_BUTTON_LABEL": "Ask",
|
||||
"DEFAULT_PLACEHOLDER": "Please ask your question with as much detail as possible...",
|
||||
"HIDE_INPUT": "Hide input"
|
||||
},
|
||||
"ERRORS": {
|
||||
"AGENTS_FETCHING": "Error while fetching agents.",
|
||||
"LOADING_ERROR": "Hmm... something seems to have gone wrong.",
|
||||
"PAGE_NOT_AVAILABLE_ERROR": "Page is not available for these conditions."
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
projects/aca-content/assets/images/colored-stars-ai.svg
Normal file
19
projects/aca-content/assets/images/colored-stars-ai.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_340_107020)">
|
||||
<g clip-path="url(#clip1_340_107020)">
|
||||
<path d="M23 13L21.75 10.25L19 8.99999L21.75 7.74999L23 4.99999L24.25 7.74999L27 8.99999L24.25 10.25L23 13ZM23 27L21.75 24.25L19 23L21.75 21.75L23 19L24.25 21.75L27 23L24.25 24.25L23 27ZM13 24L10.5 18.5L4.99998 16L10.5 13.5L13 7.99999L15.5 13.5L21 16L15.5 18.5L13 24ZM13 19.15L14 17L16.15 16L14 15L13 12.85L12 15L9.84998 16L12 17L13 19.15Z" fill="url(#paint0_linear_340_107020)" fill-opacity="0.7"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_340_107020" x1="4" y1="16" x2="28" y2="16" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2A7DE1"/>
|
||||
<stop offset="1" stop-color="#902AE1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_340_107020">
|
||||
<rect width="32" height="32" rx="6" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_340_107020">
|
||||
<rect width="32" height="32" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 987 B |
19
projects/aca-content/assets/images/three-magic-stars-ai.svg
Normal file
19
projects/aca-content/assets/images/three-magic-stars-ai.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_340_107020)">
|
||||
<g clip-path="url(#clip1_340_107020)">
|
||||
<path d="M23 13L21.75 10.25L19 8.99999L21.75 7.74999L23 4.99999L24.25 7.74999L27 8.99999L24.25 10.25L23 13ZM23 27L21.75 24.25L19 23L21.75 21.75L23 19L24.25 21.75L27 23L24.25 24.25L23 27ZM13 24L10.5 18.5L4.99998 16L10.5 13.5L13 7.99999L15.5 13.5L21 16L15.5 18.5L13 24ZM13 19.15L14 17L16.15 16L14 15L13 12.85L12 15L9.84998 16L12 17L13 19.15Z" fill="white" />
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_340_107020" x1="4" y1="16" x2="28" y2="16" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2A7DE1"/>
|
||||
<stop offset="1" stop-color="#902AE1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_340_107020">
|
||||
<rect width="32" height="32" rx="6" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_340_107020">
|
||||
<rect width="32" height="32" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 944 B |
@@ -77,6 +77,7 @@ import { ContextMenuComponent } from './components/context-menu/context-menu.com
|
||||
import { MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material/dialog';
|
||||
import { SearchResultsRowComponent } from './components/search/search-results-row/search-results-row.component';
|
||||
import { BulkActionsDropdownComponent } from './components/bulk-actions-dropdown/bulk-actions-dropdown.component';
|
||||
import { AgentsButtonComponent } from './components/knowledge-retrieval/search-ai/agents-button/agents-button.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -138,6 +139,7 @@ export class ContentServiceExtensionModule {
|
||||
'app.toolbar.toggleFavorite': ToggleFavoriteComponent,
|
||||
'app.toolbar.toggleFavoriteLibrary': ToggleFavoriteLibraryComponent,
|
||||
'app.toolbar.toggleJoinLibrary': ToggleJoinLibraryButtonComponent,
|
||||
'app.toolbar.ai.agents-button': AgentsButtonComponent,
|
||||
'app.menu.toggleJoinLibrary': ToggleJoinLibraryMenuComponent,
|
||||
'app.bulk-actions-dropdown': BulkActionsDropdownComponent,
|
||||
'app.shared-link.toggleSharedLink': ToggleSharedComponent,
|
||||
@@ -197,6 +199,7 @@ export class ContentServiceExtensionModule {
|
||||
'app.selection.hasNoLibraryRole': rules.hasNoLibraryRole,
|
||||
'app.selection.folder': rules.hasFolderSelected,
|
||||
'app.selection.folder.canUpdate': rules.canUpdateSelectedFolder,
|
||||
'app.selection.displayedKnowledgeRetrievalButton': rules.canDisplayKnowledgeRetrievalButton,
|
||||
|
||||
'app.navigation.folder.canCreate': rules.canCreateFolder,
|
||||
'app.navigation.folder.canUpload': rules.canUpload,
|
||||
|
@@ -40,6 +40,7 @@ import { Route } from '@angular/router';
|
||||
import { SharedLinkViewComponent } from './components/shared-link-view/shared-link-view.component';
|
||||
import { TrashcanComponent } from './components/trashcan/trashcan.component';
|
||||
import { ShellLayoutComponent } from '@alfresco/adf-core/shell';
|
||||
import { SearchAiResultsComponent } from './components/knowledge-retrieval/search-ai/search-ai-results/search-ai-results.component';
|
||||
|
||||
export const CONTENT_ROUTES: ExtensionRoute[] = [
|
||||
{
|
||||
@@ -507,6 +508,10 @@ export const CONTENT_LAYOUT_ROUTES: Route = {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'knowledge-retrieval',
|
||||
component: SearchAiResultsComponent
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
component: GenericErrorComponent
|
||||
|
@@ -1,9 +1,17 @@
|
||||
<aca-page-layout>
|
||||
<div class="aca-page-layout-header">
|
||||
<h1 class="aca-page-title">
|
||||
<aca-search-ai-input-container
|
||||
*ngIf="searchAiInputState.active; else header"
|
||||
[agentId]="searchAiInputState.selectedAgentId">
|
||||
</aca-search-ai-input-container>
|
||||
<ng-template #header>
|
||||
<div class="aca-header-container">
|
||||
<h1 class="aca-page-title">
|
||||
{{ (selectedRowItemsCount < 1 ? 'APP.BROWSE.FAVORITES.TITLE' : 'APP.HEADER.SELECTED') | translate: { count: selectedRowItemsCount } }}
|
||||
</h1>
|
||||
<aca-toolbar [items]="actions"></aca-toolbar>
|
||||
<aca-toolbar [items]="actions"></aca-toolbar>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div class="aca-page-layout-content">
|
||||
|
@@ -46,6 +46,7 @@ import {
|
||||
import { DocumentListDirective } from '../../directives/document-list.directive';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DocumentListComponent } from '@alfresco/adf-content-services';
|
||||
import { SearchAiInputContainerComponent } from '../knowledge-retrieval/search-ai/search-ai-input-container/search-ai-input-container.component';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -59,6 +60,7 @@ import { DocumentListComponent } from '@alfresco/adf-content-services';
|
||||
PageLayoutComponent,
|
||||
TranslateModule,
|
||||
ToolbarComponent,
|
||||
SearchAiInputContainerComponent,
|
||||
EmptyContentComponent,
|
||||
DynamicColumnComponent,
|
||||
DocumentListComponent,
|
||||
@@ -67,7 +69,8 @@ import { DocumentListComponent } from '@alfresco/adf-content-services';
|
||||
CustomEmptyContentTemplateDirective
|
||||
],
|
||||
templateUrl: './favorites.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
selector: 'aca-favorites'
|
||||
})
|
||||
export class FavoritesComponent extends PageComponent implements OnInit {
|
||||
columns: DocumentListPresetRef[] = [];
|
||||
|
@@ -1,12 +1,20 @@
|
||||
<aca-page-layout [hasError]="!isValidPath">
|
||||
<div class="aca-page-layout-header">
|
||||
<adf-breadcrumb [root]="title"
|
||||
[folderNode]="node"
|
||||
[selectedRowItemsCount]="selectedRowItemsCount"
|
||||
[maxItems]="isSmallScreen ? 1 : 0"
|
||||
(navigate)="onBreadcrumbNavigate($event)">
|
||||
</adf-breadcrumb>
|
||||
<aca-toolbar [items]="actions"></aca-toolbar>
|
||||
<aca-search-ai-input-container
|
||||
*ngIf="searchAiInputState.active; else header"
|
||||
[agentId]="searchAiInputState.selectedAgentId">
|
||||
</aca-search-ai-input-container>
|
||||
<ng-template #header>
|
||||
<div class="aca-header-container">
|
||||
<adf-breadcrumb
|
||||
[root]="title"
|
||||
[folderNode]="node"
|
||||
[selectedRowItemsCount]="selectedRowItemsCount" [maxItems]="isSmallScreen ? 1 : 0"
|
||||
(navigate)="onBreadcrumbNavigate($event)">
|
||||
</adf-breadcrumb>
|
||||
<aca-toolbar [items]="actions"></aca-toolbar>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div class="aca-page-layout-error">
|
||||
|
@@ -58,6 +58,7 @@ import { CommonModule } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DocumentListDirective } from '../../directives/document-list.directive';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { SearchAiInputContainerComponent } from '../knowledge-retrieval/search-ai/search-ai-input-container/search-ai-input-container.component';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -73,6 +74,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
PaginationDirective,
|
||||
PageLayoutComponent,
|
||||
ToolbarComponent,
|
||||
SearchAiInputContainerComponent,
|
||||
DynamicColumnComponent,
|
||||
BreadcrumbComponent,
|
||||
UploadDragAreaComponent,
|
||||
@@ -82,7 +84,8 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
CustomEmptyContentTemplateDirective
|
||||
],
|
||||
templateUrl: './files.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
selector: 'aca-files'
|
||||
})
|
||||
export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
isValidPath = true;
|
||||
|
@@ -0,0 +1,34 @@
|
||||
<ng-container *ngIf="agents.length">
|
||||
<button
|
||||
[matMenuTriggerFor]="disabled ? null : agentsList"
|
||||
class="aca-agents-menu-button aca-agents-button-menu-trigger"
|
||||
(mouseup)="onClick()"
|
||||
(keydown.enter)="onClick()"
|
||||
data-automation-id="aca-agents-button">
|
||||
<adf-icon
|
||||
value="adf:colored-stars-ai"
|
||||
class="aca-agents-button-icon">
|
||||
</adf-icon>
|
||||
{{ 'KNOWLEDGE_RETRIEVAL.SEARCH.AGENTS_BUTTON.LABEL' | translate}}
|
||||
</button>
|
||||
<mat-menu
|
||||
#agentsList="matMenu"
|
||||
class="aca-agents-button-menu"
|
||||
xPosition="before">
|
||||
<mat-selection-list
|
||||
(selectionChange)="onAgentSelection($event)"
|
||||
[multiple]="false"
|
||||
class="aca-agents-button-menu-list">
|
||||
<mat-list-option
|
||||
*ngFor="let agent of agents"
|
||||
class="aca-agents-button-menu-list-agent"
|
||||
[attr.data-automation-id]="'aca-agents-button-agent-' + agent.id"
|
||||
[value]="agent">
|
||||
<div class="aca-agents-button-menu-list-agent-content">
|
||||
<adf-avatar [initials]="initialsByAgentId[agent.id]"></adf-avatar>
|
||||
{{ agent.name }}
|
||||
</div>
|
||||
</mat-list-option>
|
||||
</mat-selection-list>
|
||||
</mat-menu>
|
||||
</ng-container>
|
@@ -0,0 +1,59 @@
|
||||
aca-agents-button.aca-agents-button {
|
||||
height: 32px;
|
||||
display: block;
|
||||
|
||||
button {
|
||||
&.aca-agents-menu-button {
|
||||
&.aca-agents-button-menu-trigger {
|
||||
height: auto;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: transparent;
|
||||
width: max-content;
|
||||
padding: 0 4px 0 0;
|
||||
}
|
||||
|
||||
.aca-agents-button-icon {
|
||||
display: flex;
|
||||
align-self: baseline;
|
||||
|
||||
svg {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
position: absolute;
|
||||
margin-left: -21px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.aca-agents-button-menu {
|
||||
padding-top: 2px;
|
||||
padding-bottom: 1px;
|
||||
|
||||
.aca-agents-button-menu-list {
|
||||
margin-left: -6px;
|
||||
padding-top: 0;
|
||||
|
||||
&-agent {
|
||||
height: 40px;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
adf-avatar {
|
||||
margin-right: 12px;
|
||||
margin-bottom: 2px;
|
||||
padding-left: 1px;
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,123 @@
|
||||
/*!
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SelectionState } from '@alfresco/adf-extensions';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore, getAppSelection } from '@alfresco/aca-shared/store';
|
||||
import { AvatarComponent, IconComponent, NotificationService } from '@alfresco/adf-core';
|
||||
import { Subject } from 'rxjs';
|
||||
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 { AgentService, SearchAiService } from '@alfresco/adf-content-services';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatMenuModule, MatListModule, TranslateModule, AvatarComponent, IconComponent],
|
||||
selector: 'aca-agents-button',
|
||||
templateUrl: './agents-button.component.html',
|
||||
styleUrls: ['./agents-button.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'aca-agents-button' }
|
||||
})
|
||||
export class AgentsButtonComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
data: { trigger: string };
|
||||
|
||||
private selectedNodesState: SelectionState;
|
||||
private _agents: Agent[] = [];
|
||||
private onDestroy$ = new Subject<void>();
|
||||
private _disabled = true;
|
||||
private _initialsByAgentId: { [key: string]: string } = {};
|
||||
|
||||
get agents(): Agent[] {
|
||||
return this._agents;
|
||||
}
|
||||
|
||||
get disabled(): boolean {
|
||||
return this._disabled;
|
||||
}
|
||||
|
||||
get initialsByAgentId(): { [key: string]: string } {
|
||||
return this._initialsByAgentId;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private notificationService: NotificationService,
|
||||
private searchAiService: SearchAiService,
|
||||
private translateService: TranslateService,
|
||||
private agentService: AgentService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe((selection) => {
|
||||
this.selectedNodesState = selection;
|
||||
});
|
||||
this.agentService
|
||||
.getAgents()
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(
|
||||
(paging) => {
|
||||
this._agents = paging.list.entries.map((agentEntry) => agentEntry.entry);
|
||||
if (this.agents.length) {
|
||||
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] || ''}`;
|
||||
return initials;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
() => this.notificationService.showError(this.translateService.instant('KNOWLEDGE_RETRIEVAL.SEARCH.ERRORS.AGENTS_FETCHING'))
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy$.next();
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
onClick(): void {
|
||||
const error = this.searchAiService.checkSearchAvailability(this.selectedNodesState);
|
||||
if (error) {
|
||||
this.notificationService.showInfo(error);
|
||||
}
|
||||
this._disabled = !!error;
|
||||
}
|
||||
|
||||
onAgentSelection(change: MatSelectionListChange): void {
|
||||
this.store.dispatch({
|
||||
type: this.data.trigger,
|
||||
agentId: change.options[0].value.id
|
||||
});
|
||||
change.source.deselectAll();
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
<aca-search-ai-input
|
||||
(searchSubmitted)="hideSearchInput()"
|
||||
[placeholder]="placeholder"
|
||||
[agentId]="agentId"
|
||||
[useStoredNodes]="useStoredNodes">
|
||||
</aca-search-ai-input>
|
||||
<mat-divider
|
||||
[vertical]="true"
|
||||
class="aca-search-ai-input-container-divider">
|
||||
</mat-divider>
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="leaveSearchInput()"
|
||||
data-automation-id="aca-search-ai-input-container-leaving-search-button"
|
||||
[title]="'KNOWLEDGE_RETRIEVAL.SEARCH.SEARCH_INPUT.HIDE_INPUT' | translate">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
@@ -0,0 +1,14 @@
|
||||
aca-search-ai-input-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.aca-search-ai-input-container-divider {
|
||||
height: 24px;
|
||||
margin-left: 30px;
|
||||
margin-right: 7px;
|
||||
background: var(--adf-theme-foreground-text-color-025);
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
/*!
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { SearchAiInputComponent } from '../search-ai-input/search-ai-input.component';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { SearchAiNavigationService } from '../../../../services/search-ai-navigation.service';
|
||||
import { NavigationStart, Router } from '@angular/router';
|
||||
import { filter, takeUntil } from 'rxjs/operators';
|
||||
import { SearchAiService } from '@alfresco/adf-content-services';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [SearchAiInputComponent, MatIconModule, MatDividerModule, MatButtonModule, TranslateModule],
|
||||
selector: 'aca-search-ai-input-container',
|
||||
templateUrl: './search-ai-input-container.component.html',
|
||||
styleUrls: ['./search-ai-input-container.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class SearchAiInputContainerComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
placeholder = 'KNOWLEDGE_RETRIEVAL.SEARCH.SEARCH_INPUT.DEFAULT_PLACEHOLDER';
|
||||
@Input()
|
||||
agentId: string;
|
||||
@Input()
|
||||
useStoredNodes: boolean;
|
||||
|
||||
private onDestroy$ = new Subject<void>();
|
||||
|
||||
constructor(private searchAiService: SearchAiService, private searchNavigationService: SearchAiNavigationService, private router: Router) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.router.events
|
||||
.pipe(
|
||||
filter((event) => event instanceof NavigationStart),
|
||||
takeUntil(this.onDestroy$)
|
||||
)
|
||||
.subscribe(() => this.hideSearchInput());
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy$.next();
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
hideSearchInput(): void {
|
||||
this.searchAiService.updateSearchAiInputState({
|
||||
active: false
|
||||
});
|
||||
}
|
||||
|
||||
leaveSearchInput(): void {
|
||||
this.searchNavigationService.navigateToPreviousRoute();
|
||||
this.hideSearchInput();
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
<mat-select
|
||||
[formControl]="agentControl"
|
||||
class="aca-search-ai-input-agent-select"
|
||||
panelClass="aca-search-ai-input-agent-select-options"
|
||||
data-automation-id="aca-search-ai-agents-select">
|
||||
<mat-select-trigger class="aca-search-ai-input-agent-select-displayed-value">
|
||||
<adf-avatar
|
||||
[initials]="initialsByAgentId[agentControl.value.id]"
|
||||
size="26px">
|
||||
</adf-avatar>
|
||||
{{ agentControl.value.name }}
|
||||
</mat-select-trigger>
|
||||
<mat-option
|
||||
*ngFor="let agent of agents"
|
||||
[value]="agent"
|
||||
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 }}
|
||||
</div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<input
|
||||
class="aca-search-ai-input-text"
|
||||
matInput
|
||||
[type]="'text'"
|
||||
[formControl]="queryControl"
|
||||
data-automation-id="aca-search-ai-input"
|
||||
[placeholder]="placeholder | translate"
|
||||
(keydown.enter)="onSearchSubmit()"/>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
class="aca-search-ai-asking-button"
|
||||
(click)="onSearchSubmit()"
|
||||
[disabled]="!queryControl.value"
|
||||
data-automation-id="aca-search-ai-asking-button">
|
||||
<adf-icon [value]="'adf:three_magic_stars_ai'"></adf-icon>
|
||||
{{ 'KNOWLEDGE_RETRIEVAL.SEARCH.SEARCH_INPUT.ASK_BUTTON_LABEL' | translate }}
|
||||
</button>
|
@@ -0,0 +1,81 @@
|
||||
aca-search-ai-input {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.aca-search-ai-input-text {
|
||||
flex: 1;
|
||||
font-size: 20px;
|
||||
margin-right: 167px;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
&::placeholder {
|
||||
color: var(--theme-primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.aca-search-ai-asking-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 5px;
|
||||
padding-right: 12px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
|
||||
adf-icon {
|
||||
margin-bottom: 3px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.aca-search-ai-input-agent-select {
|
||||
width: 149px;
|
||||
height: 35px;
|
||||
align-content: center;
|
||||
border-radius: 16px;
|
||||
padding-left: 3px;
|
||||
padding-right: 10px;
|
||||
background-color: var(--theme-grey-text-background-color);
|
||||
color: var(--theme-text-light-color);
|
||||
font-size: 15px;
|
||||
margin-right: 26px;
|
||||
|
||||
&:focus {
|
||||
outline: -webkit-focus-ring-color auto 1px;
|
||||
}
|
||||
|
||||
&-displayed-value {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
adf-avatar {
|
||||
margin-left: 2px;
|
||||
margin-right: 6px;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.aca-search-ai-input-agent-select-options {
|
||||
margin-top: 48px;
|
||||
|
||||
.aca-search-ai-input-agent-select-options-option {
|
||||
padding-left: 11px;
|
||||
padding-right: 11px;
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
adf-avatar {
|
||||
margin-right: 12px;
|
||||
padding-left: 1px;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,158 @@
|
||||
/*!
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewEncapsulation } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { A11yModule } from '@angular/cdk/a11y';
|
||||
import { AvatarComponent, IconComponent, NotificationService, UserPreferencesService } from '@alfresco/adf-core';
|
||||
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AiSearchByTermPayload, AppStore, getAppSelection, SearchByTermAiAction } from '@alfresco/aca-shared/store';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { SelectionState } from '@alfresco/adf-extensions';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { Agent } from '@alfresco/js-api';
|
||||
import { AgentService, SearchAiService } from '@alfresco/adf-content-services';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
TranslateModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
A11yModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatSelectModule,
|
||||
IconComponent,
|
||||
AvatarComponent
|
||||
],
|
||||
selector: 'aca-search-ai-input',
|
||||
templateUrl: './search-ai-input.component.html',
|
||||
styleUrls: ['./search-ai-input.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class SearchAiInputComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
placeholder: string;
|
||||
@Input()
|
||||
agentId: string;
|
||||
@Input()
|
||||
useStoredNodes: boolean;
|
||||
|
||||
@Output()
|
||||
searchSubmitted = new EventEmitter<void>();
|
||||
|
||||
private readonly storedNodesKey = 'knowledgeRetrievalNodes';
|
||||
|
||||
private _agentControl = new FormControl<Agent>(null);
|
||||
private _agents: Agent[] = [];
|
||||
private onDestroy$ = new Subject<void>();
|
||||
private selectedNodesState: SelectionState;
|
||||
private _queryControl = new FormControl('');
|
||||
private _initialsByAgentId: { [key: string]: string } = {};
|
||||
|
||||
get agentControl(): FormControl<Agent> {
|
||||
return this._agentControl;
|
||||
}
|
||||
|
||||
get agents(): Agent[] {
|
||||
return this._agents;
|
||||
}
|
||||
|
||||
get queryControl(): FormControl<string> {
|
||||
return this._queryControl;
|
||||
}
|
||||
|
||||
get initialsByAgentId(): { [key: string]: string } {
|
||||
return this._initialsByAgentId;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private searchAiService: SearchAiService,
|
||||
private notificationService: NotificationService,
|
||||
private agentService: AgentService,
|
||||
private translateService: TranslateService,
|
||||
private userPreferencesService: UserPreferencesService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.useStoredNodes) {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe((selection) => {
|
||||
this.selectedNodesState = selection;
|
||||
});
|
||||
} 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));
|
||||
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] || ''}`;
|
||||
return initials;
|
||||
}, {});
|
||||
},
|
||||
() => this.notificationService.showError(this.translateService.instant('KNOWLEDGE_RETRIEVAL.SEARCH.ERRORS.AGENTS_FETCHING'))
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy$.next();
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
onSearchSubmit(): void {
|
||||
const error = this.searchAiService.checkSearchAvailability(this.selectedNodesState);
|
||||
if (error) {
|
||||
this.notificationService.showInfo(error);
|
||||
} else {
|
||||
const payload: AiSearchByTermPayload = {
|
||||
searchTerm: this.queryControl.value,
|
||||
agentId: this.agentControl.value.id
|
||||
};
|
||||
this.userPreferencesService.set(this.storedNodesKey, JSON.stringify(this.selectedNodesState));
|
||||
this.store.dispatch(new SearchByTermAiAction(payload));
|
||||
this.queryControl.reset();
|
||||
this.searchSubmitted.emit();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
<aca-page-layout>
|
||||
<aca-search-ai-input-container
|
||||
class="aca-page-layout-header"
|
||||
placeholder="KNOWLEDGE_RETRIEVAL.SEARCH.RESULTS_PAGE.QUERY_INPUT_PLACEHOLDER"
|
||||
[agentId]="agentId"
|
||||
[useStoredNodes]="true"
|
||||
*ngIf="!hasError">
|
||||
</aca-search-ai-input-container>
|
||||
<div
|
||||
class="aca-page-layout-content"
|
||||
*ngIf="!hasError">
|
||||
<div class="aca-search-ai-results-container">
|
||||
<div
|
||||
class="aca-search-ai-results-container-query"
|
||||
data-automation-id="aca-search-ai-results-query">
|
||||
{{ searchQuery }}
|
||||
</div>
|
||||
<div
|
||||
class="aca-search-ai-response-container"
|
||||
[class.aca-search-ai-response-container-error]="hasAnsweringError">
|
||||
<ng-container *ngIf="!loading">
|
||||
<div
|
||||
class="aca-search-ai-response-container-body"
|
||||
*ngIf="!hasAnsweringError">
|
||||
<div
|
||||
class="aca-search-ai-response-container-body-response"
|
||||
data-automation-id="aca-search-ai-results-response">
|
||||
{{ queryAnswer?.answer }}
|
||||
</div>
|
||||
<button
|
||||
class="aca-search-ai-response-container-body-response-action aca-search-ai-response-container-body-response-action-regeneration"
|
||||
mat-icon-button
|
||||
(click)="performAiSearch()"
|
||||
data-automation-id="aca-search-ai-results-regeneration-button"
|
||||
[title]="'KNOWLEDGE_RETRIEVAL.SEARCH.RESULTS_PAGE.REGENERATION_BUTTON_LABEL' | translate">
|
||||
<mat-icon>cached</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
class="aca-search-ai-response-container-body-response-action"
|
||||
mat-icon-button
|
||||
(click)="copyResponseToClipboard()"
|
||||
data-automation-id="aca-search-ai-results-copying-button"
|
||||
[title]="'KNOWLEDGE_RETRIEVAL.SEARCH.RESULTS_PAGE.COPY_BUTTON_LABEL' | translate">
|
||||
<mat-icon>copy</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
class="aca-search-ai-response-container-body-response-action"
|
||||
mat-icon-button
|
||||
data-automation-id="aca-search-ai-results-thumb-up-button"
|
||||
[title]="'KNOWLEDGE_RETRIEVAL.SEARCH.RESULTS_PAGE.LIKE_BUTTON_LABEL' | translate">
|
||||
<mat-icon>thumb_up</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
class="aca-search-ai-response-container-body-response-action aca-search-ai-response-container-body-response-action-thumb-down"
|
||||
mat-icon-button
|
||||
data-automation-id="aca-search-ai-results-thumb-down-button"
|
||||
[title]="'KNOWLEDGE_RETRIEVAL.SEARCH.RESULTS_PAGE.DISLIKE_BUTTON_LABEL' | translate">
|
||||
<mat-icon>thumb_down</mat-icon>
|
||||
</button>
|
||||
<ng-container *ngIf="nodes?.length">
|
||||
<mat-divider class="aca-search-ai-response-container-body-divider"></mat-divider>
|
||||
<div class="aca-search-ai-response-container-body-references-container">
|
||||
<p class="aca-search-ai-response-container-body-references-container-header">
|
||||
{{ 'KNOWLEDGE_RETRIEVAL.SEARCH.RESULTS_PAGE.REFERENCED_DOCUMENTS_HEADER' | translate }}
|
||||
</p>
|
||||
<div class="aca-search-ai-response-container-body-references-container-documents">
|
||||
<div
|
||||
class="aca-search-ai-response-container-body-references-container-documents-document"
|
||||
*ngFor="let node of nodes"
|
||||
[attr.data-automation-id]="'aca-search-ai-results-' + node.id + '-document'">
|
||||
<mat-icon
|
||||
mat-list-icon
|
||||
class="aca-search-ai-response-container-body-references-container-documents-document-icon">
|
||||
<img [alt]="node.content?.mimeType" [src]="mimeTypeIconsByNodeId[node.id]"/>
|
||||
</mat-icon>
|
||||
<div class="aca-search-ai-response-container-body-references-container-documents-document-name">
|
||||
{{ node.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="hasAnsweringError"
|
||||
class="aca-search-ai-response-container-error-message">
|
||||
{{ 'KNOWLEDGE_RETRIEVAL.SEARCH.ERRORS.LOADING_ERROR' | translate }}
|
||||
<button
|
||||
mat-flat-button
|
||||
(click)="performAiSearch()"
|
||||
class="aca-search-ai-response-container-error-message-regeneration-button"
|
||||
data-automation-id="aca-search-ai-results-error-regeneration-button">
|
||||
<mat-icon>cached</mat-icon>
|
||||
{{ 'KNOWLEDGE_RETRIEVAL.SEARCH.RESULTS_PAGE.REGENERATION_BUTTON_LABEL' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<adf-empty-content
|
||||
class="aca-page-layout-content"
|
||||
icon="star"
|
||||
title="KNOWLEDGE_RETRIEVAL.SEARCH.ERRORS.PAGE_NOT_AVAILABLE_ERROR"
|
||||
*ngIf="hasError">
|
||||
</adf-empty-content>
|
||||
</aca-page-layout>
|
@@ -0,0 +1,124 @@
|
||||
.aca-search-ai-results {
|
||||
aca-page-layout {
|
||||
.aca-page-layout-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
border-top: 1px solid var(--theme-grey-background-color);
|
||||
padding-top: 28px;
|
||||
|
||||
.aca-search-ai-results-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding-right: 24%;
|
||||
padding-left: 24%;
|
||||
min-width: 51%;
|
||||
|
||||
&-query {
|
||||
border-radius: 12px;
|
||||
padding: 20px 15px 19px;
|
||||
background: var(--theme-card-background-grey-color);
|
||||
}
|
||||
}
|
||||
|
||||
.aca-search-ai-response-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--adf-card-view-border-color);
|
||||
border-radius: 12px;
|
||||
margin: 16px 0 75px;
|
||||
padding: 14px 40px 36px 35px;
|
||||
|
||||
&-error {
|
||||
border-color: var(--adf-error-color);
|
||||
padding: 21px 19px 27px 18px;
|
||||
|
||||
&-message {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
&-regeneration-button {
|
||||
background-color: var(--adf-secondary-button-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-body {
|
||||
width: 100%;
|
||||
|
||||
&-response {
|
||||
margin-bottom: 17px;
|
||||
padding-left: 6px;
|
||||
padding-right: 5px;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
&-action {
|
||||
width: max-content;
|
||||
|
||||
mat-icon {
|
||||
font-size: 17.25px;
|
||||
}
|
||||
|
||||
&-regeneration {
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
&-thumb-down {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-divider {
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
&-references-container {
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
|
||||
&-header {
|
||||
margin-top: 8px;
|
||||
color: var(--theme-text-light-color);
|
||||
font-weight: 400;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
&-documents {
|
||||
padding-right: 5px;
|
||||
padding-top: 5px;
|
||||
margin-left: -2px;
|
||||
display: flex;
|
||||
gap: 21px;
|
||||
|
||||
&-document {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
|
||||
&-icon {
|
||||
padding-right: 11px;
|
||||
}
|
||||
|
||||
&-name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,168 @@
|
||||
/*!
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { ActivatedRoute, Params } 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 { 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 { SelectionState } from '@alfresco/adf-extensions';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
PageLayoutComponent,
|
||||
ToolbarActionComponent,
|
||||
ToolbarModule,
|
||||
ToolbarComponent,
|
||||
SearchAiInputContainerComponent,
|
||||
TranslateModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
MatListModule,
|
||||
EmptyContentComponent
|
||||
],
|
||||
selector: 'aca-search-ai-results',
|
||||
templateUrl: './search-ai-results.component.html',
|
||||
styleUrls: ['./search-ai-results.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'aca-search-ai-results' }
|
||||
})
|
||||
export class SearchAiResultsComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
private _agentId: string;
|
||||
private _hasAnsweringError = false;
|
||||
private _hasError = false;
|
||||
private _loading = true;
|
||||
private _mimeTypeIconsByNodeId: { [key: string]: string } = {};
|
||||
private _nodes: Node[] = [];
|
||||
private selectedNodesState: SelectionState;
|
||||
private _searchQuery = '';
|
||||
private _queryAnswer: AiAnswer;
|
||||
|
||||
get agentId(): string {
|
||||
return this._agentId;
|
||||
}
|
||||
|
||||
get hasAnsweringError(): boolean {
|
||||
return this._hasAnsweringError;
|
||||
}
|
||||
|
||||
get hasError(): boolean {
|
||||
return this._hasError;
|
||||
}
|
||||
|
||||
get loading(): boolean {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
get mimeTypeIconsByNodeId(): { [key: string]: string } {
|
||||
return this._mimeTypeIconsByNodeId;
|
||||
}
|
||||
|
||||
get nodes(): Node[] {
|
||||
return this._nodes;
|
||||
}
|
||||
|
||||
get queryAnswer(): AiAnswer {
|
||||
return this._queryAnswer;
|
||||
}
|
||||
|
||||
get searchQuery(): string {
|
||||
return this._searchQuery;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private clipboardService: ClipboardService,
|
||||
private thumbnailService: ThumbnailService,
|
||||
private nodesApiService: NodesApiService,
|
||||
private userPreferencesService: UserPreferencesService,
|
||||
private translateService: TranslateService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.queryParams.pipe(takeUntil(this.onDestroy$)).subscribe((params: 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;
|
||||
}
|
||||
this.performAiSearch();
|
||||
});
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy$.next();
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
copyResponseToClipboard(): void {
|
||||
this.clipboardService.copyContentToClipboard(
|
||||
this.queryAnswer.answer,
|
||||
this.translateService.instant('KNOWLEDGE_RETRIEVAL.SEARCH.RESULTS_PAGE.COPY_MESSAGE')
|
||||
);
|
||||
}
|
||||
|
||||
performAiSearch(): void {
|
||||
this._loading = true;
|
||||
this.searchAiService
|
||||
.ask({
|
||||
question: this.searchQuery,
|
||||
nodeIds: this.selectedNodesState.nodes.map((node) => node.entry.id)
|
||||
})
|
||||
.pipe(
|
||||
switchMap((response) => this.searchAiService.getAnswer(response.questionId)),
|
||||
switchMap((response) => {
|
||||
this._queryAnswer = response.list.entries[0].entry;
|
||||
return forkJoin(this.queryAnswer.references.map((reference) => this.nodesApiService.getNode(reference.referenceId)));
|
||||
}),
|
||||
finalize(() => (this._loading = false)),
|
||||
takeUntil(this.onDestroy$)
|
||||
)
|
||||
.subscribe(
|
||||
(nodes) => {
|
||||
nodes.forEach((node) => {
|
||||
this._mimeTypeIconsByNodeId[node.id] = this.thumbnailService.getMimeTypeIcon(node.content?.mimeType);
|
||||
});
|
||||
this._nodes = nodes;
|
||||
},
|
||||
() => (this._hasAnsweringError = true)
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,9 +1,17 @@
|
||||
<aca-page-layout>
|
||||
<div class="aca-page-layout-header">
|
||||
<h1 class="aca-page-title">
|
||||
<aca-search-ai-input-container
|
||||
*ngIf="searchAiInputState.active; else header"
|
||||
[agentId]="searchAiInputState.selectedAgentId">
|
||||
</aca-search-ai-input-container>
|
||||
<ng-template #header>
|
||||
<div class="aca-header-container">
|
||||
<h1 class="aca-page-title">
|
||||
{{ (selectedRowItemsCount < 1 ? 'APP.BROWSE.RECENT.TITLE' : 'APP.HEADER.SELECTED') | translate: { count: selectedRowItemsCount } }}
|
||||
</h1>
|
||||
<aca-toolbar [items]="actions"></aca-toolbar>
|
||||
<aca-toolbar [items]="actions"></aca-toolbar>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div class="aca-page-layout-content">
|
||||
|
@@ -39,6 +39,7 @@ import { DocumentListModule } from '@alfresco/adf-content-services';
|
||||
import { DataTableModule, EmptyContentComponent, PaginationComponent } from '@alfresco/adf-core';
|
||||
import { DocumentListDirective } from '../../directives/document-list.directive';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SearchAiInputContainerComponent } from '../knowledge-retrieval/search-ai/search-ai-input-container/search-ai-input-container.component';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -54,11 +55,13 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
PageLayoutComponent,
|
||||
TranslateModule,
|
||||
ToolbarComponent,
|
||||
SearchAiInputContainerComponent,
|
||||
EmptyContentComponent,
|
||||
DynamicColumnComponent
|
||||
],
|
||||
templateUrl: './recent-files.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
selector: 'aca-recent-files'
|
||||
})
|
||||
export class RecentFilesComponent extends PageComponent implements OnInit {
|
||||
columns: DocumentListPresetRef[] = [];
|
||||
|
@@ -1,9 +1,15 @@
|
||||
<aca-page-layout>
|
||||
<aca-page-layout [class.aca-search-results-active-search-ai-input]="searchAiInputState.active">
|
||||
<div class="aca-page-layout-header">
|
||||
<aca-search-input></aca-search-input>
|
||||
<aca-bulk-actions-dropdown *ngIf="bulkActions" [items]="bulkActions"></aca-bulk-actions-dropdown>
|
||||
<div class="aca-search-toolbar-spacer"></div>
|
||||
<aca-toolbar [items]="actions"></aca-toolbar>
|
||||
<aca-search-ai-input-container
|
||||
*ngIf="searchAiInputState.active"
|
||||
[agentId]="searchAiInputState.selectedAgentId">
|
||||
</aca-search-ai-input-container>
|
||||
<div class="aca-header-container">
|
||||
<aca-search-input></aca-search-input>
|
||||
<aca-bulk-actions-dropdown *ngIf="bulkActions" [items]="bulkActions"></aca-bulk-actions-dropdown>
|
||||
<div class="aca-search-toolbar-spacer"></div>
|
||||
<aca-toolbar [items]="actions"></aca-toolbar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="aca-page-layout-content">
|
||||
|
@@ -1,6 +1,13 @@
|
||||
@import '../../../ui/mixins';
|
||||
|
||||
aca-search-results {
|
||||
.aca-search-results-active-search-ai-input {
|
||||
.aca-header-container,
|
||||
.adf-search-results__content-header.aca-content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.aca-search-toolbar-spacer {
|
||||
width: 100%;
|
||||
}
|
||||
|
@@ -77,6 +77,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { SearchResultsRowComponent } from '../search-results-row/search-results-row.component';
|
||||
import { DocumentListPresetRef, DynamicColumnComponent } from '@alfresco/adf-extensions';
|
||||
import { BulkActionsDropdownComponent } from '../../bulk-actions-dropdown/bulk-actions-dropdown.component';
|
||||
import { SearchAiInputContainerComponent } from '../../knowledge-retrieval/search-ai/search-ai-input-container/search-ai-input-container.component';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -110,7 +111,8 @@ import { BulkActionsDropdownComponent } from '../../bulk-actions-dropdown/bulk-a
|
||||
DateColumnHeaderComponent,
|
||||
CustomEmptyContentTemplateDirective,
|
||||
ViewerToolbarComponent,
|
||||
BulkActionsDropdownComponent
|
||||
BulkActionsDropdownComponent,
|
||||
SearchAiInputContainerComponent
|
||||
],
|
||||
selector: 'aca-search-results',
|
||||
templateUrl: './search-results.component.html',
|
||||
|
@@ -1,10 +1,18 @@
|
||||
<aca-page-layout>
|
||||
<div class="aca-page-layout-header">
|
||||
<h1 class="aca-page-title">
|
||||
<aca-search-ai-input-container
|
||||
*ngIf="searchAiInputState.active; else header"
|
||||
[agentId]="searchAiInputState.selectedAgentId">
|
||||
</aca-search-ai-input-container>
|
||||
<ng-template #header>
|
||||
<div class="aca-header-container">
|
||||
<h1 class="aca-page-title">
|
||||
{{ (selectedRowItemsCount < 1 ? 'APP.BROWSE.SHARED.TITLE' : 'APP.HEADER.SELECTED') | translate: { count: selectedRowItemsCount } }}
|
||||
</h1>
|
||||
|
||||
<aca-toolbar [items]="actions"></aca-toolbar>
|
||||
<aca-toolbar [items]="actions"></aca-toolbar>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div class="aca-page-layout-content">
|
||||
|
@@ -40,6 +40,7 @@ import { DocumentListModule } from '@alfresco/adf-content-services';
|
||||
import { DataTableModule, EmptyContentComponent, PaginationComponent } from '@alfresco/adf-core';
|
||||
import { DocumentListDirective } from '../../directives/document-list.directive';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SearchAiInputContainerComponent } from '../knowledge-retrieval/search-ai/search-ai-input-container/search-ai-input-container.component';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -55,11 +56,13 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
PageLayoutComponent,
|
||||
TranslateModule,
|
||||
ToolbarComponent,
|
||||
SearchAiInputContainerComponent,
|
||||
EmptyContentComponent,
|
||||
DynamicColumnComponent
|
||||
],
|
||||
templateUrl: './shared-files.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
selector: 'aca-shared-files'
|
||||
})
|
||||
export class SharedFilesComponent extends PageComponent implements OnInit {
|
||||
columns: DocumentListPresetRef[] = [];
|
||||
|
@@ -0,0 +1,48 @@
|
||||
/*!
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Params, Router } from '@angular/router';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SearchAiNavigationService {
|
||||
private readonly knowledgeRetrievalRoute = '/knowledge-retrieval';
|
||||
|
||||
private previousRoute = '';
|
||||
|
||||
constructor(private router: Router) {}
|
||||
|
||||
navigateToPreviousRoute(): void {
|
||||
if (this.router.url.includes(this.knowledgeRetrievalRoute)) {
|
||||
void this.router.navigateByUrl(this.previousRoute || '/personal-files');
|
||||
}
|
||||
}
|
||||
|
||||
navigateToSearchAi(queryParams: Params): void {
|
||||
if (!this.router.url.includes(this.knowledgeRetrievalRoute)) {
|
||||
this.previousRoute = this.router.url;
|
||||
}
|
||||
void this.router.navigate([this.knowledgeRetrievalRoute], { queryParams: queryParams });
|
||||
}
|
||||
}
|
@@ -41,6 +41,7 @@ import {
|
||||
ContextMenuEffects
|
||||
} from './effects';
|
||||
import { INITIAL_STATE } from './initial-state';
|
||||
import { SearchAiEffects } from './effects/search-ai.effects';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -69,6 +70,8 @@ import { INITIAL_STATE } from './initial-state';
|
||||
FavoriteEffects,
|
||||
TemplateEffects,
|
||||
ContextMenuEffects,
|
||||
SearchAiEffects,
|
||||
ContextMenuEffects,
|
||||
SnackbarEffects,
|
||||
RouterEffects
|
||||
])
|
||||
|
@@ -33,3 +33,4 @@ export * from './effects/upload.effects';
|
||||
export * from './effects/upload.effects';
|
||||
export * from './effects/template.effects';
|
||||
export * from './effects/contextmenu.effects';
|
||||
export * from './effects/search-ai.effects';
|
||||
|
@@ -0,0 +1,64 @@
|
||||
/*!
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
import { SearchAiActionTypes, SearchByTermAiAction, ToggleAISearchInput } from '@alfresco/aca-shared/store';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { SearchAiNavigationService } from '../../services/search-ai-navigation.service';
|
||||
import { SearchAiService } from '@alfresco/adf-content-services';
|
||||
|
||||
@Injectable()
|
||||
export class SearchAiEffects {
|
||||
constructor(private actions$: Actions, private searchNavigationService: SearchAiNavigationService, private searchAiService: SearchAiService) {}
|
||||
|
||||
searchByTerm$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<SearchByTermAiAction>(SearchAiActionTypes.SearchByTermAi),
|
||||
map((action) => {
|
||||
const queryParams = {
|
||||
query: encodeURIComponent(action.payload.searchTerm),
|
||||
agentId: action.payload.agentId
|
||||
};
|
||||
this.searchNavigationService.navigateToSearchAi(queryParams);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
toggleAISearchInput$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<ToggleAISearchInput>(SearchAiActionTypes.ToggleAiSearchInput),
|
||||
map((action) =>
|
||||
this.searchAiService.updateSearchAiInputState({
|
||||
active: true,
|
||||
selectedAgentId: action.agentId
|
||||
})
|
||||
)
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
}
|
@@ -66,3 +66,11 @@ ng-component {
|
||||
color: var(--adf-theme-foreground-text-color-087);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.aca-header-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
@@ -46,6 +46,8 @@ $disabled-chip-background-color: #f5f5f5;
|
||||
$contrast-gray: mat.get-color-from-palette($foreground, 'secondary-tex');
|
||||
$search-highlight-background-color: #ffd180;
|
||||
$info-snackbar-background: #1f74db;
|
||||
$text-light-color: rgba(33, 35, 40, 0.7);
|
||||
$card-background-grey-color: rgb(248, 248, 248);
|
||||
|
||||
// CSS Variables
|
||||
$defaults: (
|
||||
@@ -96,7 +98,9 @@ $defaults: (
|
||||
--theme-search-chip-icon-color: $search-chip-icon-color,
|
||||
--theme-disabled-chip-background-color: $disabled-chip-background-color,
|
||||
--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-card-background-grey-color: $card-background-grey-color
|
||||
);
|
||||
|
||||
// propagates SCSS variables into the CSS variables scope
|
||||
|
@@ -629,3 +629,10 @@ export function isSmartFolder(context: RuleContext): boolean {
|
||||
export const areTagsEnabled = (context: AcaRuleContext): boolean => context.appConfig.get('plugins.tagsEnabled', true);
|
||||
|
||||
export const areCategoriesEnabled = (context: AcaRuleContext): boolean => context.appConfig.get('plugins.categoriesEnabled', true);
|
||||
|
||||
export const canDisplayKnowledgeRetrievalButton = (context: AcaRuleContext): boolean =>
|
||||
navigation.isPersonalFiles(context) ||
|
||||
navigation.isSharedFiles(context) ||
|
||||
navigation.isRecentFiles(context) ||
|
||||
navigation.isFavorites(context) ||
|
||||
((navigation.isSearchResults(context) || navigation.isLibraryContent(context)) && navigation.isNotLibraries(context));
|
||||
|
@@ -22,7 +22,7 @@
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { DocumentListComponent, DocumentListService, ShareDataRow, UploadService } from '@alfresco/adf-content-services';
|
||||
import { DocumentListComponent, DocumentListService, SearchAiInputState, SearchAiService, ShareDataRow, UploadService } from '@alfresco/adf-content-services';
|
||||
import { ShowHeaderMode } from '@alfresco/adf-core';
|
||||
import { ContentActionRef, DocumentListPresetRef, SelectionState } from '@alfresco/adf-extensions';
|
||||
import { OnDestroy, OnInit, OnChanges, ViewChild, SimpleChanges, Directive, inject, HostListener } from '@angular/core';
|
||||
@@ -81,8 +81,18 @@ export abstract class PageComponent implements OnInit, OnDestroy, OnChanges {
|
||||
protected router = inject(Router);
|
||||
private autoDownloadService = inject(AutoDownloadService, { optional: true });
|
||||
|
||||
protected searchAiService: SearchAiService = inject(SearchAiService);
|
||||
protected subscriptions: Subscription[] = [];
|
||||
|
||||
private fileAutoDownloadService = inject(AcaFileAutoDownloadService, { optional: true });
|
||||
private _searchAiInputState: SearchAiInputState = {
|
||||
active: false
|
||||
};
|
||||
|
||||
get searchAiInputState(): SearchAiInputState {
|
||||
return this._searchAiInputState;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.extensions
|
||||
.getCreateActions()
|
||||
@@ -135,6 +145,10 @@ export abstract class PageComponent implements OnInit, OnDestroy, OnChanges {
|
||||
.subscribe((result) => {
|
||||
this.isSmallScreen = result.matches;
|
||||
});
|
||||
|
||||
this.searchAiService.toggleSearchAiInput$
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe((searchAiInputState) => (this._searchAiInputState = searchAiInputState));
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
|
42
projects/aca-shared/store/src/actions/search-ai.actions.ts
Normal file
42
projects/aca-shared/store/src/actions/search-ai.actions.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*!
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Action } from '@ngrx/store';
|
||||
import { AiSearchByTermPayload } from '../models/ai-search-by-term-payload';
|
||||
|
||||
export enum SearchAiActionTypes {
|
||||
SearchByTermAi = 'SEARCH_BY_TERM_AI',
|
||||
ToggleAiSearchInput = 'TOGGLE_AI_SEARCH_INPUT'
|
||||
}
|
||||
|
||||
export class SearchByTermAiAction implements Action {
|
||||
readonly type = SearchAiActionTypes.SearchByTermAi;
|
||||
constructor(public payload: AiSearchByTermPayload) {}
|
||||
}
|
||||
|
||||
export class ToggleAISearchInput implements Action {
|
||||
readonly type = SearchAiActionTypes.ToggleAiSearchInput;
|
||||
|
||||
constructor(public agentId: string) {}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
/*!
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export interface AiSearchByTermPayload {
|
||||
searchTerm: string;
|
||||
agentId: string;
|
||||
}
|
@@ -37,10 +37,12 @@ export * from './actions/viewer.actions';
|
||||
export * from './actions/metadata-aspect.actions';
|
||||
export * from './actions/template.actions';
|
||||
export * from './actions/contextmenu.actions';
|
||||
export * from './actions/search-ai.actions';
|
||||
|
||||
export * from './effects/router.effects';
|
||||
export * from './effects/snackbar.effects';
|
||||
|
||||
export * from './models/ai-search-by-term-payload';
|
||||
export * from './models/delete-status.model';
|
||||
export * from './models/deleted-node-info.model';
|
||||
export * from './models/node-info.model';
|
||||
|
Reference in New Issue
Block a user