[ACS-8201] Knowledge Retrieval - getting AI response for one or more selected files (#10229)

* [ACS-8202] basic flow getting ai response for one or more selected files (#9944)

* ACS-8202 Getting list of agents

* ACS-8202 Mocked agents, used base api from hxi connector

* ACS-8202 Search Ai service

* ACS-8202 Small correction and mocked data

* ACS-8202 Renamed variable

* ACS-8202 Added documentation

* ACS-8202 Addressed PR comments

* ACS-8202 Type change

* ACS-8202 Reverted unwatend change

* ACS-8202 Reverted unwanted change

* ACS-8201 Small correction after rebasing with Angular 15

* [ACS-8398] Unit tests for agents and search ai  (#9974)

* ACS-8398 Unit tests for search ai api and agents api

* ACS-8398 Unit tests for getAnswer function from SearchAiApi, corrections for unit tests for SearchAiApi and AgentsApi

* ACS-8398 Unit tests for SearchAiService and AgentService

* [ACS-8210] Agent basic details popup (#9956)

* [ACS-8573] Allow user to ask question without file selection

* [ACS-8312] Display warning about losing response (#10059)

* [ACS-8312] Display warning about losing response

* [ACS-8312] Display warning about losing response - fixes

* [ACS-8432] Sending all file types to HX instead of only the text file types (#10087)

* ACS-8201 Fixed issues after rebase

* [ACS-8588] Navigation is triggered twice when leaving Knowledge Retrieval page (#10132)

* [ACS-8588] Navigation is triggered twice when leaving Knowledge Retrieval page

* [ACS-8588] Navigation is triggered twice when leaving Knowledge Retrieval page - review fixes

* [ACS-8588] Navigation is triggered twice when leaving Knowledge Retrieval page - review fixes 2

* [ACS-8399] Integrate all changes with backend (#10163)

* Answers endpoint fix (#10176)

* [ACS-8664] generic question redirection to hx insight (#10174)

* ACS-8664 Loading HX insight url

* ACS-8664 Added documentation for loading config of Knowledge Retrieval

* ACS-8664 Unit tests

* ACS-8664 Fixed unit tests

* ACS-8664 Fixed unit tests after rebase

* ACS-8664 Addressed comment

* ACS-8201 Fixed issues after rebase

* [ACS-8695] Getting Agent avatar (#10189)

* [ACS-8695] Getting Agent avatar

* [ACS-8695] Getting Agent avatar - on image load error

* [ACS-8695] Getting Agent avatar - removed getAgentAvatar call (#10209)

* [ACS-8201] Review fixes

---------

Co-authored-by: AleksanderSklorz <115619721+AleksanderSklorz@users.noreply.github.com>
Co-authored-by: Aleksander Sklorz <Aleksander.Sklorz@hyland.com>
This commit is contained in:
jacekpluta 2024-09-19 12:42:50 +02:00 committed by GitHub
parent 6a40e2a25e
commit 797b800bd6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 1848 additions and 71 deletions

View File

@ -368,6 +368,7 @@ for more information about installing and using the source code.
| Name | Description | Source link | | Name | Description | Source link |
| ---- | ----------- | ----------- | | ---- | ----------- | ----------- |
| [Agent Service](content-services/services/agent.service.md) | Manages agents in Content Services. | [Source](../lib/content-services/src/lib/agent/services/agent.service.ts) |
| [Audit Service](content-services/services/audit.service.md) | Manages Audit apps and entries. | [Source](../lib/content-services/src/lib/audit/audit.service.ts) | | [Audit Service](content-services/services/audit.service.md) | Manages Audit apps and entries. | [Source](../lib/content-services/src/lib/audit/audit.service.ts) |
| [Card View Content Update Service](content-services/services/card-view-content-update.service.md) | Manages Card View properties in the content services environment. Implements BaseCardViewContentUpdate. | [Source](../lib/content-services/src/lib/common/services/card-view-content-update.service.ts) | | | [Card View Content Update Service](content-services/services/card-view-content-update.service.md) | Manages Card View properties in the content services environment. Implements BaseCardViewContentUpdate. | [Source](../lib/content-services/src/lib/common/services/card-view-content-update.service.ts) | |
| [Category tree datasource service](content-services/services/category-tree-datasource.service.md) | Datasource service for category tree. | [Source](../lib/content-services/src/lib/category/services/category-tree-datasource.service.ts) | | [Category tree datasource service](content-services/services/category-tree-datasource.service.md) | Datasource service for category tree. | [Source](../lib/content-services/src/lib/category/services/category-tree-datasource.service.ts) |
@ -383,6 +384,7 @@ for more information about installing and using the source code.
| [Node Comments Service](content-services/services/node-comments.service.md) | Adds and retrieves comments for nodes in Content Services. | [Source](../lib/content-services/src/lib/node-comments/services/node-comments.service.ts) | | [Node Comments Service](content-services/services/node-comments.service.md) | Adds and retrieves comments for nodes in Content Services. | [Source](../lib/content-services/src/lib/node-comments/services/node-comments.service.ts) |
| [Node permission dialog service](content-services/services/node-permission-dialog.service.md) | Displays dialogs to let the user set node permissions. | [Source](../lib/content-services/src/lib/permission-manager/services/node-permission-dialog.service.ts) | | [Node permission dialog service](content-services/services/node-permission-dialog.service.md) | Displays dialogs to let the user set node permissions. | [Source](../lib/content-services/src/lib/permission-manager/services/node-permission-dialog.service.ts) |
| [Node Permission service](content-services/services/node-permission.service.md) | Manages role permissions for content nodes. | [Source](../lib/content-services/src/lib/permission-manager/services/node-permission.service.ts) | | [Node Permission service](content-services/services/node-permission.service.md) | Manages role permissions for content nodes. | [Source](../lib/content-services/src/lib/permission-manager/services/node-permission.service.ts) |
| [Search Ai Service](content-services/services/search-ai.service.md) | Manages search AI in Content Services. | [Source](../lib/content-services/src/lib/search-ai/services/search-ai.service.ts) |
| [Search filter service](content-services/services/search-filter.service.md) | Registers widgets for use with the Search Filter component. | [Source](../lib/content-services/src/lib/search/services/search-filter.service.ts) | | [Search filter service](content-services/services/search-filter.service.md) | Registers widgets for use with the Search Filter component. | [Source](../lib/content-services/src/lib/search/services/search-filter.service.ts) |
| [Search Query Builder service](content-services/services/search-query-builder.service.md) | Stores information from all the custom search and faceted search widgets, compiles and runs the final search query. | [Source](../lib/content-services/src/lib/search/services/search-query-builder.service.ts) | | [Search Query Builder service](content-services/services/search-query-builder.service.md) | Stores information from all the custom search and faceted search widgets, compiles and runs the final search query. | [Source](../lib/content-services/src/lib/search/services/search-query-builder.service.ts) |
| [Security Controls service](content-services/services/security-controls.service.md) | Manages security groups & marks in Content Services. | [Source](../lib/content-services/src/lib/security/services/security-controls-groups-marks-security.service.ts) | | [Security Controls service](content-services/services/security-controls.service.md) | Manages security groups & marks in Content Services. | [Source](../lib/content-services/src/lib/security/services/security-controls-groups-marks-security.service.ts) |

View File

@ -0,0 +1,27 @@
---
Title: Agent service
Added: v7.0.0-alpha.3
Status: Active
Last reviewed: 2024-07-12
---
# [Agent service](../../../lib/content-services/src/lib/agent/services/agent.service.ts "Defined in agent.service.ts")
Manages agents in Content Services.<br/>
<b>In order to use this service, you need to have the HX Insights Connector (additional ACS module) installed.</b>
## Class members
### Methods
- **getAgents**(): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`AgentPaging`](../../../lib/js-api/src/api/content-rest-api/docs/AgentsApi.md#agentpaging)`>`<br/>
Gets all agents.
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`AgentPaging`](../../../lib/js-api/src/api/content-rest-api/docs/AgentsApi.md#agentpaging)`>` - AgentPaging object containing the agents.
## Details
See the
[Agents API](../../../lib/js-api/src/api/content-rest-api/docs/AgentsApi.md) for more information about the types returned by [Agent
service](agent.service.md) methods and for the implementation of the REST API the service is
based on.

View File

@ -0,0 +1,43 @@
---
Title: Search Ai service
Added: v7.0.0-alpha.3
Status: Active
Last reviewed: 2024-07-12
---
# [Search Ai service](../../../lib/content-services/src/lib/search-ai/services/search-ai.service.ts "Defined in search-ai.service.ts")
Manages search AI in Content Services.
<b>In order to use this service, you need to have the HX Insights Connector (additional ACS module) installed.</b>
## Class members
### Methods
- **updateSearchAiInputState**(state: `SearchAiInputState`): `void`<br/>
Update the state of the search AI input.
- _state:_ `SearchAiInputState` - The new state of the search AI input.
- **ask**(question: [`QuestionRequest`](../../../lib/js-api/src/api/content-rest-api/docs/SearchAiApi.md#questionrequest)): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`QuestionModel`](../../../lib/js-api/src/api/content-rest-api/docs/SearchAiApi.md#questionmodel)`>`<br/>
Ask a question to the AI.
- _question:_ [`QuestionRequest`](../../../lib/js-api/src/api/content-rest-api/docs/SearchAiApi.md#questionrequest) - The question to ask.
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`QuestionModel`](../../../lib/js-api/src/api/content-rest-api/docs/SearchAiApi.md#questionmodel)`>` - QuestionModel object containing information about questions.
- **getAnswer**(questionId: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`AiAnswerEntry`](../../../lib/js-api/src/api/content-rest-api/docs/SearchAiApi.md#aianswerentry)`>`<br/>
Get an answer to specific question.
- _questionId:_ `string` - The ID of the question to get an answer for.
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`AiAnswerEntry`](../../../lib/js-api/src/api/content-rest-api/docs/SearchAiApi.md#aianswerentry)`>` - AiAnswerEntry object containing the answer.
- **getConfig**(): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`KnowledgeRetrievalConfigEntry`](../../../lib/js-api/src/api/content-rest-api/docs/SearchAiApi.md#knowledgeretrievalconfigentry)`>`<br/>
Get the knowledge retrieval configuration.
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`KnowledgeRetrievalConfigEntry`](../../../lib/js-api/src/api/content-rest-api/docs/SearchAiApi.md#knowledgeretrievalconfigentry)`>` - KnowledgeRetrievalConfigEntry object containing the configuration.
- **checkSearchAvailability**(selectedNodesState: `SelectionState`, maxSelectedNodes: `number`): `string`<br/>
Check if using of search is possible (if all conditions are met).
- _selectedNodesState:_ `SelectionState` - information about selected nodes.
- _maxSelectedNodes:_ `number` - max number of selected nodes. Default 100.
- **Returns** `string` - string with error if any condition is not met, empty string otherwise.
## Details
See the
[Search Ai API](../../../lib/js-api/src/api/content-rest-api/docs/SearchAiApi.md) for more information about the types returned by [Search Ai
service](search-ai.service.md) methods and for the implementation of the REST API the service is
based on.

View File

@ -12,6 +12,7 @@ backend services have been tested with each released version of ADF.
## Versions ## Versions
- [v7.0.0-alpha.3](#v700-alpha3)
- [v6.8.0](#v680) - [v6.8.0](#v680)
- [v6.7.0](#v670) - [v6.7.0](#v670)
- [v6.4.0](#v640) - [v6.4.0](#v640)
@ -46,6 +47,16 @@ backend services have been tested with each released version of ADF.
- [v2.1.0](#v210) - [v2.1.0](#v210)
- [v2.0.0](#v200) - [v2.0.0](#v200)
## v7.0.0-alpha.3
<!--7.0.0-alpha.3 start-->
- [AgentService](content-services/services/agent.service.md)
- [SearchAiService](content-services/services/search-ai.service.md)
<!--7.0.0-alpha.3 end-->
## v6.8.0 ## v6.8.0
<!--680 start--> <!--680 start-->

View File

@ -0,0 +1,18 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './public-api';

View File

@ -0,0 +1,18 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './services/agent.service';

View File

@ -0,0 +1,72 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TestBed } from '@angular/core/testing';
import { CoreTestingModule } from '@alfresco/adf-core';
import { AgentService } from './agent.service';
import { Agent, AgentPaging } from '@alfresco/js-api';
const agent1: Agent = {
id: '1',
name: 'HR Agent',
description: 'Your Claims Doc Agent streamlines the extraction, analysis, and management of data from insurance claims documents.',
avatarUrl: ''
};
const agent2: Agent = {
id: '2',
name: 'Policy Agent',
description: 'Your Claims Doc Agent streamlines the extraction, analysis, and management of data from insurance claims documents.',
avatarUrl: ''
};
const agentPagingObjectMock: AgentPaging = {
list: {
entries: [
{
entry: agent1
},
{
entry: agent2
}
]
}
};
const agentListMock: Agent[] = [agent1, agent2];
describe('AgentService', () => {
let agentService: AgentService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule]
});
agentService = TestBed.inject(AgentService);
});
it('should load agents', (done) => {
spyOn(agentService.agentsApi, 'getAgents').and.returnValue(Promise.resolve(agentPagingObjectMock));
agentService.getAgents().subscribe((pagingResponse) => {
expect(pagingResponse).toEqual(agentListMock);
expect(agentService.agentsApi.getAgents).toHaveBeenCalled();
done();
});
});
});

View File

@ -0,0 +1,61 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { Agent, AgentsApi } from '@alfresco/js-api';
import { BehaviorSubject, from, Observable, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { AlfrescoApiService } from '../../services';
@Injectable({
providedIn: 'root'
})
export class AgentService {
private _agentsApi: AgentsApi;
private agents = new BehaviorSubject<Agent[]>([]);
get agentsApi(): AgentsApi {
this._agentsApi = this._agentsApi ?? new AgentsApi(this.apiService.getInstance());
return this._agentsApi;
}
agents$ = this.agents.asObservable();
constructor(private apiService: AlfrescoApiService) {}
/**
* Gets all agents from cache. If cache is empty, fetches agents from backend.
*
* @returns Agent[] list containing agents.
*/
getAgents(): Observable<Agent[]> {
return this.agents$.pipe(
switchMap((agentsList) => {
if (agentsList.length) {
return of(agentsList);
}
return from(this.agentsApi.getAgents()).pipe(
map((paging) => {
const agentEntries = paging.list.entries.map((agentEntry) => agentEntry.entry);
this.agents.next(agentEntries);
return agentEntries;
})
);
})
);
}
}

View File

@ -701,5 +701,13 @@
"JOIN_CANCELED": "Canceled the request to join the library", "JOIN_CANCELED": "Canceled the request to join the library",
"JOIN_REQUESTED": "Request sent to join this library" "JOIN_REQUESTED": "Request sent to join this library"
} }
},
"KNOWLEDGE_RETRIEVAL": {
"SEARCH": {
"WARNINGS": {
"TOO_MANY_FILES_SELECTED": "Please select no more than {{ maxFiles }} files.",
"FOLDER_SELECTED": "Folders are not compatible with AI Agents."
}
}
} }
} }

View File

@ -0,0 +1,18 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './public-api';

View File

@ -0,0 +1,21 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface SearchAiInputState {
active: boolean;
selectedAgentId?: string;
}

View File

@ -0,0 +1,19 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './services/search-ai.service';
export * from './models/search-ai-input-state';

View File

@ -0,0 +1,220 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TestBed } from '@angular/core/testing';
import { AiAnswerEntry, KnowledgeRetrievalConfigEntry, Node, QuestionModel, QuestionRequest } from '@alfresco/js-api';
import { ContentTestingModule } from '../../testing/content.testing.module';
import { SearchAiService } from './search-ai.service';
import { SearchAiInputState } from '../models/search-ai-input-state';
import { TranslateService } from '@ngx-translate/core';
describe('SearchAiService', () => {
let service: SearchAiService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ContentTestingModule]
});
service = TestBed.inject(SearchAiService);
});
describe('ask', () => {
it('should load information about question', (done) => {
const question: QuestionModel = {
question: 'some question',
questionId: 'some id',
restrictionQuery: { nodesIds: ['nodeId1', 'nodeId2'] }
};
spyOn(service.searchAiApi, 'ask').and.returnValue(Promise.resolve(question));
const questionRequest: QuestionRequest = {
question: 'some question',
nodeIds: ['nodeId1', 'nodeId2'],
agentId: 'some id'
};
service.ask(questionRequest).subscribe((questionResponse) => {
expect(questionResponse).toBe(question);
expect(service.searchAiApi.ask).toHaveBeenCalledWith([questionRequest]);
done();
});
});
});
describe('getAnswer', () => {
it('should load information about question', (done) => {
const questionId = 'some id';
const answer: AiAnswerEntry = {
entry: {
answer: 'Some answer 1',
questionId,
references: [
{
referenceId: 'some reference id 1',
referenceText: 'some reference text 1'
}
]
}
};
spyOn(service.searchAiApi, 'getAnswer').and.returnValue(Promise.resolve(answer));
service.getAnswer(questionId).subscribe((answerResponse) => {
expect(answerResponse).toBe(answer);
expect(service.searchAiApi.getAnswer).toHaveBeenCalledWith(questionId);
done();
});
});
});
describe('getConfig', () => {
it('should load knowledge retrieval configuration', (done) => {
const config: KnowledgeRetrievalConfigEntry = {
entry: {
knowledgeRetrievalUrl: 'https://some-url'
}
};
spyOn(service.searchAiApi, 'getConfig').and.returnValue(Promise.resolve(config));
service.getConfig().subscribe((configResponse) => {
expect(configResponse).toBe(config);
expect(service.searchAiApi.getConfig).toHaveBeenCalled();
done();
});
});
});
describe('updateSearchAiInputState', () => {
it('should trigger toggleSearchAiInput$', () => {
const state: SearchAiInputState = {
active: true,
selectedAgentId: 'some id'
};
service.updateSearchAiInputState(state);
service.toggleSearchAiInput$.subscribe((receivedState) => {
expect(receivedState).toBe(state);
});
});
});
describe('checkSearchAvailability', () => {
let translateService: TranslateService;
const tooManyFilesSelectedError = 'Please select no more than 100 files.';
const folderSelectedError = 'Folders are not compatible with AI Agents.';
beforeEach(() => {
translateService = TestBed.inject(TranslateService);
spyOn(translateService, 'instant').and.callFake((key) => {
switch (key) {
case 'KNOWLEDGE_RETRIEVAL.SEARCH.WARNINGS.TOO_MANY_FILES_SELECTED':
return tooManyFilesSelectedError;
case 'KNOWLEDGE_RETRIEVAL.SEARCH.WARNINGS.FOLDER_SELECTED':
return folderSelectedError;
default:
return '';
}
});
});
it('should not return error if user did not select any files', () => {
expect(
service.checkSearchAvailability({
count: 0,
nodes: [],
libraries: [],
isEmpty: true
})
).toEqual('');
});
it('should return error for too many files selected', () => {
expect(
service.checkSearchAvailability({
count: 101,
nodes: [],
libraries: [],
isEmpty: false
})
).toBe(tooManyFilesSelectedError);
expect(translateService.instant).toHaveBeenCalledWith('KNOWLEDGE_RETRIEVAL.SEARCH.WARNINGS.TOO_MANY_FILES_SELECTED', {
maxFiles: 100,
key: 'KNOWLEDGE_RETRIEVAL.SEARCH.WARNINGS.TOO_MANY_FILES_SELECTED'
});
});
it('should return error for folder selected', () => {
expect(
service.checkSearchAvailability({
count: 1,
nodes: [
{
entry: {
isFolder: true
} as Node
}
],
libraries: [],
isEmpty: false
})
).toBe(folderSelectedError);
});
it('should return error for folder and if non text mime type node is selected', () => {
expect(
service.checkSearchAvailability({
count: 1,
nodes: [
{
entry: {
isFolder: true,
content: {
mimeType: 'some mime type',
mimeTypeName: 'some mime type',
sizeInBytes: 100
}
} as Node
}
],
libraries: [],
isEmpty: false
})
).toBe(folderSelectedError);
});
it('should return more than one error if more validators detected issues', () => {
expect(
service.checkSearchAvailability({
count: 101,
nodes: [
{
entry: {
isFolder: true,
content: {
mimeType: 'image/jpeg',
mimeTypeName: 'image/jpeg',
sizeInBytes: 100
}
} as Node
}
],
libraries: [],
isEmpty: false
})
).toBe(`${tooManyFilesSelectedError} ${folderSelectedError}`);
});
});
});

View File

@ -0,0 +1,110 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { AiAnswerEntry, KnowledgeRetrievalConfigEntry, QuestionModel, QuestionRequest, SearchAiApi } from '@alfresco/js-api';
import { BehaviorSubject, from, Observable } from 'rxjs';
import { SelectionState } from '@alfresco/adf-extensions';
import { TranslateService } from '@ngx-translate/core';
import { SearchAiInputState } from '../models/search-ai-input-state';
import { AlfrescoApiService } from '../../services';
@Injectable({
providedIn: 'root'
})
export class SearchAiService {
private toggleSearchAiInput = new BehaviorSubject<SearchAiInputState>({
active: false
});
private _searchAiApi: SearchAiApi;
get searchAiApi(): SearchAiApi {
this._searchAiApi = this._searchAiApi ?? new SearchAiApi(this.apiService.getInstance());
return this._searchAiApi;
}
toggleSearchAiInput$ = this.toggleSearchAiInput.asObservable();
constructor(
private apiService: AlfrescoApiService,
private translateService: TranslateService
) {}
/**
* Update the state of the search AI input.
*
* @param state The new state of the search AI input.
*/
updateSearchAiInputState(state: SearchAiInputState): void {
this.toggleSearchAiInput.next(state);
}
/**
* Ask a question to the AI.
*
* @param question The question to ask.
* @returns QuestionModel object containing information about questions.
*/
ask(question: QuestionRequest): Observable<QuestionModel> {
return from(this.searchAiApi.ask([question]));
}
/**
* Get an answer to specific question.
*
* @param questionId The ID of the question to get an answer for.
* @returns AiAnswerEntry object containing the answer.
*/
getAnswer(questionId: string): Observable<AiAnswerEntry> {
return from(this.searchAiApi.getAnswer(questionId));
}
/**
* Get the knowledge retrieval configuration.
*
* @returns KnowledgeRetrievalConfigEntry object containing the configuration.
*/
getConfig(): Observable<KnowledgeRetrievalConfigEntry> {
return from(this.searchAiApi.getConfig());
}
/**
* Check if using of search is possible (if all conditions are met).
*
* @param selectedNodesState information about selected nodes.
* @param maxSelectedNodes max number of selected nodes. Default 100.
* @returns string with error if any condition is not met, empty string otherwise.
*/
checkSearchAvailability(selectedNodesState: SelectionState, maxSelectedNodes = 100): string {
const messages: {
key: string;
[parameter: string]: number | string;
}[] = [];
if (selectedNodesState.count > maxSelectedNodes) {
messages.push({
key: 'KNOWLEDGE_RETRIEVAL.SEARCH.WARNINGS.TOO_MANY_FILES_SELECTED',
maxFiles: maxSelectedNodes
});
}
if (selectedNodesState.nodes.some((node) => node.entry.isFolder)) {
messages.push({
key: 'KNOWLEDGE_RETRIEVAL.SEARCH.WARNINGS.FOLDER_SELECTED'
});
}
return messages.map((message) => this.translateService.instant(message.key, message)).join(' ');
}
}

View File

@ -49,6 +49,8 @@ export * from './lib/prediction/index';
export * from './lib/legal-hold/index'; export * from './lib/legal-hold/index';
export * from './lib/api-factories'; export * from './lib/api-factories';
export * from './lib/mock/alfresco-api.service.mock'; export * from './lib/mock/alfresco-api.service.mock';
export * from './lib/agent/index';
export * from './lib/search-ai/index';
export * from './lib/content.module'; export * from './lib/content.module';
export * from './lib/material.module'; export * from './lib/material.module';

View File

@ -47,7 +47,8 @@ export enum AppConfigValues {
STORAGE_PREFIX = 'application.storagePrefix', STORAGE_PREFIX = 'application.storagePrefix',
NOTIFY_DURATION = 'notificationDefaultDuration', NOTIFY_DURATION = 'notificationDefaultDuration',
CONTENT_TICKET_STORAGE_LABEL = 'ticket-ECM', CONTENT_TICKET_STORAGE_LABEL = 'ticket-ECM',
PROCESS_TICKET_STORAGE_LABEL = 'ticket-BPM' PROCESS_TICKET_STORAGE_LABEL = 'ticket-BPM',
UNSAVED_CHANGES_MODAL_HIDDEN = 'unsaved_changes__modal_hidden'
} }
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow

View File

@ -1,5 +1,5 @@
<div class="adf-avatar"> <div class="adf-avatar">
<img *ngIf="src; else initialsTemplate" class="adf-avatar__image" [src]="src" [alt]="initials" [title]="tooltip" /> <img (error)="src = ''" *ngIf="src; else initialsTemplate" class="adf-avatar__image" [src]="src" [alt]="initials" [title]="tooltip" />
</div> </div>
<ng-template #initialsTemplate> <ng-template #initialsTemplate>

View File

@ -32,6 +32,8 @@ describe('AvatarComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
}); });
const getAvatarImageElement = (): HTMLImageElement => fixture.nativeElement.querySelector('.adf-avatar__image');
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
@ -46,8 +48,7 @@ describe('AvatarComponent', () => {
it('should display image when src is provided', () => { it('should display image when src is provided', () => {
component.src = 'path/to/image.jpg'; component.src = 'path/to/image.jpg';
fixture.detectChanges(); fixture.detectChanges();
const imgElement: HTMLImageElement = fixture.nativeElement.querySelector('.adf-avatar__image'); expect(getAvatarImageElement().src).toContain(component.src);
expect(imgElement.src).toContain(component.src);
}); });
it('should use default initials when not provided', () => { it('should use default initials when not provided', () => {
@ -67,7 +68,7 @@ describe('AvatarComponent', () => {
component.size = '48px'; component.size = '48px';
fixture.detectChanges(); fixture.detectChanges();
const style = getComputedStyle(fixture.nativeElement.querySelector('.adf-avatar__image')); const style = getComputedStyle(getAvatarImageElement());
expect(style.width).toBe('48px'); expect(style.width).toBe('48px');
expect(style.height).toBe('48px'); expect(style.height).toBe('48px');
}); });
@ -76,14 +77,22 @@ describe('AvatarComponent', () => {
component.cursor = 'pointer'; component.cursor = 'pointer';
fixture.detectChanges(); fixture.detectChanges();
const style = getComputedStyle(fixture.nativeElement.querySelector('.adf-avatar__image')); const style = getComputedStyle(getAvatarImageElement());
expect(style.cursor).toBe('pointer'); expect(style.cursor).toBe('pointer');
}); });
it('should display tooltip when provided', () => { it('should display tooltip when provided', () => {
component.tooltip = 'User Tooltip'; component.tooltip = 'User Tooltip';
fixture.detectChanges(); fixture.detectChanges();
const avatarElement: HTMLElement = fixture.nativeElement.querySelector('.adf-avatar__image'); expect(getAvatarImageElement().getAttribute('title')).toBe('User Tooltip');
expect(avatarElement.getAttribute('title')).toBe('User Tooltip'); });
it('should call onImageError when the image fails to load', () => {
component.src = 'path/to/image.jpg';
fixture.detectChanges();
getAvatarImageElement().dispatchEvent(new Event('error'));
expect(component.src).toEqual('');
}); });
}); });

