diff --git a/src/i18n/locale-en.json b/src/i18n/locale-en.json index 5c7e72ae7..a52c24f62 100644 --- a/src/i18n/locale-en.json +++ b/src/i18n/locale-en.json @@ -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" @@ -379,6 +382,9 @@ }, "cancel": { "label": "Cancel" + }, + "save": { + "label": "Save" } } }, @@ -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?", diff --git a/src/i18n/locale-fr.json b/src/i18n/locale-fr.json index dfb3588d6..343b55a95 100644 --- a/src/i18n/locale-fr.json +++ b/src/i18n/locale-fr.json @@ -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" @@ -380,6 +383,9 @@ }, "cancel": { "label": "Annuler" + }, + "save": { + "label": "Enregistrer" } } }, @@ -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 ?", diff --git a/src/js/angular/core/services/ttyg.service.js b/src/js/angular/core/services/ttyg.service.js index f6bae4643..72452a735 100644 --- a/src/js/angular/core/services/ttyg.service.js +++ b/src/js/angular/core/services/ttyg.service.js @@ -109,6 +109,18 @@ function TTYGService(TTYGRestService) { }); }; + /** + * Updates the agent with the provided payload. + * @param {*} payload - The data for updating the agent. + * @return {Promise} 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 @@ -129,6 +141,7 @@ function TTYGService(TTYGRestService) { getAgents, getAgent, createAgent, + editAgent, deleteAgent }; } diff --git a/src/js/angular/models/ttyg/agent-form.js b/src/js/angular/models/ttyg/agent-form.js index 72854e419..f1d10fb6e 100644 --- a/src/js/angular/models/ttyg/agent-form.js +++ b/src/js/angular/models/ttyg/agent-form.js @@ -1,5 +1,11 @@ export class AgentFormModel { constructor(data) { + /** + * @type {string} + * @private + */ + this._id = data.id; + /** * @type {string} * @private @@ -54,6 +60,7 @@ export class AgentFormModel { */ toPayload() { return { + id: this.id, name: this._name, repositoryId: this._repositoryId, model: this._model, @@ -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; } @@ -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() { diff --git a/src/js/angular/models/ttyg/agents.js b/src/js/angular/models/ttyg/agents.js index fc9c67fe5..5f1854748 100644 --- a/src/js/angular/models/ttyg/agents.js +++ b/src/js/angular/models/ttyg/agents.js @@ -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} @@ -321,7 +321,7 @@ export class AdditionalExtractionMethodModel { * @type {'iri_discovery_search'} * @private */ - this._method = data._method; + this._method = data.method; } get method() { diff --git a/src/js/angular/rest/ttyg.rest.service.fake.backend.js b/src/js/angular/rest/ttyg.rest.service.fake.backend.js index 4cb3492a3..06af0392b 100644 --- a/src/js/angular/rest/ttyg.rest.service.fake.backend.js +++ b/src/js/angular/rest/ttyg.rest.service.fake.backend.js @@ -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(); diff --git a/src/js/angular/rest/ttyg.rest.service.js b/src/js/angular/rest/ttyg.rest.service.js index c7545bf26..e42fc7eb7 100644 --- a/src/js/angular/rest/ttyg.rest.service.js +++ b/src/js/angular/rest/ttyg.rest.service.js @@ -140,6 +140,18 @@ function TTYGRestService($http) { return $http.post(AGENTS_ENDPOINT, agent); }; + /** + * Updates the specified agent. + * @param {AgentModel} agent - The agent to be updated. + * @return {Promise} 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 @@ -163,6 +175,7 @@ function TTYGRestService($http) { getAgents, getAgent, createAgent, + editAgent, deleteAgent }; } diff --git a/src/js/angular/ttyg/controllers/agent-settings-modal.controller.js b/src/js/angular/ttyg/controllers/agent-settings-modal.controller.js index 9887c615d..b10ef3468 100644 --- a/src/js/angular/ttyg/controllers/agent-settings-modal.controller.js +++ b/src/js/angular/ttyg/controllers/agent-settings-modal.controller.js @@ -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|*} diff --git a/src/js/angular/ttyg/controllers/ttyg-view.controller.js b/src/js/angular/ttyg/controllers/ttyg-view.controller.js index 697990fe6..1ddab7b56 100644 --- a/src/js/angular/ttyg/controllers/ttyg-view.controller.js +++ b/src/js/angular/ttyg/controllers/ttyg-view.controller.js @@ -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"; @@ -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})); + }); + }); }; // ========================= @@ -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); diff --git a/src/js/angular/ttyg/directives/agent-list.directive.js b/src/js/angular/ttyg/directives/agent-list.directive.js index ddd70d79d..18356874e 100644 --- a/src/js/angular/ttyg/directives/agent-list.directive.js +++ b/src/js/angular/ttyg/directives/agent-list.directive.js @@ -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); }; /** diff --git a/src/js/angular/ttyg/services/agents.mapper.js b/src/js/angular/ttyg/services/agents.mapper.js index 3ceb353e9..f360f964f 100644 --- a/src/js/angular/ttyg/services/agents.mapper.js +++ b/src/js/angular/ttyg/services/agents.mapper.js @@ -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 @@ -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); }; diff --git a/src/js/angular/ttyg/services/constants.js b/src/js/angular/ttyg/services/constants.js index 57f44c903..ef95c1831 100644 --- a/src/js/angular/ttyg/services/constants.js +++ b/src/js/angular/ttyg/services/constants.js @@ -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} diff --git a/src/js/angular/ttyg/templates/modal/agent-settings-modal.html b/src/js/angular/ttyg/templates/modal/agent-settings-modal.html index a45c2fb58..4c38370dc 100644 --- a/src/js/angular/ttyg/templates/modal/agent-settings-modal.html +++ b/src/js/angular/ttyg/templates/modal/agent-settings-modal.html @@ -3,7 +3,7 @@ diff --git a/src/js/angular/ttyg/templates/ttyg.html b/src/js/angular/ttyg/templates/ttyg.html index 9e686df89..b5a384ef0 100644 --- a/src/js/angular/ttyg/templates/ttyg.html +++ b/src/js/angular/ttyg/templates/ttyg.html @@ -61,7 +61,7 @@