diff --git a/src/lib/common/InPlaceEdit.svelte b/src/lib/common/InPlaceEdit.svelte index 2eed2a9a..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; } } @@ -63,7 +62,7 @@ {:else} -
edit()}> +
edit()}> {#if !!value?.trim()} {value} {:else} 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/http.js b/src/lib/helpers/http.js index cfcd4bf7..7f53127e 100644 --- a/src/lib/helpers/http.js +++ b/src/lib/helpers/http.js @@ -61,12 +61,14 @@ 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 = [ 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 = [ 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 3f50a321..529afe2b 100644 --- a/src/lib/helpers/types/userTypes.js +++ b/src/lib/helpers/types/userTypes.js @@ -7,13 +7,45 @@ * @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. * @property {string} [avatar] - The user avatar. * @property {string} [color] * @property {string} [token] + * @property {boolean} [open_detail] + */ + +/** + * @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 + */ + +/** + * @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 + * @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/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/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 new file mode 100644 index 00000000..feddbf37 --- /dev/null +++ b/src/lib/scss/custom/pages/_users.scss @@ -0,0 +1,98 @@ +.users-table { + .user-plain-col { + width: 10%; + max-width: 100px; + } + + .user-permission-col { + width: 20%; + 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; + } + } + } + + .user-agent-container { + margin: 20px 0px; + 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-wrapper { + display: flex; + gap: 3px; + justify-content: center; + 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: 16px; + margin-top: 3px; + margin-right: 5px; + } + } +} 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/lib/services/api-endpoints.js b/src/lib/services/api-endpoints.js index 2d099617..1c4a95d4 100644 --- a/src/lib/services/api-endpoints.js +++ b/src/lib/services/api-endpoints.js @@ -5,6 +5,8 @@ export const endpoints = { // user 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 new file mode 100644 index 00000000..dacb88d5 --- /dev/null +++ b/src/lib/services/user-service.js @@ -0,0 +1,23 @@ +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; +} + + +/** + * 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/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/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..516bbe66 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; @@ -43,7 +45,7 @@ function getPagedAgents() { isLoading = true; - getAgents(filter).then(data => { + getAgents(filter, true).then(data => { agents = data; }).catch(() => { agents = { items: [], count: 0 }; @@ -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]/+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/agent/[agentId]/agent-overview.svelte b/src/routes/page/agent/[agentId]/agent-overview.svelte index eb9682d0..e4a5e5c9 100644 --- a/src/routes/page/agent/[agentId]/agent-overview.svelte +++ b/src/routes/page/agent/[agentId]/agent-overview.svelte @@ -75,15 +75,17 @@ 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/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/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/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} - + + +
    +
    {$_('User List')}
    +
    + + + + + + +
    +
    +
    + + +
    + + + + + + + + + + + + + + + + + +
    +
    +
      {#if isQuestionAnswerCollection} diff --git a/src/routes/page/users/+page.svelte b/src/routes/page/users/+page.svelte new file mode 100644 index 00000000..86212624 --- /dev/null +++ b/src/routes/page/users/+page.svelte @@ -0,0 +1,257 @@ + + + + + + + + + +
    + + + + + + + + + + + + + {#each userItems as item, idx (idx)} + saveUser(e)} + /> + {/each} + +
    {$_('User Name')}{$_('Full Name')}{$_('External Id')}{$_('Role')}{$_('Source')}{$_('Permissions')}{$_('')}
    +
    + 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..87cf41e5 --- /dev/null +++ b/src/routes/page/users/user-item.svelte @@ -0,0 +1,339 @@ + + + + {item.user_name} + {item.full_name} + {item.external_id} + +
    {item.role}
    + + {item.source} + +
    + {item.permissions?.length > 0 ? item.permissions.join(', ') : 'N/A'} +
    + + +
      +
    • + +
    • +
    + + + +{#if open} + + +
    +
    + + +
    save(item.id)} + > + +
    +
    +
      +
    • +
      + {'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} + {#if item.permissions} +
    • +
      + {'Permissions:'} + {item.permissions?.length > 0 ? item.permissions.join(', ') : 'N/A'} +
      +
    • + {/if} +
    + {#if innerActions.length > 0} +
    +
    +
    + {'Agent'} +
    + {#each allActions as title} +
    +
    {title.name}
    +
    + checkAll(e, title)} + /> +
    +
    + {/each} +
    +
    + {#each innerActions as agentActionItem} +
    +
    + {agentActionItem.agent_name} +
    + {#each agentActionItem.actions as actionItem} +
    + checkAction(e, agentActionItem, actionItem)} + /> +
    + {/each} +
    + {/each} +
    +
    + {/if} +
    + + +{/if} \ No newline at end of file