View File

@ -1,29 +1,35 @@
<h1 mat-dialog-title class="adf-unsaved-changes-dialog-title"> <h1 mat-dialog-title class="adf-unsaved-changes-dialog-header">
{{ 'CORE.DIALOG.UNSAVED_CHANGES.TITLE' | translate }} {{ dialogData.headerText | translate }}
<button <button
data-automation-id="adf-unsaved-changes-dialog-close-button" data-automation-id="adf-unsaved-changes-dialog-close-button"
class="adf-unsaved-changes-dialog-header-close-button"
mat-icon-button mat-icon-button
[title]="'CLOSE' | translate" [title]="'CLOSE' | translate"
[mat-dialog-close]="false"> [mat-dialog-close]="false">
<mat-icon>close</mat-icon> <mat-icon>close</mat-icon>
</button> </button>
</h1> </h1>
<mat-dialog-content> <mat-dialog-content class="adf-unsaved-changes-dialog-content">
{{ 'CORE.DIALOG.UNSAVED_CHANGES.DESCRIPTION' | translate }} {{ dialogData.descriptionText | translate }}
<div class="adf-unsaved-changes-dialog-content-checkbox" *ngIf="dialogData.checkboxText.length">
<mat-checkbox data-automation-id="adf-unsaved-changes-dialog-content-checkbox"
(change)="onToggleCheckboxPreferences($event)"
>{{ dialogData.checkboxText | translate }}</mat-checkbox>
</div>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions align="end"> <mat-dialog-actions align="end" class="adf-unsaved-changes-dialog-actions">
<button <button
data-automation-id="adf-unsaved-changes-dialog-cancel-button" data-automation-id="adf-unsaved-changes-dialog-cancel-button"
mat-button mat-button
[mat-dialog-close]="false" [mat-dialog-close]="false"
class="adf-unsaved-changes-dialog-cancel-button"> class="adf-unsaved-changes-dialog-actions-cancel-button">
{{ 'CANCEL' | translate | titlecase }} {{ 'CANCEL' | translate | titlecase }}
</button> </button>
<button <button
data-automation-id="adf-unsaved-changes-dialog-discard-changes-button" data-automation-id="adf-unsaved-changes-dialog-discard-changes-button"
mat-button mat-button
[mat-dialog-close]="true" [mat-dialog-close]="true"
class="adf-unsaved-changes-dialog-discard-changes-button"> class="adf-unsaved-changes-dialog-actions-discard-changes-button">
{{ 'CORE.DIALOG.UNSAVED_CHANGES.DISCARD_CHANGES_BUTTON' | translate }} {{ dialogData.confirmButtonText | translate }}
</button> </button>
</mat-dialog-actions> </mat-dialog-actions>

