Skip to content

Commit

Permalink
GDB-10713 ttyg agent list filter (#1526)
Browse files Browse the repository at this point in the history
* GDB-10713 ttyg agent list filter

## What
Introduces an agent list filter in ttyg page.

## Why
This allows the user to filter the agent list by repository to which each agent is bound.

## How
Implemented a filter menu in the agent list component and provided a filter model to it.
Implemented tests.
Improved some other styling a bit.

* Stub repositories endpoint to make the test predictable.
  • Loading branch information
svilenvelikov authored Sep 5, 2024
1 parent 0a601a8 commit 7e8e3c8
Show file tree
Hide file tree
Showing 15 changed files with 338 additions and 64 deletions.
26 changes: 26 additions & 0 deletions src/css/ttyg/agent-list.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
.agent-list-component .agent-list {
margin-top: 1rem;
}

.agent-list-component .agents-filter-dropdown {
width: 100%;
}

/* Reset the dropdown toggle button styles to make it look more like an input */
.agent-list-component .agents-filter-dropdown .dropdown-toggle {
width: 100%;
display: flex;
justify-content: space-between;
font-weight: unset;
color: inherit;
border-color: var(--gray-color);
}

.agent-list-component .agents-filter-dropdown .dropdown-toggle:hover {
background-color: #fff;
}

.agent-list-component .agents-filter-dropdown .dropdown-toggle:after {
display: none;
}

.agent-list-component .agent-item {
padding: 4px 8px;
display: flex;
Expand Down
9 changes: 9 additions & 0 deletions src/css/ttyg/chat-list.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
padding: 0 8px;
}

.chat-list-component .chat-group label {
color: #aaa;
}

.chat-list-component .chat-group ul {
list-style: none;
padding-inline-start: 0;
}

.chat-list-component .chat-item {
padding: 4px;
display: flex;
Expand Down
19 changes: 3 additions & 16 deletions src/css/ttyg/ttyg.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ page layout styles
/* override the main-container and title to accommodate to the new design without left and right padding */
.main-container {
padding-left: 0 !important;
padding-right: 0 !important;
padding-right: 1rem !important;
}

/*override btn-small to properly center the icons */
Expand Down Expand Up @@ -48,7 +48,8 @@ common sidebar and slidepanel styles
}

.ttyg-view .sidebar .sidebar-content {
padding: 1rem 0;
/*padding: 1rem 0;*/
padding: 0;
}

.ttyg-view .sidebar .slide-panel {
Expand Down Expand Up @@ -81,15 +82,6 @@ left sidebar
overflow-y: auto;
}

.ttyg-view .left-sidebar .chat-list-panel .chat-group label {
color: #aaa;
}

.ttyg-view .left-sidebar .chat-list-panel .chat-group ul {
list-style: none;
padding-inline-start: 0;
}

/*
right sidebar
*/
Expand All @@ -106,11 +98,6 @@ right sidebar
overflow-y: auto;
}

.ttyg-view .right-sidebar .agent-list-panel ul {
list-style: none;
padding-inline-start: 0;
}

/*
chat area
*/
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/locale-en.json
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,10 @@
"loading_agents": "Loading agents...",
"deleted_repository": "Deleted repository",
"btn": {
"filter": {
"tooltip": "Filter by repository",
"all": "All"
},
"edit_agent": {
"label": "Settings",
"tooltip": "Edit Agent"
Expand All @@ -292,6 +296,7 @@
}
},
"messages": {
"no_agents": "No agents found for the selected filter."
}
},
"help.what.title": "What is this?",
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/locale-fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,10 @@
"agent": {
"loading_agents": "Chargement des agents...",
"btn": {
"filter": {
"tooltip": "Filtrer par dépôt",
"all": "Tous les dépôts"
},
"edit_agent": {
"label": "Paramètres",
"tooltip": "Modifier l'agent"
Expand All @@ -292,6 +296,7 @@
}
},
"messages": {
"no_agents": "Aucun agent n'est configuré pour le moment."
}
},
"help.what.title": "Qu'est-ce que c'est ?",
Expand Down
60 changes: 60 additions & 0 deletions src/js/angular/models/ttyg/agents.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import {cloneDeep} from "lodash";
import {AGENTS_FILTER_ALL_KEY} from "../../ttyg/services/constants";

