From 9a6aea4b7b138628d7eb6a78559bbd98094245e5 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 30 Oct 2024 17:44:56 -0500 Subject: [PATCH 1/5] add user page --- src/lib/common/InPlaceEdit.svelte | 2 +- src/lib/helpers/types/userTypes.js | 21 ++ src/lib/scss/app.scss | 1 + src/lib/scss/custom/pages/_users.scss | 50 +++++ src/lib/services/api-endpoints.js | 1 + src/lib/services/user-service.js | 12 ++ .../common/vector-table/vector-item.svelte | 2 +- src/routes/page/users/+page.svelte | 196 ++++++++++++++++++ src/routes/page/users/user-item.svelte | 134 ++++++++++++ 9 files changed, 417 insertions(+), 2 deletions(-) create mode 100644 src/lib/scss/custom/pages/_users.scss create mode 100644 src/lib/services/user-service.js create mode 100644 src/routes/page/users/+page.svelte create mode 100644 src/routes/page/users/user-item.svelte diff --git a/src/lib/common/InPlaceEdit.svelte b/src/lib/common/InPlaceEdit.svelte index 2eed2a9a..cf037830 100644 --- a/src/lib/common/InPlaceEdit.svelte +++ b/src/lib/common/InPlaceEdit.svelte @@ -63,7 +63,7 @@ {:else} -
edit()}> +
edit()}> {#if !!value?.trim()} {value} {:else} diff --git a/src/lib/helpers/types/userTypes.js b/src/lib/helpers/types/userTypes.js index 3f50a321..33c742fd 100644 --- a/src/lib/helpers/types/userTypes.js +++ b/src/lib/helpers/types/userTypes.js @@ -7,7 +7,10 @@ * @property {string} [full_name] - The user full name. * @property {string} [email] - The user email. * @property {string} source - Account source. + * @property {string?} [type] - Account source. * @property {string} [external_id] - The user external id. + * @property {string[]} permissions - Permissions. + * @property {UserAgentAction[]} agent_actions - Agent actions * @property {string} [create_date] - The user create date. * @property {string} [update_date] - The user update date. * @property {string} [role] - The user role. @@ -16,4 +19,22 @@ * @property {string} [token] */ +/** + * @typedef {Object} UserAgentAction + * @property {string} agent_id - The agent id + * @property {import('$agentTypes').AgentModel} [agent] - The agent details + * @property {string[]} actions - The actions + */ + +/** + * @typedef {Object} UserFilter + * @property {number} page - The page number + * @property {number} size - The page size + * @property {string[]} [user_ids] - The user ids. + * @property {string[]} [user_names] - The user names + * @property {string[]} [roles] - The roles. + * @property {string[]} [sources] - The sources. + * @property {string[]} [external_ids] - The external ids. + */ + export default {}; \ No newline at end of file diff --git a/src/lib/scss/app.scss b/src/lib/scss/app.scss index 78f25531..389b85c0 100644 --- a/src/lib/scss/app.scss +++ b/src/lib/scss/app.scss @@ -92,6 +92,7 @@ File: Main Css File @import "custom/pages/conversation"; @import "custom/pages/agent"; @import "custom/pages/knowledgebase"; +@import "custom/pages/users"; // Common @import "custom/common/animation"; diff --git a/src/lib/scss/custom/pages/_users.scss b/src/lib/scss/custom/pages/_users.scss new file mode 100644 index 00000000..7d27393a --- /dev/null +++ b/src/lib/scss/custom/pages/_users.scss @@ -0,0 +1,50 @@ +.users-table { + .user-permission-col { + width: 10%; + max-width: 300px; + } + + .user-agent-col { + width: 25%; + max-width: 350px; + } + + .user-detail { + padding: 2px 5px; + border-radius: 3px; + border-color: var(--#{$prefix}light) !important; + background-color: var(--#{$prefix}light) !important; + position: relative; + + ul { + li { + margin: 2px 0px; + } + } + + .wrappable { + white-space: wrap !important; + } + + .basic-info { + margin: 15px 0px 8px 0px; + display: flex; + flex-wrap: wrap; + + li { + flex: 0 0 50%; + + .inline-edit { + display: flex; + gap: 3px; + } + } + } + + .edit-btn { + display: flex; + justify-content: flex-end; + font-size: 18px; + } + } +} diff --git a/src/lib/services/api-endpoints.js b/src/lib/services/api-endpoints.js index 2d099617..5661f28a 100644 --- a/src/lib/services/api-endpoints.js +++ b/src/lib/services/api-endpoints.js @@ -5,6 +5,7 @@ export const endpoints = { // user tokenUrl: `${host}/token`, myInfoUrl: `${host}/user/me`, + usersUrl: `${host}/users`, usrCreationUrl: `${host}/user`, userAvatarUrl: `${host}/user/avatar`, diff --git a/src/lib/services/user-service.js b/src/lib/services/user-service.js new file mode 100644 index 00000000..4db5cddc --- /dev/null +++ b/src/lib/services/user-service.js @@ -0,0 +1,12 @@ +import { endpoints } from './api-endpoints.js'; +import axios from 'axios'; + +/** + * Get user list + * @param {import('$userTypes').UserFilter} filter + * @returns {Promise>} + */ +export async function getUsers(filter) { + const response = await axios.post(endpoints.usersUrl, { ...filter }); + return response.data; +} \ No newline at end of file diff --git a/src/routes/page/knowledge-base/common/vector-table/vector-item.svelte b/src/routes/page/knowledge-base/common/vector-table/vector-item.svelte index 5f300c86..b8f927f7 100644 --- a/src/routes/page/knowledge-base/common/vector-table/vector-item.svelte +++ b/src/routes/page/knowledge-base/common/vector-table/vector-item.svelte @@ -118,7 +118,7 @@ {#if open} - +
    {#if isQuestionAnswerCollection} diff --git a/src/routes/page/users/+page.svelte b/src/routes/page/users/+page.svelte new file mode 100644 index 00000000..846993c7 --- /dev/null +++ b/src/routes/page/users/+page.svelte @@ -0,0 +1,196 @@ + + + + + + + + + + + + +
    +
    {$_('User List')}
    +
    + + + + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + {#each users.items as item} + + {/each} + +
    {$_('User Name')}{$_('Full Name')}{$_('External Id')}{$_('Role')}{$_('Source')}{$_('Permissions')}{$_('Agents')}{$_('')}
    +
    + pageTo(pn)} /> +
    +
    + +
    \ No newline at end of file diff --git a/src/routes/page/users/user-item.svelte b/src/routes/page/users/user-item.svelte new file mode 100644 index 00000000..fb53a785 --- /dev/null +++ b/src/routes/page/users/user-item.svelte @@ -0,0 +1,134 @@ + + + + {item.user_name} + {item.full_name} + {item.external_id} + {item.role} + {item.source} + +
    + {item.permissions?.length > 0 ? item.permissions.join(', ') : 'N/A'} +
    + + +
    + {formatAgentActions(item.agent_actions)} +
    + + +
      +
    • + +
    • +
    + + + +{#if open} + + +
    +
      +
    • +
      + {'User name:'} + {`${item.user_name}`} +
      +
    • + {#if item.full_name} +
    • +
      + {'Full name:'} + {`${item.full_name}`} +
      +
    • + {/if} + {#if item.role} +
    • +
      +
      {'Role:'}
      +
      +
      +
    • + {/if} + {#if item.source} +
    • +
      + {'Source:'} + {`${item.source}`} +
      +
    • + {/if} + {#if item.type} +
    • +
      + {'Type:'} + {`${item.type}`} +
      +
    • + {/if} +
    +
    + +
    +
    + + +
    save()} + > + +
    +
    +
    + + +{/if} \ No newline at end of file From 71a9959e8035ef2dc08c4fee5c54ced76e703f9e Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 31 Oct 2024 14:56:34 -0500 Subject: [PATCH 2/5] temp save --- src/lib/common/InPlaceEdit.svelte | 5 +- src/lib/helpers/constants.js | 8 +- src/lib/helpers/enums.js | 16 +- src/lib/helpers/types/agentTypes.js | 1 + src/lib/helpers/types/userTypes.js | 1 + src/lib/scss/custom/components/_chat.scss | 2 +- src/lib/scss/custom/pages/_users.scss | 45 ++++- .../[conversationId]/chat-box.svelte | 12 +- src/routes/page/agent/+page.svelte | 6 +- .../agent/[agentId]/agent-overview.svelte | 16 +- src/routes/page/agent/card-agent.svelte | 4 +- src/routes/page/users/+page.svelte | 19 +- src/routes/page/users/user-item.svelte | 185 ++++++++++++++++-- 13 files changed, 275 insertions(+), 45 deletions(-) diff --git a/src/lib/common/InPlaceEdit.svelte b/src/lib/common/InPlaceEdit.svelte index cf037830..527c0579 100644 --- a/src/lib/common/InPlaceEdit.svelte +++ b/src/lib/common/InPlaceEdit.svelte @@ -26,11 +26,10 @@ } function submit() { + editing = false; if (value != original) { dispatch('submit', value); } - - editing = false; } /** @param {any} event */ @@ -38,7 +37,7 @@ if (event.key == 'Escape') { event.preventDefault() value = original; - editing = false + editing = false; } } diff --git a/src/lib/helpers/constants.js b/src/lib/helpers/constants.js index d85ffe1f..aa550b80 100644 --- a/src/lib/helpers/constants.js +++ b/src/lib/helpers/constants.js @@ -5,7 +5,13 @@ export const CHAT_FRAME_ID = "chatbox-frame"; export const USER_SENDERS = [ UserRole.Admin, UserRole.User, - UserRole.Client + UserRole.Client, + UserRole.Root +]; + +export const ADMIN_ROLES = [ + UserRole.Admin, + UserRole.Root ]; export const BOT_SENDERS = [ diff --git a/src/lib/helpers/enums.js b/src/lib/helpers/enums.js index b7a4feaf..4583819c 100644 --- a/src/lib/helpers/enums.js +++ b/src/lib/helpers/enums.js @@ -4,7 +4,8 @@ const userRole = { User: "user", Client: "client", Function: "function", - Assistant: "assistant" + Assistant: "assistant", + Root: "root" }; export const UserRole = Object.freeze(userRole); @@ -115,4 +116,15 @@ const conversationTag = { Evaluation: "evaluation-set", Test: "test-set" }; -export const ConversationTag = Object.freeze(conversationTag); \ No newline at end of file +export const ConversationTag = Object.freeze(conversationTag); + +const userPermission = { + CreateAgent: "create-agent" +}; +export const UserPermission = Object.freeze(userPermission); + +const userAction = { + Edit: "edit", + Chat: "chat" +}; +export const UserAction = Object.freeze(userAction); \ No newline at end of file diff --git a/src/lib/helpers/types/agentTypes.js b/src/lib/helpers/types/agentTypes.js index 0caa9854..d429c2db 100644 --- a/src/lib/helpers/types/agentTypes.js +++ b/src/lib/helpers/types/agentTypes.js @@ -58,6 +58,7 @@ * @property {RoutingRule[]} routing_rules * @property {AgentWelcomeInfo} welcome_info - Welcome information. * @property {boolean} editable + * @property {boolean} chatable */ diff --git a/src/lib/helpers/types/userTypes.js b/src/lib/helpers/types/userTypes.js index 33c742fd..e2faa205 100644 --- a/src/lib/helpers/types/userTypes.js +++ b/src/lib/helpers/types/userTypes.js @@ -21,6 +21,7 @@ /** * @typedef {Object} UserAgentAction + * @property {string?} [id] - The id * @property {string} agent_id - The agent id * @property {import('$agentTypes').AgentModel} [agent] - The agent details * @property {string[]} actions - The actions diff --git a/src/lib/scss/custom/components/_chat.scss b/src/lib/scss/custom/components/_chat.scss index 14fa8eaa..291ea7cf 100644 --- a/src/lib/scss/custom/components/_chat.scss +++ b/src/lib/scss/custom/components/_chat.scss @@ -54,7 +54,7 @@ $bubble-chat-theme-color: rgba($primary, 80%); width: 100%; z-index: 999; padding: 10px 15px; - background-color: rgb(255, 255, 239); + background-color: white; } .chat-util-item { diff --git a/src/lib/scss/custom/pages/_users.scss b/src/lib/scss/custom/pages/_users.scss index 7d27393a..b416230d 100644 --- a/src/lib/scss/custom/pages/_users.scss +++ b/src/lib/scss/custom/pages/_users.scss @@ -1,4 +1,9 @@ .users-table { + .user-plain-col { + width: 10%; + max-width: 100px; + } + .user-permission-col { width: 10%; max-width: 300px; @@ -41,10 +46,48 @@ } } + .user-agent-container { + margin-top: 20px; + padding: 0px 2rem; + + .action-row-wrapper { + overflow-y: auto; + scrollbar-width: thin; + height: fit-content; + max-height: 300px; + } + + .action-row { + display: flex; + } + + .action-col { + padding: 3px 0px; + + input[type='checkbox'] { + outline: none !important; + box-shadow: none !important; + } + } + + .action-title { + .action-title-text { + text-transform: capitalize; + text-align: center; + border-bottom: 2px solid var(--bs-primary); + } + } + + .action-center { + display: flex; + justify-content: center; + } + } + .edit-btn { display: flex; justify-content: flex-end; - font-size: 18px; + font-size: 16px; } } } diff --git a/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte b/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte index fec3ff64..8ca3a5a7 100644 --- a/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte +++ b/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte @@ -43,7 +43,7 @@ PUBLIC_LIVECHAT_ENABLE_TRAINING, PUBLIC_DEBUG_MODE } from '$env/static/public'; - import { BOT_SENDERS, LERNER_ID, TEXT_EDITORS, TRAINING_MODE, USER_SENDERS } from '$lib/helpers/constants'; + import { BOT_SENDERS, LERNER_ID, TEXT_EDITORS, TRAINING_MODE, USER_SENDERS, ADMIN_ROLES } from '$lib/helpers/constants'; import { signalr } from '$lib/services/signalr-service.js'; import { webSpeech } from '$lib/services/web-speech.js'; import { newConversation } from '$lib/services/conversation-service'; @@ -201,7 +201,7 @@ } $: { - disableAction = currentUser?.role !== UserRole.Admin && currentUser?.id !== conversationUser?.id; + disableAction = !ADMIN_ROLES.includes(currentUser?.role || '') && currentUser?.id !== conversationUser?.id || !agent?.chatable; } setContext('chat-window-context', { @@ -1504,8 +1504,12 @@ {/if} - {#if currentUser?.role === UserRole.Admin} - toggleTagModal()}> + + {#if ADMIN_ROLES.includes(currentUser?.role || '')} + toggleTagModal()} + > Add Tags {/if} diff --git a/src/routes/page/agent/+page.svelte b/src/routes/page/agent/+page.svelte index c3566729..7249d3b8 100644 --- a/src/routes/page/agent/+page.svelte +++ b/src/routes/page/agent/+page.svelte @@ -11,6 +11,8 @@ import { _ } from 'svelte-i18n' import { goto } from '$app/navigation'; import Swal from 'sweetalert2'; + import { UserPermission } from '$lib/helpers/enums'; + import { ADMIN_ROLES } from '$lib/helpers/constants'; const firstPage = 1; @@ -124,7 +126,7 @@ -{#if !!user} +{#if !!user && (ADMIN_ROLES.includes(user.role || '') || !!user.permissions?.includes(UserPermission.CreateAgent))} @@ -134,4 +136,4 @@ - \ No newline at end of file + pageTo(pn)} /> \ No newline at end of file diff --git a/src/routes/page/agent/[agentId]/agent-overview.svelte b/src/routes/page/agent/[agentId]/agent-overview.svelte index eb9682d0..946d1870 100644 --- a/src/routes/page/agent/[agentId]/agent-overview.svelte +++ b/src/routes/page/agent/[agentId]/agent-overview.svelte @@ -75,13 +75,15 @@ height="50" class="mx-auto d-block" /> - + {#if !!agent.chatable} + + {/if}

Updated at {format(agent.updated_datetime, 'time')}

diff --git a/src/routes/page/agent/card-agent.svelte b/src/routes/page/agent/card-agent.svelte index 4c1eb703..e0fec885 100644 --- a/src/routes/page/agent/card-agent.svelte +++ b/src/routes/page/agent/card-agent.svelte @@ -73,12 +73,12 @@ {#if agent.is_public }
  • - + {$_('Train')}
  • - + {$_('Test')}
  • diff --git a/src/routes/page/users/+page.svelte b/src/routes/page/users/+page.svelte index 846993c7..efdd911f 100644 --- a/src/routes/page/users/+page.svelte +++ b/src/routes/page/users/+page.svelte @@ -21,6 +21,7 @@ import LoadingToComplete from '$lib/common/LoadingToComplete.svelte'; import { getUsers } from '$lib/services/user-service'; import UserItem from './user-item.svelte'; + import { getAgents } from '$lib/services/agent-service'; const duration = 3000; const firstPage = 1; @@ -43,6 +44,9 @@ /** @type {import('$commonTypes').PagedItems} */ let users = { count: 0, items: [] }; + /** @type {import('$commonTypes').IdName[]} */ + let agents = []; + let searchOption = { userName: '', externalId: '', @@ -51,6 +55,7 @@ }; onMount(async () => { + await getPagedAgents(); await getPagedUsers(); }); @@ -59,6 +64,16 @@ refresh(); } + async function getPagedAgents() { + const response = await getAgents({ pager: { page: 1, size: 100, count: 0 } }); + agents = response?.items?.map(x => { + return { + id: x.id, + name: x.name + }; + })?.sort((a, b) => a.name.localeCompare(b.name)) || []; + } + function refresh() { refreshUsers(); refreshPager(users.count, filter.page, filter.size); @@ -183,8 +198,8 @@ - {#each users.items as item} - + {#each users.items as item, idx (idx)} + {/each} diff --git a/src/routes/page/users/user-item.svelte b/src/routes/page/users/user-item.svelte index fb53a785..a9de663c 100644 --- a/src/routes/page/users/user-item.svelte +++ b/src/routes/page/users/user-item.svelte @@ -1,14 +1,21 @@ @@ -41,7 +141,9 @@ {item.user_name} {item.full_name} {item.external_id} - {item.role} + +
    {item.role}
    + {item.source}
    @@ -76,22 +178,22 @@
  • {'User name:'} - {`${item.user_name}`} + {item.user_name}
  • {#if item.full_name}
  • {'Full name:'} - {`${item.full_name}`} + {item.full_name}
  • {/if} {#if item.role}
  • -
    {'Role:'}
    -
    +
    {'Role:'}
    +
  • {/if} @@ -99,7 +201,7 @@
  • {'Source:'} - {`${item.source}`} + {item.source}
  • {/if} @@ -107,23 +209,66 @@
  • {'Type:'} - {`${item.type}`} + {item.type}
  • {/if} + {#if item.permissions} +
  • +
    + {'Permissions:'} + {item.permissions?.length > 0 ? item.permissions.join(', ') : 'N/A'} +
    +
  • + {/if} + + {#if innerActions.length > 0} +
      +
    • +
      {'Agent actions:'}
      +
    -
    - +
    +
    +
    + {''} +
    + {#each allActions as title} +
    + {title} +
    + {/each} +
    +
    + {#each innerActions as agentActionItem} +
    +
    + {agentActionItem.agentName} +
    + {#each agentActionItem.actions as actionItem} +
    + checkAction(e, agentActionItem, actionItem)} + /> +
    + {/each} +
    + {/each} +
    + {/if}
    - +
    save()} + on:click={() => save(item.id)} >
    From c8398072ff7d76c4fa01ffb87543e73a477503ea Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 31 Oct 2024 17:59:07 -0500 Subject: [PATCH 3/5] add user update api --- src/lib/helpers/http.js | 4 +- src/lib/helpers/types/userTypes.js | 9 +++ src/lib/scss/custom/common/_common.scss | 9 +++ src/lib/scss/custom/pages/_users.scss | 3 +- src/lib/services/api-endpoints.js | 1 + src/lib/services/user-service.js | 11 +++ .../agent/[agentId]/agent-overview.svelte | 2 +- src/routes/page/conversation/+page.svelte | 2 +- src/routes/page/users/+page.svelte | 67 ++++++++++++++---- src/routes/page/users/user-item.svelte | 68 ++++++++++--------- 10 files changed, 128 insertions(+), 48 deletions(-) diff --git a/src/lib/helpers/http.js b/src/lib/helpers/http.js index cfcd4bf7..4d5c467b 100644 --- a/src/lib/helpers/http.js +++ b/src/lib/helpers/http.js @@ -67,6 +67,7 @@ function skipLoader(config) { new RegExp('http(s*)://(.*?)/knowledge/vector/(.*?)/update', 'g'), new RegExp('http(s*)://(.*?)/conversation/(.*?)/update-message', 'g'), new RegExp('http(s*)://(.*?)/conversation/(.*?)/update-tags', 'g'), + new RegExp('http(s*)://(.*?)/users', 'g'), ]; const deleteRegexes = [ @@ -83,7 +84,8 @@ function skipLoader(config) { new RegExp('http(s*)://(.*?)/conversation/(.*?)/files/(.*?)', 'g'), new RegExp('http(s*)://(.*?)/llm-provider/(.*?)/models', 'g'), new RegExp('http(s*)://(.*?)/knowledge/vector/collections', 'g'), - new RegExp('http(s*)://(.*?)/knowledge/vector/(.*?)/exist', 'g') + new RegExp('http(s*)://(.*?)/knowledge/vector/(.*?)/exist', 'g'), + new RegExp('http(s*)://(.*?)/users', 'g') ]; if (config.method === 'post' && postRegexes.some(regex => regex.test(config.url || ''))) { diff --git a/src/lib/helpers/types/userTypes.js b/src/lib/helpers/types/userTypes.js index e2faa205..2bffe3e1 100644 --- a/src/lib/helpers/types/userTypes.js +++ b/src/lib/helpers/types/userTypes.js @@ -27,6 +27,15 @@ * @property {string[]} actions - The actions */ +/** + * @typedef {Object} UserAgentInnerAction + * @property {string?} [id] - The id + * @property {string} agent_id - The agent id + * @property {string} [agent_name] - The agent name + * @property {import('$agentTypes').AgentModel} [agent] - The agent details + * @property {{ key: string, value: string, checked: boolean }[]} actions - The actions + */ + /** * @typedef {Object} UserFilter * @property {number} page - The page number diff --git a/src/lib/scss/custom/common/_common.scss b/src/lib/scss/custom/common/_common.scss index c5ff3913..1a58aac4 100644 --- a/src/lib/scss/custom/common/_common.scss +++ b/src/lib/scss/custom/common/_common.scss @@ -157,6 +157,11 @@ button:focus { justify-content:center; } +.div-center { + display: flex; + justify-content:center; +} + .ellipsis { white-space: nowrap; overflow: hidden; @@ -171,6 +176,10 @@ button:focus { .danger-background { background-color: $danger !important; } + +.thin-scrollbar { + scrollbar-width: thin; +} .markdown-container { overflow-x: auto; diff --git a/src/lib/scss/custom/pages/_users.scss b/src/lib/scss/custom/pages/_users.scss index b416230d..fe095b95 100644 --- a/src/lib/scss/custom/pages/_users.scss +++ b/src/lib/scss/custom/pages/_users.scss @@ -5,7 +5,7 @@ } .user-permission-col { - width: 10%; + width: 20%; max-width: 300px; } @@ -88,6 +88,7 @@ display: flex; justify-content: flex-end; font-size: 16px; + margin-top: 10px; } } } diff --git a/src/lib/services/api-endpoints.js b/src/lib/services/api-endpoints.js index 5661f28a..1c4a95d4 100644 --- a/src/lib/services/api-endpoints.js +++ b/src/lib/services/api-endpoints.js @@ -6,6 +6,7 @@ export const endpoints = { tokenUrl: `${host}/token`, myInfoUrl: `${host}/user/me`, usersUrl: `${host}/users`, + userUpdateUrl: `${host}/user`, usrCreationUrl: `${host}/user`, userAvatarUrl: `${host}/user/avatar`, diff --git a/src/lib/services/user-service.js b/src/lib/services/user-service.js index 4db5cddc..dacb88d5 100644 --- a/src/lib/services/user-service.js +++ b/src/lib/services/user-service.js @@ -9,4 +9,15 @@ import axios from 'axios'; export async function getUsers(filter) { const response = await axios.post(endpoints.usersUrl, { ...filter }); return response.data; +} + + +/** + * Get user list + * @param {import('$userTypes').UserModel} model + * @returns {Promise} + */ +export async function updateUser(model) { + const response = await axios.put(endpoints.userUpdateUrl, { ...model }); + return response.data; } \ No newline at end of file diff --git a/src/routes/page/agent/[agentId]/agent-overview.svelte b/src/routes/page/agent/[agentId]/agent-overview.svelte index 946d1870..e4a5e5c9 100644 --- a/src/routes/page/agent/[agentId]/agent-overview.svelte +++ b/src/routes/page/agent/[agentId]/agent-overview.svelte @@ -85,7 +85,7 @@ {/if}
    -
    +

    Updated at {format(agent.updated_datetime, 'time')}

    diff --git a/src/routes/page/conversation/+page.svelte b/src/routes/page/conversation/+page.svelte index 8933b30e..0342afce 100644 --- a/src/routes/page/conversation/+page.svelte +++ b/src/routes/page/conversation/+page.svelte @@ -440,7 +440,7 @@ {/if} -
    +
    diff --git a/src/routes/page/users/+page.svelte b/src/routes/page/users/+page.svelte index efdd911f..863f3ce0 100644 --- a/src/routes/page/users/+page.svelte +++ b/src/routes/page/users/+page.svelte @@ -6,8 +6,6 @@ CardBody, Col, Dropdown, - DropdownItem, - DropdownMenu, DropdownToggle, Input, Row, @@ -19,21 +17,21 @@ import Breadcrumb from '$lib/common/Breadcrumb.svelte'; import TablePagination from '$lib/common/TablePagination.svelte'; import LoadingToComplete from '$lib/common/LoadingToComplete.svelte'; - import { getUsers } from '$lib/services/user-service'; + import { getUsers, updateUser } from '$lib/services/user-service'; import UserItem from './user-item.svelte'; import { getAgents } from '$lib/services/agent-service'; const duration = 3000; const firstPage = 1; - const pageSize = 5; + const pageSize = 15; const initPager = { page: firstPage, size: pageSize }; let isLoading = false; let isComplete = false; let isError = false; - let successText = ''; - let errorText = ''; + let successText = 'User has been updated!'; + let errorText = 'Failed to update user!'; /** @type {import('$commonTypes').Pagination} */ let pager = { ...initPager, count: 0 }; @@ -60,8 +58,18 @@ }); async function getPagedUsers() { - users = await getUsers(filter); - refresh(); + isLoading = true; + return new Promise((resolve, reject) => { + getUsers(filter).then(res => { + users = res; + refresh(); + resolve(res); + }).finally(() => { + setTimeout(() => { + isLoading = false; + }, 200); + }); + }); } async function getPagedAgents() { @@ -123,9 +131,43 @@ page: pageNum, size: pageSize }; - getPagedUsers(); } + + /** @param {any} e */ + function saveUser(e) { + const data = e.detail.updatedData; + isLoading = true; + updateUser(data).then(() => { + isLoading = false; + isComplete = true; + postUpdateUser(data); + setTimeout(() => { + isComplete = false; + }, duration); + }).catch(() => { + isLoading = false; + isComplete = false; + isError = true; + setTimeout(() => { + isError = false; + }, duration); + }); + } + + /** @param {import('$userTypes').UserModel} data */ + function postUpdateUser(data) { + const newItems = users?.items?.map(x => { + if (x.id === data.id) { + return { ...data }; + } + return x; + }) || []; + users = { + ...users, + items: newItems + }; + } @@ -183,23 +225,22 @@ -
    +
    - + - {#each users.items as item, idx (idx)} - + saveUser(e)} /> {/each}
    {$_('User Name')} {$_('Full Name')} {$_('External Id')}{$_('Role')}{$_('Role')} {$_('Source')} {$_('Permissions')}{$_('Agents')} {$_('')}
    diff --git a/src/routes/page/users/user-item.svelte b/src/routes/page/users/user-item.svelte index a9de663c..788b0743 100644 --- a/src/routes/page/users/user-item.svelte +++ b/src/routes/page/users/user-item.svelte @@ -3,6 +3,7 @@ import { fly } from 'svelte/transition'; import { Button, Input } from '@sveltestrap/sveltestrap'; import Swal from 'sweetalert2'; + import lodash from "lodash"; import InPlaceEdit from '$lib/common/InPlaceEdit.svelte'; import { UserAction } from '$lib/helpers/enums'; import { v4 as uuidv4 } from 'uuid'; @@ -20,7 +21,7 @@ export let item; /** @type {boolean} */ - export let open = true; + export let open = false; /** @type {boolean} */ export let disabled = false; @@ -32,7 +33,7 @@ /** @type {import('$userTypes').UserModel} */ let innerItem = { ...item }; - /** @type {any[]} */ + /** @type {import('$userTypes').UserAgentInnerAction[]} */ let innerActions = []; onMount(() => { @@ -40,12 +41,6 @@ initAgentActions(); }); - /** @param {import('$userTypes').UserAgentAction[]} agent_actions */ - function formatAgentActions(agent_actions) { - const validAgents = agent_actions?.filter(x => !!x.agent)?.map(x => x.agent?.name) || []; - return validAgents.join(", "); - } - function initInnerItem() { innerItem = { ...item }; } @@ -57,13 +52,12 @@ if (!found) { return { id: null, - agentId: x.id, - agentName: x.name, + agent_id: x.id, + agent_name: x.name, actions: allActions.map(a => { return { key: uuidv4(), value: a, - displayName: a, checked: false }; }) @@ -75,21 +69,19 @@ return { key: uuidv4(), value: a, - displayName: a, checked: checked }; }); return { id: found.id, - agentId: x.id, - agentName: x.name, + agent_id: x.id, + agent_name: x.name, actions: actions } }); innerActions = handledActions.filter(Boolean); - console.log(innerActions); } function toggleUserDetail() { @@ -106,14 +98,12 @@ */ function checkAction(e, agentActionItem, actionItem) { const checked = e.target.checked; - const found = innerActions.find(x => x.agentId === agentActionItem.agentId); + const found = innerActions.find(x => x.agent_id === agentActionItem.agent_id); // @ts-ignore const action = found?.actions?.find(x => x.key === actionItem.key); if (action) { action.checked = checked; } - - console.log(found); } /** @param {string} id */ @@ -129,11 +119,37 @@ confirmButtonText: 'Yes' }).then(async (result) => { if (result.value) { + const data = formatUpdatedData(); svelteDispatch("save", { - user: innerItem + updatedData: data + }); + open = false; + } + }); + } + + function formatUpdatedData() { + /** @type {any[]} */ + const list = []; + innerActions.forEach(x => { + // @ts-ignore + const items = x.actions.filter(a => !!a.checked); + if (x.id || items.length > 0) { + list.push({ + ...x, + // @ts-ignore + actions: items.map(i => i.value) }); } }); + + const updated = { + ...innerItem, + role: lodash.trim(innerItem.role), + agent_actions: list + }; + innerItem = { ...updated }; + return updated; } @@ -150,11 +166,6 @@ {item.permissions?.length > 0 ? item.permissions.join(', ') : 'N/A'}
    - -
    - {formatAgentActions(item.agent_actions)} -
    -
    • @@ -223,15 +234,10 @@ {/if}
    {#if innerActions.length > 0} -
      -
    • -
      {'Agent actions:'}
      -
    • -
    - {''} + {'Agent'}
    {#each allActions as title}
    @@ -243,7 +249,7 @@ {#each innerActions as agentActionItem}
    - {agentActionItem.agentName} + {agentActionItem.agent_name}
    {#each agentActionItem.actions as actionItem}
    From 5e86e9672efec8d965cc03fb1fb037b1fd9a4559 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 1 Nov 2024 11:38:50 -0500 Subject: [PATCH 4/5] add agent actions --- src/lib/helpers/types/userTypes.js | 1 + src/lib/scss/custom/pages/_users.scss | 10 +- src/routes/page/agent/[agentId]/+page.svelte | 1 + .../[conversationId]/+page.svelte | 2 +- src/routes/page/users/+page.svelte | 11 +- src/routes/page/users/user-item.svelte | 150 ++++++++++++------ 6 files changed, 120 insertions(+), 55 deletions(-) diff --git a/src/lib/helpers/types/userTypes.js b/src/lib/helpers/types/userTypes.js index 2bffe3e1..529afe2b 100644 --- a/src/lib/helpers/types/userTypes.js +++ b/src/lib/helpers/types/userTypes.js @@ -17,6 +17,7 @@ * @property {string} [avatar] - The user avatar. * @property {string} [color] * @property {string} [token] + * @property {boolean} [open_detail] */ /** diff --git a/src/lib/scss/custom/pages/_users.scss b/src/lib/scss/custom/pages/_users.scss index fe095b95..feddbf37 100644 --- a/src/lib/scss/custom/pages/_users.scss +++ b/src/lib/scss/custom/pages/_users.scss @@ -47,7 +47,7 @@ } .user-agent-container { - margin-top: 20px; + margin: 20px 0px; padding: 0px 2rem; .action-row-wrapper { @@ -71,7 +71,10 @@ } .action-title { - .action-title-text { + .action-title-wrapper { + display: flex; + gap: 3px; + justify-content: center; text-transform: capitalize; text-align: center; border-bottom: 2px solid var(--bs-primary); @@ -88,7 +91,8 @@ display: flex; justify-content: flex-end; font-size: 16px; - margin-top: 10px; + margin-top: 3px; + margin-right: 5px; } } } diff --git a/src/routes/page/agent/[agentId]/+page.svelte b/src/routes/page/agent/[agentId]/+page.svelte index 1cab3186..84cc4bd0 100644 --- a/src/routes/page/agent/[agentId]/+page.svelte +++ b/src/routes/page/agent/[agentId]/+page.svelte @@ -119,6 +119,7 @@ title: 'Are you sure?', text: "Are you sure you want to delete this agent?", icon: 'warning', + customClass: { confirmButton: 'danger-background' }, showCancelButton: true, cancelButtonText: 'No', confirmButtonText: 'Yes' diff --git a/src/routes/page/conversation/[conversationId]/+page.svelte b/src/routes/page/conversation/[conversationId]/+page.svelte index c28f2add..6929d9e1 100644 --- a/src/routes/page/conversation/[conversationId]/+page.svelte +++ b/src/routes/page/conversation/[conversationId]/+page.svelte @@ -26,8 +26,8 @@ title: 'Are you sure?', text: "You won't be able to revert this!", icon: 'warning', + customClass: { confirmButton: 'danger-background' }, showCancelButton: true, - customClass: 'custom-modal', confirmButtonText: 'Yes, delete it!' }).then(async (result) => { if (result.value) { diff --git a/src/routes/page/users/+page.svelte b/src/routes/page/users/+page.svelte index 863f3ce0..506006d2 100644 --- a/src/routes/page/users/+page.svelte +++ b/src/routes/page/users/+page.svelte @@ -89,7 +89,7 @@ function refreshUsers() { users = { - items: users?.items?.map(t => ({ ...t })) || [], + items: users?.items?.map(t => ({ ...t, open_detail: false })) || [], count: users?.count || 0 }; } @@ -159,7 +159,7 @@ function postUpdateUser(data) { const newItems = users?.items?.map(x => { if (x.id === data.id) { - return { ...data }; + return { ...data, open_detail: true }; } return x; }) || []; @@ -240,7 +240,12 @@ {#each users.items as item, idx (idx)} - saveUser(e)} /> + saveUser(e)} + /> {/each} diff --git a/src/routes/page/users/user-item.svelte b/src/routes/page/users/user-item.svelte index 788b0743..d4f0b846 100644 --- a/src/routes/page/users/user-item.svelte +++ b/src/routes/page/users/user-item.svelte @@ -10,12 +10,6 @@ const svelteDispatch = createEventDispatcher(); - const allActions = [ - UserAction.Edit, - UserAction.Chat - ]; - - const colStyle = `flex: 0 0 ${allActions.length > 2 ? Math.floor(1 / (allActions.length + 1) * 100) - 1 : 32}%;`; /** @type {import('$userTypes').UserModel} */ export let item; @@ -36,6 +30,29 @@ /** @type {import('$userTypes').UserAgentInnerAction[]} */ let innerActions = []; + let allActions = [ + { + name: UserAction.Edit, + value: UserAction.Edit, + checked: false + }, + { + name: UserAction.Chat, + value: UserAction.Chat, + checked:false + } + ]; + + const colStyle = `flex: 0 0 ${allActions.length > 2 ? Math.floor(1 / (allActions.length + 1) * 100) - 1 : 32}%;`; + + $: { + allActions = allActions.map(x => { + const list = innerActions.flatMap(a => a.actions.filter(y => y.value === x.value)); + x.checked = list.every(a => !!a.checked); + return { ...x }; + }); + } + onMount(() => { initInnerItem(); initAgentActions(); @@ -57,7 +74,7 @@ actions: allActions.map(a => { return { key: uuidv4(), - value: a, + value: a.value, checked: false }; }) @@ -65,10 +82,10 @@ } const actions = allActions.map(a => { - const checked = !!found.actions.find(y => y === a) || false; + const checked = !!found.actions.find(y => y === a.value) || false; return { key: uuidv4(), - value: a, + value: a.value, checked: checked }; }); @@ -93,7 +110,7 @@ /** * @param {any} e - * @param {any} agentActionItem + * @param {import('$userTypes').UserAgentInnerAction} agentActionItem * @param {any} actionItem */ function checkAction(e, agentActionItem, actionItem) { @@ -104,6 +121,36 @@ if (action) { action.checked = checked; } + + innerActions = [ ...innerActions ]; + } + + /** + * @param {any} e + * @param {any} title + */ + function checkAll(e, title) { + const checked = e.target.checked; + allActions = allActions.map(x => { + if (x.value === title.value) { + x.checked = checked; + } + return { ...x }; + }); + + innerActions = innerActions.map(x => { + const actions = x.actions.map(a => { + if (a.value === title.value) { + a.checked = checked; + } + return { ...a }; + }); + + return { + ...x, + actions: actions + }; + }); } /** @param {string} id */ @@ -123,7 +170,6 @@ svelteDispatch("save", { updatedData: data }); - open = false; } }); } @@ -185,6 +231,19 @@
    +
    + + +
    save(item.id)} + > + +
    +
    • @@ -234,51 +293,46 @@ {/if}
    {#if innerActions.length > 0} -
    -
    -
    - {'Agent'} -
    - {#each allActions as title} -
    - {title} +
    +
    +
    + {'Agent'}
    - {/each} -
    -
    - {#each innerActions as agentActionItem} -
    -
    - {agentActionItem.agent_name} -
    - {#each agentActionItem.actions as actionItem} -
    + {#each allActions as title} +
    +
    {title.name}
    +
    checkAction(e, agentActionItem, actionItem)} + checked={title.checked} + on:change={e => checkAll(e, title)} />
    - {/each} -
    - {/each} +
    + {/each} +
    +
    + {#each innerActions as agentActionItem} +
    +
    + {agentActionItem.agent_name} +
    + {#each agentActionItem.actions as actionItem} +
    + checkAction(e, agentActionItem, actionItem)} + /> +
    + {/each} +
    + {/each} +
    -
    {/if} -
    - - -
    save(item.id)} - > - -
    -
    From 42a1e95c868c4bb8cdeab4475613ff1304038370 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 1 Nov 2024 13:16:32 -0500 Subject: [PATCH 5/5] refine agent auth --- src/lib/helpers/http.js | 4 +- src/lib/services/agent-service.js | 6 ++- src/routes/chat/+page.svelte | 2 +- src/routes/page/agent/+page.svelte | 2 +- src/routes/page/users/+page.svelte | 54 +++++++++++++------------- src/routes/page/users/user-item.svelte | 2 +- 6 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/lib/helpers/http.js b/src/lib/helpers/http.js index 4d5c467b..7f53127e 100644 --- a/src/lib/helpers/http.js +++ b/src/lib/helpers/http.js @@ -61,6 +61,7 @@ function skipLoader(config) { new RegExp('http(s*)://(.*?)/knowledge/(.*?)/search', 'g'), new RegExp('http(s*)://(.*?)/knowledge/vector/(.*?)/create', 'g'), new RegExp('http(s*)://(.*?)/knowledge/document/(.*?)/page', 'g'), + new RegExp('http(s*)://(.*?)/users', 'g') ]; const putRegexes = [ @@ -84,8 +85,7 @@ function skipLoader(config) { new RegExp('http(s*)://(.*?)/conversation/(.*?)/files/(.*?)', 'g'), new RegExp('http(s*)://(.*?)/llm-provider/(.*?)/models', 'g'), new RegExp('http(s*)://(.*?)/knowledge/vector/collections', 'g'), - new RegExp('http(s*)://(.*?)/knowledge/vector/(.*?)/exist', 'g'), - new RegExp('http(s*)://(.*?)/users', 'g') + new RegExp('http(s*)://(.*?)/knowledge/vector/(.*?)/exist', 'g') ]; if (config.method === 'post' && postRegexes.some(regex => regex.test(config.url || ''))) { diff --git a/src/lib/services/agent-service.js b/src/lib/services/agent-service.js index 970ab721..73193cae 100644 --- a/src/lib/services/agent-service.js +++ b/src/lib/services/agent-service.js @@ -14,13 +14,15 @@ export async function getSettings() { /** * Get agent list * @param {import('$agentTypes').AgentFilter} filter + * @param {boolean} checkAuth * @returns {Promise>} */ -export async function getAgents(filter) { +export async function getAgents(filter, checkAuth = false) { let url = endpoints.agentListUrl; const response = await axios.get(url, { params: { - ...filter + ...filter, + checkAuth : checkAuth }, paramsSerializer: { dots: true, diff --git a/src/routes/chat/+page.svelte b/src/routes/chat/+page.svelte index 63777ae7..30f02cc0 100644 --- a/src/routes/chat/+page.svelte +++ b/src/routes/chat/+page.svelte @@ -16,7 +16,7 @@ let agentId = 'undefined'; onMount(async () => { - const response = await getAgents(filter); + const response = await getAgents(filter, true); agents = response?.items?.map(t => { return { ...t }; }) || []; const agentSettings = await getSettingDetail("Agent"); // @ts-ignore diff --git a/src/routes/page/agent/+page.svelte b/src/routes/page/agent/+page.svelte index 7249d3b8..516bbe66 100644 --- a/src/routes/page/agent/+page.svelte +++ b/src/routes/page/agent/+page.svelte @@ -45,7 +45,7 @@ function getPagedAgents() { isLoading = true; - getAgents(filter).then(data => { + getAgents(filter, true).then(data => { agents = data; }).catch(() => { agents = { items: [], count: 0 }; diff --git a/src/routes/page/users/+page.svelte b/src/routes/page/users/+page.svelte index 506006d2..86212624 100644 --- a/src/routes/page/users/+page.svelte +++ b/src/routes/page/users/+page.svelte @@ -12,7 +12,6 @@ Table } from '@sveltestrap/sveltestrap'; import { _ } from 'svelte-i18n'; - import lodash from "lodash"; import HeadTitle from "$lib/common/HeadTitle.svelte"; import Breadcrumb from '$lib/common/Breadcrumb.svelte'; import TablePagination from '$lib/common/TablePagination.svelte'; @@ -39,8 +38,8 @@ /** @type {import('$userTypes').UserFilter} */ let filter = { ... initPager }; - /** @type {import('$commonTypes').PagedItems} */ - let users = { count: 0, items: [] }; + /** @type {import('$userTypes').UserModel[]} */ + let userItems = []; /** @type {import('$commonTypes').IdName[]} */ let agents = []; @@ -53,45 +52,50 @@ }; onMount(async () => { - await getPagedAgents(); - await getPagedUsers(); + isLoading = true; + getPagedAgents().then(() => { + getPagedUsers().then(() => { + isLoading = false; + }); + }); }); - async function getPagedUsers() { + function getPagedUsers() { + userItems = []; isLoading = true; return new Promise((resolve, reject) => { getUsers(filter).then(res => { - users = res; - refresh(); + refresh(res); resolve(res); }).finally(() => { - setTimeout(() => { - isLoading = false; - }, 200); + isLoading = false; }); }); } - async function getPagedAgents() { - const response = await getAgents({ pager: { page: 1, size: 100, count: 0 } }); - agents = response?.items?.map(x => { + function getPagedAgents() { + return new Promise((resolve, reject) => { + getAgents({ pager: { page: 1, size: 100, count: 0 } }).then(res => { + agents = res?.items?.map(x => { return { id: x.id, name: x.name }; })?.sort((a, b) => a.name.localeCompare(b.name)) || []; + resolve(agents); + }); + }); } - function refresh() { - refreshUsers(); + /** @param {import('$commonTypes').PagedItems} users */ + function refresh(users) { + refreshUsers(users); refreshPager(users.count, filter.page, filter.size); } - function refreshUsers() { - users = { - items: users?.items?.map(t => ({ ...t, open_detail: false })) || [], - count: users?.count || 0 - }; + /** @param {import('$commonTypes').PagedItems} users */ + function refreshUsers(users) { + userItems = [ ...users.items ]; } /** @param {number} totalItemsCount */ @@ -157,16 +161,12 @@ /** @param {import('$userTypes').UserModel} data */ function postUpdateUser(data) { - const newItems = users?.items?.map(x => { + userItems = userItems?.map(x => { if (x.id === data.id) { return { ...data, open_detail: true }; } return x; }) || []; - users = { - ...users, - items: newItems - }; } @@ -239,7 +239,7 @@ - {#each users.items as item, idx (idx)} + {#each userItems as item, idx (idx)}