View File

@ -1,32 +1,61 @@
adf-unsaved-changes-dialog { .adf-unsaved-changes-dialog {
margin-top: -4px;
display: block;
.adf-unsaved-changes-dialog { .adf-unsaved-changes-dialog {
&-title { &-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
font-size: 16px; font-size: 18px;
font-weight: bold; font-weight: 700;
margin-bottom: 16px;
height: 24px;
&-close-button {
margin-right: -16px;
margin-bottom: 2px;
}
&::before {
display: none;
}
} }
&-cancel-button { &-content {
background-color: var(--adf-secondary-button-background); padding: 0 8px 0 0;
margin-right: 4px; overflow: unset;
color: var(--adf-secondary-modal-text-color);
&-checkbox {
margin-top: 20px;
label {
color: var(--adf-secondary-modal-text-color);
}
}
} }
&-discard-changes-button { &-actions {
color: var(--theme-warn-color-default-contrast); margin-top: 11px;
background-color: var(--adf-danger-button-background); margin-bottom: 1px;
min-width: 143px; padding: 0;
} align-items: flex-end;
&-cancel-button, &-discard-changes-button { &-cancel-button {
padding: 4px 14px; background-color: var(--adf-secondary-button-background);
height: 32px; margin-right: 4px;
display: flex; }
align-items: center;
&-discard-changes-button {
color: white;
background-color: var(--adf-error-color);
}
&-cancel-button,
&-discard-changes-button {
padding: 4px 12px;
height: 32px;
display: flex;
align-items: center;
}
} }
} }
} }