export class AgentModel {
constructor(data, hashGenerator) {
this.hashGenerator = hashGenerator;
Expand Down Expand Up @@ -178,12 +181,35 @@ export class AgentListModel {
* @private
*/
this._agents = agents;
/**
* Used to store the original list of agents when filtering.
* @type {AgentModel[]}
* @private
*/
this._agentsClone = cloneDeep(agents);
}

isEmpty() {
return this._agents.length === 0;
}

/**
* Filters the agents in place by the repository ID property. This uses the private _agentsClone property to do the
* filtering without losing the original list.
* There is a special case when the repository ID is equal to AGENTS_FILTER_ALL_KEY which means that all agents
* should be shown.
* @param {string} repositoryId
*/
filterByRepository(repositoryId) {
this._agents = this._agentsClone.filter((agent) => {
if (repositoryId === AGENTS_FILTER_ALL_KEY) {
return true;
} else if (agent.repositoryId === repositoryId) {
return agent;
}
});
}

get agents() {
return this._agents;
}
Expand All @@ -199,3 +225,37 @@ export const ExtractionMethod = {
SIMILARITY: 'similarity_search',
RETRIEVAL: 'retrieval_search'
};

/**
* A model used for the filter dropdown in the agent list.
*/
export class AgentListFilterModel {
constructor(key, label) {
/**
* @type {string}
* @private
*/
this._key = key;
/**
* @type {string}
* @private
*/
this._label = label;
}

get key() {
return this._key;
}

set key(value) {
this._key = value;
}

get label() {
return this._label;
}

set label(value) {
this._label = value;
}
}
43 changes: 41 additions & 2 deletions src/js/angular/ttyg/controllers/ttyg-view.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {TTYGEventName} from "../services/ttyg-context.service";
import {ChatQuestion} from "../../models/ttyg/chat-question";
import {chatQuestionToChatMessageMapper} from "../services/chat-message.mapper";
import {cloneDeep} from "lodash";
import {AGENTS_FILTER_ALL_KEY} from "../services/constants";
import {AgentListFilterModel} from "../../models/ttyg/agents";

const modules = [
'toastr',
Expand All @@ -20,23 +22,28 @@ angular
.module('graphdb.framework.ttyg.controllers', modules)
.controller('TTYGViewCtrl', TTYGViewCtrl);

TTYGViewCtrl.$inject = ['$scope', '$http', '$timeout', '$translate', '$uibModal', '$repositories', 'toastr', 'ModalService', 'LocalStorageAdapter', 'TTYGService', 'TTYGContextService'];
TTYGViewCtrl.$inject = ['$rootScope', '$scope', '$http', '$timeout', '$translate', '$uibModal', '$repositories', 'toastr', 'ModalService', 'LocalStorageAdapter', 'TTYGService', 'TTYGContextService'];

const CHATGPTRETRIEVAL_ENDPOINT = 'rest/chat/retrieval';

function TTYGViewCtrl($scope, $http, $timeout, $translate, $uibModal, $repositories, toastr, ModalService, LocalStorageAdapter, TTYGService, TTYGContextService) {
function TTYGViewCtrl($rootScope, $scope, $http, $timeout, $translate, $uibModal, $repositories, toastr, ModalService, LocalStorageAdapter, TTYGService, TTYGContextService) {

// =========================
// Private variables
// =========================

const subscriptions = [];

const labels = {
filter_all: $translate.instant('ttyg.agent.btn.filter.all')
};

// =========================
// Public variables
// =========================

$scope.helpTemplateUrl = "js/angular/ttyg/templates/chatInfo.html";

/**
* Controls the visibility of the chats list sidebar. By default, it is visible unless there are no chats.
* @type {boolean}
Expand Down Expand Up @@ -79,6 +86,12 @@ function TTYGViewCtrl($scope, $http, $timeout, $translate, $uibModal, $repositor
$scope.connectorID = undefined;
$scope.chatQuestion = new ChatQuestion();

/**
* A list of available repository IDs as a model for the agent list filter.
* @type {AgentListFilterModel[]}
*/
$scope.agentListFilterModel = [];

$scope.history = [];
$scope.askSettings = {
"queryTemplate": {
Expand Down Expand Up @@ -114,10 +127,16 @@ function TTYGViewCtrl($scope, $http, $timeout, $translate, $uibModal, $repositor

$scope.onopen = $scope.onclose = () => angular.noop();

/**
* Toggles the visibility of the chats list sidebar.
*/
$scope.toggleChatsListSidebar = () => {
$scope.showChats = !$scope.showChats;
};

/**
* Toggles the visibility of the agents list sidebar.
*/
$scope.toggleAgentsListSidebar = () => {
$scope.showAgents = !$scope.showAgents;
};
Expand Down Expand Up @@ -382,6 +401,23 @@ function TTYGViewCtrl($scope, $http, $timeout, $translate, $uibModal, $repositor
}
};

const setRepositoryIds = () => {
// TODO: this should be refreshed automatically when the repositories change
const repositoryObjects = $repositories.getReadableRepositories().map((repo) => (
new AgentListFilterModel(repo.id, repo.id)
));
$scope.agentListFilterModel = [
new AgentListFilterModel(AGENTS_FILTER_ALL_KEY, labels.filter_all),
...repositoryObjects
];
};

const updateLabels = () => {
labels.filter_all = $translate.instant('ttyg.agent.btn.filter.all');
// recreate the repository list to trigger the update in the view
setRepositoryIds();
};


// =========================
// Subscriptions
Expand All @@ -398,13 +434,16 @@ function TTYGViewCtrl($scope, $http, $timeout, $translate, $uibModal, $repositor
subscriptions.push(TTYGContextService.subscribe(TTYGEventName.CHAT_EXPORT, onExportChat));
subscriptions.push(TTYGContextService.onSelectedChatChanged(onSelectedChatChanged));
subscriptions.push(TTYGContextService.subscribe(TTYGEventName.AGENT_LIST_UPDATED, onAgentListChanged));
subscriptions.push($rootScope.$on('$translateChangeSuccess', updateLabels));
$scope.$on('$destroy', removeAllListeners);

// =========================
// Initialization
// =========================

function onInit() {
setRepositoryIds();

Promise.all([loadChats(), loadAgents()])
.then(([chats, agents]) => {
// TODO: directly set the chats and agents in the scope instead of going through the context event
Expand Down
39 changes: 31 additions & 8 deletions src/js/angular/ttyg/directives/agent-list.directive.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,21 @@ function AgentListComponent(TTYGContextService, ModalService, $translate) {
restrict: 'E',
templateUrl: 'js/angular/ttyg/templates/agent-list.html',
scope: {
agentList: '='
agentList: '=',
agentListFilterModel: '='
},
link: ($scope, element, attrs) => {

// =========================
// Public variables
// =========================

/**
* The selected agents filter.
* @type {{key: string, label: string}|undefined}
*/
$scope.selectedAgentsFilter = undefined;

// =========================
// Private variables
// =========================
Expand Down Expand Up @@ -58,12 +65,28 @@ function AgentListComponent(TTYGContextService, ModalService, $translate) {
};

/**
* Handles the selection of a chat.
* @param {ChatModel} chat
* Handles the selection of an agent.
* @param {AgentModel} agent
*/
$scope.onSelectChat = (chat) => {
TTYGContextService.selectChat(chat);
$scope.renamedChat = undefined;
$scope.onSelectAgent = (agent) => {
console.log('Select agent', agent);
};

/**
* Filters the agents based on the selected repository.
* @param {AgentListFilterModel} selectedFilter
*/
$scope.onAgentsFilterChange = (selectedFilter) => {
$scope.selectedAgentsFilter = selectedFilter;
$scope.agentList.filterByRepository($scope.selectedAgentsFilter.key);
};

// =========================
// Private functions
// =========================

const updateSelectedAgentsFilter = () => {
$scope.selectedAgentsFilter = $scope.agentListFilterModel[0];
};

// =========================
Expand All @@ -76,7 +99,7 @@ function AgentListComponent(TTYGContextService, ModalService, $translate) {
subscriptions.forEach((subscription) => subscription());
};

// subscriptions.push(TTYGContextService.onSelectedChatChanged(onSelectedChatChanged));
subscriptions.push($scope.$watch('repositoryList', updateSelectedAgentsFilter));

// Deregister the watcher when the scope/directive is destroyed
$scope.$on('$destroy', removeAllSubscribers);
Expand All @@ -86,7 +109,7 @@ function AgentListComponent(TTYGContextService, ModalService, $translate) {
// =========================

function initialize() {
console.log('AgentListComponent initialized');
updateSelectedAgentsFilter();
}
initialize();
}
Expand Down
Loading

0 comments on commit 7e8e3c8

Please sign in to comment.