Skip to content

Commit

Permalink
GDB-10714 - introduce agent edit action (#1551)
Browse files Browse the repository at this point in the history
## What
Introduce agent edit action.

## Why
Users should be able to edit existing agents.

## How
Implemented mapper from the agent model to agent form model using the default values as a base.
Wired the edit agent and open the agent settings modal with the needed data.
  • Loading branch information
svilenvelikov authored Sep 25, 2024
1 parent 59525b7 commit 07c00b4
Show file tree
Hide file tree
Showing 19 changed files with 296 additions and 27 deletions.
12 changes: 10 additions & 2 deletions src/i18n/locale-en.json
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,10 @@
"deleting_agent": "Deleting agent...",
"deleted_repository": "Deleted repository",
"create_agent_modal": {
"title": "Create Agent",
"title": {
"create": "Create Agent",
"edit": "Edit Agent"
},
"advanced_settings": {
"show": "Show advanced settings",
"hide": "Hide advanced settings"
Expand Down Expand Up @@ -379,6 +382,9 @@
},
"cancel": {
"label": "Cancel"
},
"save": {
"label": "Save"
}
}
},
Expand Down Expand Up @@ -433,7 +439,9 @@
"help_2": "The Agent is an AI-powered conversational agent designed to help with a wide range of tasks, from answering questions and providing information to assisting with creative and technical projects. It leverages advanced natural language processing to understand and respond to user inputs in a human-like manner, making interactions intuitive and efficient.",
"error_retrieval_connectors_loading": "Error loading retrieval connectors",
"error_similarity_indexes_loading": "Error loading similarity indexes",
"error_repository_config_loading": "Error loading repository configurations"
"error_repository_config_loading": "Error loading repository configurations",
"agent_save_successfully": "The agent '{{agentName}}' was saved successfully.",
"agent_save_failure": "Failed to save the agent '{{agentName}}'."
}
},
"help.what.title": "What is this?",
Expand Down
12 changes: 10 additions & 2 deletions src/i18n/locale-fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,10 @@
"deleting_agent": "Suppression de l'agent...",
"deleted_repository": "Dépôt supprimé",
"create_agent_modal": {
"title": "Créer un agent",
"title": {
"create": "Créer un agent",
"edit": "Modifier un agent"
},
"advanced_settings": {
"show": "Afficher les paramètres avancés",
"hide": "Masquer les paramètres avancés"
Expand Down Expand Up @@ -380,6 +383,9 @@
},
"cancel": {
"label": "Annuler"
},
"save": {
"label": "Enregistrer"
}
}
},
Expand Down Expand Up @@ -434,7 +440,9 @@
"help_2": "The Agent est un agent conversationnel basé sur l'IA, conçu pour vous aider dans une large gamme de tâches, de la réponse aux questions et la fourniture d'informations à l'assistance dans le cadre de projets créatifs et techniques. Il exploite un traitement avancé du langage naturel pour comprendre et répondre aux entrées des utilisateurs de manière humaine, rendant les interactions intuitives et efficaces.",
"error_retrieval_connectors_loading": "Erreur lors du chargement des connecteurs de récupération",
"error_similarity_indexes_loading": "Erreur lors du chargement des index de similarité",
"error_repository_config_loading": "Erreur lors du chargement de la configuration du dépôt"
"error_repository_config_loading": "Erreur lors du chargement de la configuration du dépôt",
"agent_save_successfully": "L'agent '{{agentName}}' a été enregistré avec succès.",
"agent_save_failure": "Échec de l'enregistrement de l'agent '{{agentName}}'."
}
},
"help.what.title": "Qu'est-ce que c'est ?",
Expand Down
13 changes: 13 additions & 0 deletions src/js/angular/core/services/ttyg.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ function TTYGService(TTYGRestService) {
});
};