View File

@ -16,39 +16,61 @@
*/ */
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreTestingModule, UnsavedChangesDialogComponent } from '@alfresco/adf-core'; import { AppConfigValues, CoreTestingModule, UnsavedChangesDialogComponent, UserPreferencesService } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core'; import { DebugElement } from '@angular/core';
import { MatDialogClose } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogClose } from '@angular/material/dialog';
import { UnsavedChangesDialogData } from './unsaved-changes-dialog.model';
describe('UnsavedChangesDialog', () => { describe('UnsavedChangesDialog', () => {
let fixture: ComponentFixture<UnsavedChangesDialogComponent>; let fixture: ComponentFixture<UnsavedChangesDialogComponent>;
let userPreferencesService: UserPreferencesService;
let savePreferenceCheckbox: DebugElement;
beforeEach(() => { const setupBeforeEach = (unsavedChangesDialogData?: UnsavedChangesDialogData) => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [CoreTestingModule] imports: [CoreTestingModule],
providers: [
{
provide: MAT_DIALOG_DATA,
useValue: unsavedChangesDialogData ?? {}
}
]
}); });
userPreferencesService = TestBed.inject(UserPreferencesService);
fixture = TestBed.createComponent(UnsavedChangesDialogComponent); fixture = TestBed.createComponent(UnsavedChangesDialogComponent);
fixture.detectChanges(); fixture.detectChanges();
}); savePreferenceCheckbox = fixture.debugElement.query(By.css('[data-automation-id="adf-unsaved-changes-dialog-content-checkbox"]'));
};
describe('Close icon button', () => { const getElements = (): { header: HTMLElement; content: HTMLElement; discardChangesButton: HTMLElement } => {
let closeIconButton: DebugElement; const header = fixture.nativeElement.querySelector('.adf-unsaved-changes-dialog-header');
const content = fixture.nativeElement.querySelector('.adf-unsaved-changes-dialog-content');
const discardChangesButton = fixture.nativeElement.querySelector('.adf-unsaved-changes-dialog-actions-discard-changes-button');
return { header, content, discardChangesButton };
};
describe('when data is not present in dialog', () => {
beforeEach(() => { beforeEach(() => {
closeIconButton = fixture.debugElement.query(By.css('[data-automation-id="adf-unsaved-changes-dialog-close-button"]')); setupBeforeEach();
}); });
it('should have assigned dialog close button with false as result', () => { it('should display correct text if there is no data object', () => {
expect(closeIconButton.injector.get(MatDialogClose).dialogResult).toBeFalse(); const { header, content, discardChangesButton } = getElements();
expect(header.textContent).toContain('CORE.DIALOG.UNSAVED_CHANGES.TITLE');
expect(content.textContent).toContain('CORE.DIALOG.UNSAVED_CHANGES.DESCRIPTION');
expect(discardChangesButton.textContent).toContain('CORE.DIALOG.UNSAVED_CHANGES.DISCARD_CHANGES_BUTTON');
}); });
it('should have displayed correct icon', () => { it('should have assigned dialog close button with true as result', () => {
expect(closeIconButton.nativeElement.textContent).toBe('close'); expect(
fixture.debugElement
.query(By.css('[data-automation-id="adf-unsaved-changes-dialog-discard-changes-button"]'))
.injector.get(MatDialogClose).dialogResult
).toBeTrue();
}); });
});
describe('Cancel button', () => {
it('should have assigned dialog close button with false as result', () => { it('should have assigned dialog close button with false as result', () => {
expect( expect(
fixture.debugElement.query(By.css('[data-automation-id="adf-unsaved-changes-dialog-cancel-button"]')).injector.get(MatDialogClose) fixture.debugElement.query(By.css('[data-automation-id="adf-unsaved-changes-dialog-cancel-button"]')).injector.get(MatDialogClose)
@ -57,13 +79,38 @@ describe('UnsavedChangesDialog', () => {
}); });
}); });
describe('Discard changes button', () => { describe('when data is present in dialog', () => {
it('should have assigned dialog close button with true as result', () => { let userPreferencesServiceSetSpy: jasmine.Spy<(property: string, value: any) => void>;
expect(
fixture.debugElement beforeEach(() => {
.query(By.css('[data-automation-id="adf-unsaved-changes-dialog-discard-changes-button"]')) setupBeforeEach({
.injector.get(MatDialogClose).dialogResult headerText: 'headerText',
).toBeTrue(); descriptionText: 'descriptionText',
confirmButtonText: 'confirmButtonText',
checkboxText: 'checkboxText'
});
userPreferencesServiceSetSpy = spyOn(userPreferencesService, 'set');
fixture.detectChanges();
});
it('should display correct text if there is data object', () => {
const { header, content, discardChangesButton } = getElements();
expect(header.textContent).toContain('headerText');
expect(content.textContent).toContain('descriptionText checkboxText');
expect(discardChangesButton.textContent).toContain('confirmButtonText');
});
it('should call UserPreferences Service and update it to true when checkbox is checked', () => {
const event = { checked: true };
savePreferenceCheckbox.triggerEventHandler('change', event);
expect(userPreferencesServiceSetSpy).toHaveBeenCalledWith(AppConfigValues.UNSAVED_CHANGES_MODAL_HIDDEN, 'true');
});
it('should call UserPreferences Service and update it to false when checkbox is unchecked', () => {
const event = { checked: false };
savePreferenceCheckbox.triggerEventHandler('change', event);
expect(userPreferencesServiceSetSpy).toHaveBeenCalledWith(AppConfigValues.UNSAVED_CHANGES_MODAL_HIDDEN, 'false');
}); });
}); });
}); });

View File

@ -15,22 +15,55 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, ViewEncapsulation } from '@angular/core'; import { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common'; import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { MatDialogModule } from '@angular/material/dialog'; import { UnsavedChangesDialogData } from './unsaved-changes-dialog.model';
import { MatCheckboxChange, MatCheckboxModule } from '@angular/material/checkbox';
import { ReactiveFormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { CommonModule } from '@angular/common';
import { UserPreferencesService } from '../../common';
import { AppConfigValues } from '../../app-config';
/** /**
* Dialog which informs about unsaved changes. Allows discard them and proceed or close dialog and stop proceeding. * Dialog which informs about unsaved changes. Allows discard them and proceed or close dialog and stop proceeding.
* Can be customized with data object - UnsavedChangesDialogData.
* If data.checkboxText is provided, checkbox will be displayed with the checkbox description.
* If data.confirmButtonText is provided, it will be displayed on the confirm button.
* If data.headerText is provided, it will be displayed as the header.
* If data.descriptionText is provided, it will be displayed as dialog content.
*/ */
@Component({ @Component({
selector: 'adf-unsaved-changes-dialog',
standalone: true, standalone: true,
imports: [CommonModule, MatDialogModule, TranslateModule, MatButtonModule, MatIconModule], selector: 'adf-unsaved-changes-dialog',
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
templateUrl: './unsaved-changes-dialog.component.html', templateUrl: './unsaved-changes-dialog.component.html',
styleUrls: ['./unsaved-changes-dialog.component.scss'] styleUrls: ['./unsaved-changes-dialog.component.scss'],
host: { class: 'adf-unsaved-changes-dialog' },
imports: [MatDialogModule, TranslateModule, MatButtonModule, MatIconModule, CommonModule, MatCheckboxModule, ReactiveFormsModule]
}) })
export class UnsavedChangesDialogComponent {} export class UnsavedChangesDialogComponent implements OnInit {
dialogData: UnsavedChangesDialogData;
constructor(@Inject(MAT_DIALOG_DATA) public data: UnsavedChangesDialogData, private userPreferencesService: UserPreferencesService) {}
ngOnInit() {
this.dialogData = {
headerText: this.data?.headerText ?? 'CORE.DIALOG.UNSAVED_CHANGES.TITLE',
descriptionText: this.data?.descriptionText ?? 'CORE.DIALOG.UNSAVED_CHANGES.DESCRIPTION',
confirmButtonText: this.data?.confirmButtonText ?? 'CORE.DIALOG.UNSAVED_CHANGES.DISCARD_CHANGES_BUTTON',
checkboxText: this.data?.checkboxText ?? ''
};
}
/**
* Sets 'unsaved_changes__modal_visible' checked state (true or false string) as new item in local storage.
*
* @param savePreferences - MatCheckboxChange object with information about checkbox state.
*/
onToggleCheckboxPreferences(savePreferences: MatCheckboxChange) {
this.userPreferencesService.set(AppConfigValues.UNSAVED_CHANGES_MODAL_HIDDEN, savePreferences.checked.toString());
}
}

View File

@ -0,0 +1,23 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface UnsavedChangesDialogData {
checkboxText?: string;
confirmButtonText?: string;
descriptionText?: string;
headerText?: string;
}

View File

@ -91,5 +91,15 @@ describe('UnsavedChangesGuard', () => {
}); });
afterClosed$.next(true); afterClosed$.next(true);
}); });
it('should return false if afterClosed subject value was undefined', (done) => {
guard.unsaved = true;
(guard.canDeactivate() as Observable<boolean>).subscribe((result) => {
expect(result).toBeFalse();
done();
});
afterClosed$.next(undefined);
});
}); });
}); });