/**
* Updates the agent with the provided <code>payload</code>.
* @param {*} payload - The data for updating the agent.
* @return {Promise<AgentModel>} A promise that resolves to the updated agent.
*/
const editAgent = (payload) => {
return TTYGRestService.editAgent(payload)
.then((response) => {
return agentModelMapper(response.data);
});
};

/**
* Deletes an agent by its ID.
* @param {string} id
Expand All @@ -129,6 +141,7 @@ function TTYGService(TTYGRestService) {
getAgents,
getAgent,
createAgent,
editAgent,
deleteAgent
};
}
31 changes: 30 additions & 1 deletion src/js/angular/models/ttyg/agent-form.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
export class AgentFormModel {
constructor(data) {
/**
* @type {string}
* @private
*/
this._id = data.id;

/**
* @type {string}
* @private
Expand Down Expand Up @@ -54,6 +60,7 @@ export class AgentFormModel {
*/
toPayload() {
return {
id: this.id,
name: this._name,
repositoryId: this._repositoryId,
model: this._model,
Expand Down Expand Up @@ -170,6 +177,26 @@ export class ExtractionMethodsFormModel {
.map((method) => method.toPayload());
}

/**
* Finds the index of the extraction method in the extraction methods array.
* @param {string} method
* @return {number}
*/
findExtractionMethodIndex(method) {
return this._extractionMethods.findIndex((extractionMethod) => extractionMethod.method === method);
}

/**
* Sets the extraction method in the extraction methods array.
* @param {ExtractionMethodsFormModel} extractionMethod
*/
setExtractionMethod(extractionMethod) {
const index = this.findExtractionMethodIndex(extractionMethod.method);
if (index !== -1) {
this._extractionMethods[index] = extractionMethod;
}
}

get extractionMethods() {
return this._extractionMethods;
}
Expand Down Expand Up @@ -352,7 +379,9 @@ export class AdditionalExtractionMethodsFormModel {
}

toPayload() {
return this._additionalExtractionMethods.map((method) => method.toPayload());
return this._additionalExtractionMethods
.filter((method) => method.selected)
.map((method) => method.toPayload());
}

get additionalExtractionMethods() {
Expand Down
4 changes: 2 additions & 2 deletions src/js/angular/models/ttyg/agents.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ export class ExtractionMethodModel {
* The maximum number of triples per call for the extraction method.
* @type {number}
*/
this._maxNumberOfTriplesPerCall = data._maxNumberOfTriplesPerCall;
this._maxNumberOfTriplesPerCall = data.maxNumberOfTriplesPerCall;
/**
* The similarity index used for the similarity extraction method.
* @type {string}
Expand Down Expand Up @@ -321,7 +321,7 @@ export class AdditionalExtractionMethodModel {
* @type {'iri_discovery_search'}
* @private
*/
this._method = data._method;
this._method = data.method;
}

get method() {
Expand Down
6 changes: 6 additions & 0 deletions src/js/angular/rest/ttyg.rest.service.fake.backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ export class TtygRestServiceFakeBackend {
return Promise.resolve({data: agent});
}

editAgent(editedAgent) {
agentsList = agentsList.map((agent) => agent.id === editedAgent.id ? editedAgent : agent);

return Promise.resolve({data: editedAgent});
}

deleteAgent(id) {
agentsList = agentsList.filter((agent) => agent.id !== id);
return Promise.resolve();
Expand Down
13 changes: 13 additions & 0 deletions src/js/angular/rest/ttyg.rest.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,18 @@ function TTYGRestService($http) {
return $http.post(AGENTS_ENDPOINT, agent);
};

/**
* Updates the specified <code>agent</code>.
* @param {AgentModel} agent - The agent to be updated.
* @return {Promise<AgentModel>} A promise that resolves the updated agent.
*/
const editAgent = (agent) => {
if (DEVELOPMENT) {
return _fakeBackend.editAgent(agent);
}
return $http.put(`${AGENTS_ENDPOINT}`, agent);
};

/**
* Deletes an agent by its ID from the backend.
* @param {string} id
Expand All @@ -163,6 +175,7 @@ function TTYGRestService($http) {
getAgents,
getAgent,
createAgent,
editAgent,
deleteAgent
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ function AgentSettingsModalController($scope, $uibModalInstance, SimilarityServi
*/
$scope.agentFormModel = dialogModel.agentFormModel;

$scope.isEdit = !!$scope.agentFormModel.id;

/**
* The active repository info model.
* @type {RepositoryInfoModel|*}
Expand Down
49 changes: 43 additions & 6 deletions src/js/angular/ttyg/controllers/ttyg-view.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {TTYGEventName} from "../services/ttyg-context.service";
import {AGENTS_FILTER_ALL_KEY} from "../services/constants";
import {AgentListFilterModel} from "../../models/ttyg/agents";
import {ChatsListModel} from "../../models/ttyg/chats";
import {newAgentFormModelProvider} from "../services/agents.mapper";
import {agentFormModelMapper, newAgentFormModelProvider} from "../services/agents.mapper";
import {SelectMenuOptionsModel} from "../../models/form-fields";
import {repositoryInfoMapper} from "../../rest/mappers/repositories-mapper";

Expand Down Expand Up @@ -230,11 +230,48 @@ function TTYGViewCtrl($rootScope, $scope, $http, $timeout, $translate, $uibModal
};

/**
* Handles the agent edit operation.
* @param {AgentModel} agent
* Handles the agent edit operation. If the agent is not provided it is assumed that we need to edit the selected
* agent which can be obtained from the context service.
* @param {AgentModel|undefined} agent
*/
$scope.onEditAgent = (agent) => {
console.log(`Edit agent`, agent);
let agentToEdit = agent;
if (!agentToEdit) {
agentToEdit = TTYGContextService.getSelectedAgent();
}
const agentFormModel = agentFormModelMapper(agentToEdit);
const activeRepositoryInfo = repositoryInfoMapper($repositories.getActiveRepositoryObject());
const options = {
templateUrl: 'js/angular/ttyg/templates/modal/agent-settings-modal.html',
controller: 'AgentSettingsModalController',
windowClass: 'agent-settings-modal',
resolve: {
dialogModel: function () {
return {
activeRepositoryInfo: activeRepositoryInfo,
activeRepositoryList: $scope.activeRepositoryList,
agentFormModel: agentFormModel
};
}
},
size: 'lg'
};
$uibModal.open(options).result.then(
// confirmed handler
(data) => {
TTYGService.editAgent(data)
.then((updatedAgent) => {
toastr.success($translate.instant("ttyg.agent.messages.agent_save_successfully", {agentName: updatedAgent.name}));
const hasSelectedAgent = TTYGContextService.getSelectedAgent();
if (hasSelectedAgent && data.id === hasSelectedAgent.id) {
TTYGContextService.selectAgent(updatedAgent);
}
loadAgents(false);
})
.catch(() => {
toastr.error($translate.instant("ttyg.agent.messages.agent_save_failure", {agentName: data.name}));
});
});
};

// =========================
Expand Down Expand Up @@ -267,8 +304,8 @@ function TTYGViewCtrl($rootScope, $scope, $http, $timeout, $translate, $uibModal
});
};

const loadAgents = () => {
$scope.loadingAgents = true;
const loadAgents = (showLoader = true) => {
$scope.loadingAgents = showLoader;
return TTYGService.getAgents()
.then((agents) => {
return TTYGContextService.updateAgents(agents);
Expand Down
2 changes: 1 addition & 1 deletion src/js/angular/ttyg/directives/agent-list.directive.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function AgentListComponent(TTYGContextService, ModalService, $translate) {
* @param {AgentModel} agent
*/
$scope.onEditAgent = (agent) => {
console.log('Edit agent', agent);
TTYGContextService.emit(TTYGEventName.EDIT_AGENT, agent);
};

/**
Expand Down
77 changes: 76 additions & 1 deletion src/js/angular/ttyg/services/agents.mapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,81 @@ export const newAgentFormModelProvider = () => {
return new AgentFormModel(cloneDeep(AGENT_MODEL_DEFAULT_VALUES));
};

/**
* Converts an angel model to an agent form model.
* @param {AgentModel} agentModel
* @return {AgentFormModel}
*/
export const agentFormModelMapper = (agentModel) => {
if (!agentModel) {
return;
}
const agentFormModel = newAgentFormModelProvider();
agentFormModel.id = agentModel.id;
agentFormModel.name = agentModel.name;
agentFormModel.repositoryId = agentModel.repositoryId;
agentFormModel.model = agentModel.model;
agentFormModel.temperature.value = agentModel.temperature !== undefined ? agentModel.temperature : AGENT_MODEL_DEFAULT_VALUES.temperature.value;
agentFormModel.topP.value = agentModel.topP !== undefined ? agentModel.topP : AGENT_MODEL_DEFAULT_VALUES.topP.value;
agentFormModel.seed = agentModel.seed;
agentFormModel.instructions = agentInstructionsFormMapper(agentModel.instructions);
extractionMethodsFormMapper(agentFormModel, agentModel.assistantExtractionMethods);
// Select additional methods if they are present in the list returned from the backend (BE).
agentFormModel.additionalExtractionMethods.additionalExtractionMethods.forEach((method) => {
method.selected = agentModel.additionalExtractionMethods && agentModel.additionalExtractionMethods.some((agentMethod) => agentMethod.method === method.method);
});

return agentFormModel;
};

/**
* @param {AgentInstructionsModel} data
* @return {AgentInstructionsFormModel}
*/
const agentInstructionsFormMapper = (data) => {
if (!data) {
return;
}
return new AgentInstructionsFormModel({
systemInstruction: data.systemInstruction,
userInstruction: data.userInstruction
});
};

/**
* @param {AgentFormModel} agentFormModel
* @param {ExtractionMethodModel[]} data
*/
const extractionMethodsFormMapper = (agentFormModel, data = []) => {
data.forEach((extractionMethod) => {
const existingMethod = new ExtractionMethodFormModel({
selected: true,
method: extractionMethod.method,
ontologyGraph: extractionMethod.ontologyGraph,
sparqlQuery: extractionMethod.sparqlQuery && new TextFieldModel({
value: extractionMethod.sparqlQuery,
minLength: 1,
maxLength: 2380
}),
similarityIndex: extractionMethod.similarityIndex,
similarityIndexThreshold: extractionMethod.similarityIndexThreshold && new NumericRangeModel({
value: extractionMethod.similarityIndexThreshold,
minValue: 0,
maxValue: 1,
step: 0.1
}),
maxNumberOfTriplesPerCall: extractionMethod.maxNumberOfTriplesPerCall,
queryTemplate: extractionMethod.queryTemplate && new TextFieldModel({
value: extractionMethod.queryTemplate,
minLength: 1,
maxLength: 2380
}),
retrievalConnectorInstance: extractionMethod.retrievalConnectorInstance
});
agentFormModel.assistantExtractionMethods.setExtractionMethod(existingMethod);
});
};

/**
* Converts the response from the server to a list of AgentModel.
* @param {*[]} data
Expand All @@ -97,7 +172,7 @@ export const agentListMapper = (data) => {
if (!data) {
return new AgentListModel();
}
const agentModels = data.map((chat) => agentModelMapper(chat));
const agentModels = data.map((agent) => agentModelMapper(agent));
return new AgentListModel(agentModels);
};

Expand Down
2 changes: 0 additions & 2 deletions src/js/angular/ttyg/services/constants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export const AGENT_ID = 'asst_uoKp5kgnPlyZHhRXY7P2r9D7'; // repoId: starwars, method: fts_search

/**
* The key to use when filtering agents indicating that all agents should be shown.
* @type {string}
Expand Down
Loading

0 comments on commit 07c00b4

Please sign in to comment.