View File

@ -20,7 +20,8 @@ import { CanDeactivate } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { UnsavedChangesDialogComponent } from './unsaved-changes-dialog.component'; import { UnsavedChangesDialogComponent } from './unsaved-changes-dialog.component';
import { tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { UnsavedChangesDialogData } from './unsaved-changes-dialog.model';
/** /**
* Guard responsible for protecting leaving page with unsaved changes. * Guard responsible for protecting leaving page with unsaved changes.
@ -30,6 +31,7 @@ import { tap } from 'rxjs/operators';
}) })
export class UnsavedChangesGuard implements CanDeactivate<any> { export class UnsavedChangesGuard implements CanDeactivate<any> {
unsaved = false; unsaved = false;
data: UnsavedChangesDialogData;
constructor(private dialog: MatDialog) {} constructor(private dialog: MatDialog) {}
@ -39,9 +41,17 @@ export class UnsavedChangesGuard implements CanDeactivate<any> {
* @returns boolean | Observable<boolean> true when there is no unsaved changes or changes can be discarded, false otherwise. * @returns boolean | Observable<boolean> true when there is no unsaved changes or changes can be discarded, false otherwise.
*/ */
canDeactivate(): boolean | Observable<boolean> { canDeactivate(): boolean | Observable<boolean> {
return this.unsaved ? return this.unsaved
this.dialog.open<UnsavedChangesDialogComponent, undefined, boolean>(UnsavedChangesDialogComponent, { ? this.dialog
maxWidth: 346 .open<UnsavedChangesDialogComponent>(UnsavedChangesDialogComponent, {
}).afterClosed().pipe(tap((confirmed) => this.unsaved = !confirmed)) : true; maxWidth: 346,
data: this.data
})
.afterClosed()
.pipe(
tap((confirmed) => (this.unsaved = !confirmed)),
map((confirmed) => !!confirmed)
)
: true;
} }
} }

View File

@ -86,8 +86,9 @@
--adf-header-icon-button-hover-color: $adf-ref-header-icon-color, --adf-header-icon-button-hover-color: $adf-ref-header-icon-color,
--adf-header-icon-button-pressed-color: $adf-ref-header-icon-color, --adf-header-icon-button-pressed-color: $adf-ref-header-icon-color,
--adf-header-icon-button-disabled-color: $adf-ref-header-icon-color, --adf-header-icon-button-disabled-color: $adf-ref-header-icon-color,
--adf-danger-button-background: $adf-danger-button-background, --adf-error-color: $adf-error-color,
--adf-secondary-button-background: $adf-secondary-button-background, --adf-secondary-button-background: $adf-secondary-button-background,
--adf-secondary-modal-text-color: $adf-secondary-modal-text-color,
--adf-display-external-property-widget-preview-selection-color: mat.get-color-from-palette($foreground, secondary-text) --adf-display-external-property-widget-preview-selection-color: mat.get-color-from-palette($foreground, secondary-text)
); );

View File

@ -121,6 +121,7 @@ $mat-notched-outline-trailing: '.mdc-notched-outline__trailing';
$mat-notched-outline-notch: '.mdc-notched-outline__notch'; $mat-notched-outline-notch: '.mdc-notched-outline__notch';
$mat-evolution-chip-set: '.mdc-evolution-chip-set'; $mat-evolution-chip-set: '.mdc-evolution-chip-set';
$mat-button-base: '.mat-mdc-button-base'; $mat-button-base: '.mat-mdc-button-base';
$mat-button-touch-target: '.mat-mdc-button-touch-target';
$mat-evolution-chip-text-label: '.mdc-evolution-chip__text-label'; $mat-evolution-chip-text-label: '.mdc-evolution-chip__text-label';
$cdk-overlay-pane: '.cdk-overlay-pane'; $cdk-overlay-pane: '.cdk-overlay-pane';
$cdk-drag-preview: '.cdk-drag-preview'; $cdk-drag-preview: '.cdk-drag-preview';

View File

@ -25,5 +25,6 @@ $adf-ref-metadata-property-panel-label-color: rgba(33, 33, 33, 0.24);
$adf-ref-metadata-property-panel-title-color: rgb(33, 33, 33); $adf-ref-metadata-property-panel-title-color: rgb(33, 33, 33);
$adf-ref-header-icon-color: inherit; $adf-ref-header-icon-color: inherit;
$adf-ref-header-icon-border-radius: 50%; $adf-ref-header-icon-border-radius: 50%;
$adf-danger-button-background: #ba1b1b; $adf-error-color: #ba1b1b;
$adf-secondary-button-background: #2121210d; $adf-secondary-button-background: #2121210d;
$adf-secondary-modal-text-color: #212121;

View File

@ -0,0 +1,36 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AgentPaging } from '../model/agentPaging';
import { BaseApi } from '../../hxi-connector-api/api/base.api';
/**
* Agents Api.
* In order to use this api, you need to have the HX Insights Connector (additional ACS module) installed.
*/
export class AgentsApi extends BaseApi {
/**
* Gets all agents.
*
* @returns AgentPaging object containing the agents.
*/
getAgents(): Promise<AgentPaging> {
return this.get({
path: '/agents'
});
}
}

View File

@ -18,6 +18,7 @@
export * from './types'; export * from './types';
export * from './actions.api'; export * from './actions.api';
export * from './activities.api'; export * from './activities.api';
export * from './agents.api';
export * from './audit.api'; export * from './audit.api';
export * from './categories.api'; export * from './categories.api';
export * from './comments.api'; export * from './comments.api';
@ -32,6 +33,7 @@ export * from './probes.api';
export * from './queries.api'; export * from './queries.api';
export * from './ratings.api'; export * from './ratings.api';
export * from './renditions.api'; export * from './renditions.api';
export * from './search-ai.api';
export * from './sharedlinks.api'; export * from './sharedlinks.api';
export * from './sites.api'; export * from './sites.api';
export * from './tags.api'; export * from './tags.api';

View File

@ -0,0 +1,64 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { QuestionModel, QuestionRequest, AiAnswerEntry, KnowledgeRetrievalConfigEntry } from '../model';
import { BaseApi } from '../../hxi-connector-api/api/base.api';
/**
* Search AI API.
*/
export class SearchAiApi extends BaseApi {
/**
* Ask a question to the AI.
*
* @param questions QuestionRequest array containing questions to ask.
* @returns QuestionModel object containing information about questions.
*/
ask(questions: QuestionRequest[]): Promise<QuestionModel> {
const agentId = questions[0].agentId;
return this.post({
path: `agents/${agentId}/questions`,
bodyParam: questions.map((questionRequest) => ({
question: questionRequest.question,
restrictionQuery: { nodesIds: questionRequest.nodeIds }
}))
}).then((response) => response.entry);
}
/**
* Get an answer to specific question.
*
* @param questionId The ID of the question to get an answer for.
* @returns AiAnswerEntry object containing the answer.
*/
getAnswer(questionId: string): Promise<AiAnswerEntry> {
return this.get({
path: `questions/${questionId}/answers/-default-`
});
}
/**
* Get the knowledge retrieval configuration.
*
* @returns KnowledgeRetrievalConfigEntry object containing the configuration.
*/
getConfig(): Promise<KnowledgeRetrievalConfigEntry> {
return this.get({
path: '/config/-default-'
});
}
}

View File

@ -0,0 +1,87 @@
# AgentsApi
| Method | HTTP request | Description |
|-----------------------------------|----------------------------------------------|--------------------------|
| [getAgents](#getAgents) | **GET** /agents | Gets all agents. |
## getAgents
Gets all agents.
A paginated list is returned in the response body. For example:
```json
{
"list": {
"pagination": {
"count": 2,
"hasMoreItems": false,
"totalItems": 2,
"skipCount": 0,
"maxItems": 100
},
"entries": [
{
"entry": {
"id": "Some id",
"name": "Some name",
"description": "Some description",
"avatarUrl": "Some avatar url"
}
}
]
}
}
```
**Example**
```javascript
import { AlfrescoApi, AgentsApi } from '@alfresco/js-api';
const alfrescoApi = new AlfrescoApi(/*..*/);
const agentsApi = new AgentsApi(alfrescoApi);
agentsApi.getAgents().then((agents) => {
console.log('API called successfully. Returned data: ' + agents);
});
```
**Return type**: [AgentPaging](#AgentPaging)
# Models
## AgentPaging
**Properties**
| Name | Type |
|------|-------------------------------------|
| list | [AgentPagingList](#AgentPagingList) |
## AgentPagingList
**Properties**
| Name | Type |
|----------------|-----------------------------|
| **pagination** | [Pagination](Pagination.md) |
| **entries** | [AgentEntry[]](#AgentEntry) |
## AgentEntry
**Properties**
| Name | Type |
|-----------|-----------------|
| **entry** | [Agent](#Agent) |
## Agent
**Properties**
| Name | Type | Description |
|-----------------|-----------|-------------------------------|
| **id** | string | Agent id |
| **name** | string | Agent name |
| **description** | string | Agent description |
| **avatarUrl** | string | (optional) Agent avatar image |

View File

@ -0,0 +1,199 @@
# SearchAiApi
| Method | HTTP request | Description |
|-------------------------|----------------------------|--------------------------------------------|
| [ask](#ask) | **GET** /questions | Ask a question to the AI. |
| [getAnswer](#getAnswer) | **GET** /answers/-default- | Get an answer to specific question. |
| [getConfig](#getConfig) | **GET** /config/-default- | Get the knowledge retrieval configuration. |
## ask
Ask a question to the AI.
A list is returned in the response body. For example:
```json
[
{
"question": "Some question",
"questionId": "Some question id",
"restrictionQuery": "Some restriction query"
}
]
```
**Example**
```javascript
import { AlfrescoApi, AgentsApi } from '@alfresco/js-api';
const alfrescoApi = new AlfrescoApi(/*..*/);
const searchAiApi = new SearchAiApi(alfrescoApi);
searchAiApi.ask([{
question: 'Some question',
restrictionQuery: 'Some restriction query',
agentId: 'Some agent id'
}]).then((questionInformation) => {
console.log('API called successfully. Returned data: ' + questionInformation);
});
```
**Parameters**
| Name | Type | Description |
|---------------|---------------------------------------|-----------------------|
| **questions** | [QuestionRequest](#QuestionRequest)[] | The questions to ask. |
**Return type**: [QuestionModel](#QuestionModel)[]
## getAnswer
Get an answer to specific question.
A paginated list is returned in the response body. For example:
```json
{
"list": {
"pagination": {
"count": 2,
"hasMoreItems": false,
"totalItems": 2,
"skipCount": 0,
"maxItems": 100
},
"entries": [
{
"entry": {
"answer": "Some answer",
"questionId": "Some question id",
"references": [
{
"referenceId": "Some reference id",
"referenceText": "Some reference text"
}
]
}
}
]
}
}
```
**Example**
```javascript
import { AlfrescoApi, AgentsApi } from '@alfresco/js-api';
const alfrescoApi = new AlfrescoApi(/*..*/);
const searchAiApi = new SearchAiApi(alfrescoApi);
searchAiApi.getAnswer('some question id').then((answer) => {
console.log('API called successfully. Returned data: ' + answer);
});
```
**Parameters**
| Name | Type | Description |
|----------------|--------|----------------------------------------------|
| **questionId** | string | The ID of the question to get an answer for. |
**Return type**: [AiAnswerEntry](#AiAnswerEntry)
## getConfig
Get the knowledge retrieval configuration. For example:
```json
{
"entry": {
"knowledgeRetrievalUrl": "https://some-url"
}
}
```
**Example**
```javascript
import { AlfrescoApi, AgentsApi } from '@alfresco/js-api';
const alfrescoApi = new AlfrescoApi(/*..*/);
const searchAiApi = new SearchAiApi(alfrescoApi);
searchAiApi.getConfig().then((answer) => {
console.log('API called successfully. Returned data: ', answer.entry.knowledgeRetrievalUrl);
});
```
**Return type**: [KnowledgeRetrievalConfigEntry](#KnowledgeRetrievalConfigEntry)
# Models
## AiAnswerEntry
**Properties**
| Name | Type |
|-----------|-----------------------|
| **entry** | [AiAnswer](#AiAnswer) |
## AiAnswer
**Properties**
| Name | Type |
|----------------|-------------------------------------------|
| **answer** | string |
| **questionId** | string |
| **references** | [AiAnswerReference](#AiAnswerReference)[] |
## AiAnswerReference
**Properties**
| Name | Type |
|-------------------|--------|
| **referenceId** | string |
| **referenceText** | string |
## QuestionModel
**Properties**
| Name | Type |
|----------------------|------------------|
| **question** | string |
| **questionId** | string |
| **restrictionQuery** | RestrictionQuery |
## RestrictionQuery
**Properties**
| Name | Type |
|--------------|----------|
| **nodesIds** | string[] |
## QuestionRequest
**Properties**
| Name | Type |
|--------------|----------|
| **question** | string |
| **nodeIds** | string[] |
| **agentId** | string |
## KnowledgeRetrievalConfigEntry
**Properties**
| Name | Type |
|-------|-------------------------------------------------------|
| entry | [KnowledgeRetrievalConfig](#KnowledgeRetrievalConfig) |
## KnowledgeRetrievalConfig
**Properties**
| Name | Type |
|-----------------------|--------|
| knowledgeRetrievalUrl | string |

View File

@ -0,0 +1,23 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface Agent {
id: string;
name: string;
description: string;
avatarUrl?: string;
}

View File

@ -0,0 +1,22 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Agent } from './agent';
export interface AgentEntry {
entry: Agent;
}

View File

@ -0,0 +1,22 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AgentPagingList } from './agentPagingList';
export interface AgentPaging {
list?: AgentPagingList;
}

View File

@ -0,0 +1,24 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Pagination } from './pagination';
import { AgentEntry } from './agentEntry';
export interface AgentPagingList {
entries?: AgentEntry[];
pagination?: Pagination;
}

View File

@ -0,0 +1,24 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AiAnswerReference } from './aiAnswerReference';
export interface AiAnswer {
answer: string;
questionId: string;
references: AiAnswerReference[];
}

View File

@ -0,0 +1,22 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AiAnswer } from './aiAnswer';
export interface AiAnswerEntry {
entry: AiAnswer;
}

View File

@ -0,0 +1,21 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface AiAnswerReference {
referenceId: string;
referenceText: string;
}

View File

@ -27,6 +27,13 @@ export * from './activity';
export * from './activityEntry'; export * from './activityEntry';
export * from './activityPaging'; export * from './activityPaging';
export * from './activityPagingList'; export * from './activityPagingList';
export * from './agent';
export * from './agentEntry';
export * from './agentPaging';
export * from './agentPagingList';
export * from './aiAnswer';
export * from './aiAnswerEntry';
export * from './aiAnswerReference';
export * from './association'; export * from './association';
export * from './associationBody'; export * from './associationBody';
export * from './associationEntry'; export * from './associationEntry';
@ -91,6 +98,8 @@ export * from './groupMemberPagingList';
export * from './groupMembershipBodyCreate'; export * from './groupMembershipBodyCreate';
export * from './groupPaging'; export * from './groupPaging';
export * from './groupPagingList'; export * from './groupPagingList';
export * from './knowledgeRetrievalConfig';
export * from './knowledgeRetrievalConfigEntry';
export * from './modelError'; export * from './modelError';
export * from './networkQuota'; export * from './networkQuota';
export * from './node'; export * from './node';
@ -133,6 +142,8 @@ export * from './preferencePagingList';
export * from './probeEntry'; export * from './probeEntry';
export * from './probeEntryEntry'; export * from './probeEntryEntry';
export * from './property'; export * from './property';
export * from './questionModel';
export * from './questionRequest';
export * from './rating'; export * from './rating';
export * from './ratingAggregate'; export * from './ratingAggregate';
export * from './ratingBody'; export * from './ratingBody';

View File

@ -0,0 +1,20 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface KnowledgeRetrievalConfig {
knowledgeRetrievalUrl: string;
}

View File

@ -0,0 +1,22 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { KnowledgeRetrievalConfig } from './knowledgeRetrievalConfig';
export interface KnowledgeRetrievalConfigEntry {
entry: KnowledgeRetrievalConfig;
}

View File

@ -0,0 +1,24 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RestrictionQuery } from './restrictionQuery';
export interface QuestionModel {
questionId: string;
question: string;
restrictionQuery: RestrictionQuery;
}

View File

@ -0,0 +1,22 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface QuestionRequest {
question: string;
nodeIds: string[];
agentId: string;
}

View File

@ -0,0 +1,20 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface RestrictionQuery {
nodesIds: string[];
}

View File

@ -0,0 +1,71 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AgentMock, EcmAuthMock } from '../mockObjects';
import { AgentsApi, AlfrescoApi } from '../../src';
import assert from 'assert';
describe('AgentsApi', () => {
let agentMock: AgentMock;
let agentsApi: AgentsApi;
beforeEach((done) => {
const hostEcm = 'https://127.0.0.1:8080';
const authResponseMock = new EcmAuthMock(hostEcm);
agentMock = new AgentMock(hostEcm);
authResponseMock.get201Response();
const alfrescoJsApi = new AlfrescoApi({
hostEcm
});
alfrescoJsApi.login('admin', 'admin').then(() => done());
agentsApi = new AgentsApi(alfrescoJsApi);
});
describe('getAgents', () => {
it('should load list of agents', (done) => {
agentMock.mockGetAgents200Response();
agentsApi.getAgents().then((paging) => {
assert.deepStrictEqual(paging, {
list: {
pagination: {
count: 2,
hasMoreItems: false,
skipCount: 0,
maxItems: 100
},
entries: [
{
entry: {
id: 'some id 1',
name: 'some name 1'
}
},
{
entry: {
id: 'some id 2',
name: 'some name 2'
}
}
]
}
});
done();
});
});
});
});

View File

@ -0,0 +1,99 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AlfrescoApi, SearchAiApi } from '../../src';
import { EcmAuthMock, SearchAiMock } from '../mockObjects';
import assert from 'assert';
describe('SearchAiApi', () => {
let searchAiApi: SearchAiApi;
let searchAiMock: SearchAiMock;
beforeEach((done) => {
const hostEcm = 'https://127.0.0.1:8080';
const authResponseMock = new EcmAuthMock(hostEcm);
searchAiMock = new SearchAiMock(hostEcm);
authResponseMock.get201Response();
const alfrescoJsApi = new AlfrescoApi({
hostEcm
});
alfrescoJsApi.login('admin', 'admin').then(() => done());
searchAiApi = new SearchAiApi(alfrescoJsApi);
});
describe('ask', () => {
it('should load question information', (done) => {
searchAiMock.mockGetAsk200Response();
searchAiApi
.ask([
{
question: 'some question 1',
nodeIds: ['some node id 1'],
agentId: 'id1'
}
])
.then((questions) => {
assert.deepStrictEqual(questions, {
questionId: 'some id 1',
question: 'some question 1',
restrictionQuery: {
nodesIds: ['some node id 1']
}
});
done();
});
});
});
describe('getAnswer', () => {
it('should load question answer', (done) => {
searchAiMock.mockGetAnswer200Response();
searchAiApi.getAnswer('id1').then((answer) => {
assert.deepStrictEqual(answer, {
entry: {
answer: 'Some answer 1',
questionId: 'some id 1',
references: [
{
referenceId: 'some reference id 1',
referenceText: 'some reference text 1'
}
]
}
});
done();
});
});
});
describe('getConfig', () => {
it('should load knowledge retrieval configuration', (done) => {
searchAiMock.mockGetConfig200Response();
searchAiApi.getConfig().then((config) => {
assert.deepStrictEqual(config, {
entry: {
knowledgeRetrievalUrl: 'https://some-url'
}
});
done();
});
});
});
});

View File

@ -0,0 +1,50 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseMock } from '../base.mock';
import nock from 'nock';
export class AgentMock extends BaseMock {
mockGetAgents200Response(): void {
nock(this.host, { encodedQueryParams: true })
.get('/alfresco/api/-default-/private/hxi/versions/1/agents')
.reply(200, {
list: {
pagination: {
count: 2,
hasMoreItems: false,
skipCount: 0,
maxItems: 100
},
entries: [
{
entry: {
id: 'some id 1',
name: 'some name 1'
}
},
{
entry: {
id: 'some id 2',
name: 'some name 2'
}
}
]
}
});
}
}

View File

@ -0,0 +1,69 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseMock } from '../base.mock';
import nock from 'nock';
export class SearchAiMock extends BaseMock {
mockGetAsk200Response(): void {
nock(this.host, { encodedQueryParams: true })
.post('/alfresco/api/-default-/private/hxi/versions/1/agents/id1/questions', [
{
question: 'some question 1',
restrictionQuery: {
nodesIds: ['some node id 1']
}
}
])
.reply(200, {
entry: {
question: 'some question 1',
questionId: 'some id 1',
restrictionQuery: {
nodesIds: ['some node id 1']
}
}
});
}
mockGetAnswer200Response(): void {
nock(this.host, { encodedQueryParams: true })
.get('/alfresco/api/-default-/private/hxi/versions/1/questions/id1/answers/-default-')
.reply(200, {
entry: {
answer: 'Some answer 1',
questionId: 'some id 1',
references: [
{
referenceId: 'some reference id 1',
referenceText: 'some reference text 1'
}
]
}
});
}
mockGetConfig200Response(): void {
nock(this.host, { encodedQueryParams: true })
.get('/alfresco/api/-default-/private/hxi/versions/1/config/-default-')
.reply(200, {
entry: {
knowledgeRetrievalUrl: 'https://some-url'
}
});
}
}

View File

@ -15,6 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
export * from './content-services/agent.mock';
export * from './content-services/categories.mock'; export * from './content-services/categories.mock';
export * from './content-services/comment.mock'; export * from './content-services/comment.mock';
export * from './content-services/ecm-auth.mock'; export * from './content-services/ecm-auth.mock';
@ -26,6 +27,7 @@ export * from './content-services/groups.mock';
export * from './content-services/find-nodes.mock'; export * from './content-services/find-nodes.mock';
export * from './content-services/rendition.mock'; export * from './content-services/rendition.mock';
export * from './content-services/search.mock'; export * from './content-services/search.mock';
export * from './content-services/search-ai.mock';
export * from './content-services/tag.mock'; export * from './content-services/tag.mock';
export * from './content-services/upload.mock'; export * from './content-services/upload.mock';
export * from './content-services/version.mock'; export * from './content-services/version.mock';