Timothy Jaeryang Baek 5 月之前
父节点
当前提交
85d7f1c6ed

+ 49 - 11
src/lib/components/admin/Users/Groups.svelte

@@ -7,7 +7,7 @@
 	import { onMount, getContext } from 'svelte';
 	import { goto } from '$app/navigation';
 
-	import { WEBUI_NAME, config, user, showSidebar } from '$lib/stores';
+	import { WEBUI_NAME, config, user, showSidebar, knowledge } from '$lib/stores';
 	import { WEBUI_BASE_URL } from '$lib/constants';
 
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
@@ -54,15 +54,51 @@
 			groups = [
 				{
 					id: '1',
-					name: 'Admins',
-					description: 'Admins have full access to all features and settings.',
-					data: {
-						permissions: {
-							admin: true
+					name: 'Group A',
+					description: 'Group A description',
+					permissions: {
+						model: {
+							enable_filter: false, // boolean
+							ids: [], // array of strings
+							default_id: null // null or string
+						},
+						workspace: {
+							models: false, // boolean
+							knowledge: false, // boolean
+							prompts: false // boolean
+						},
+						chat: {
+							delete: true, // boolean
+							edit: true, // boolean
+							temporary: true // boolean
 						}
 					},
-					user_ids: [1, 2, 3],
-					admin_ids: [1]
+					user_ids: ['1', '2', '3'], // array of strings
+					admin_ids: ['1'] // array of strings
+				},
+				{
+					id: '2',
+					name: 'Moderators',
+					description: 'Moderators description',
+					permissions: {
+						model: {
+							enable_filter: false, // boolean
+							ids: [], // array of strings
+							default_id: null // null or string
+						},
+						workspace: {
+							models: false, // boolean
+							knowledge: false, // boolean
+							prompts: false // boolean
+						},
+						chat: {
+							delete: true, // boolean
+							edit: true, // boolean
+							temporary: true // boolean
+						}
+					},
+					user_ids: ['1', '5', '6'], // array of strings
+					admin_ids: ['1'] // array of strings
 				}
 			];
 		}
@@ -153,15 +189,17 @@
 					<div class="w-full"></div>
 				</div>
 
-				<hr class="mt-1.5 mb-2 border-gray-50 dark:border-gray-850" />
+				<hr class="mt-1.5 border-gray-50 dark:border-gray-850" />
 
 				{#each filteredGroups as group}
-					<GroupItem {group} {users} />
+					<div class="my-2">
+						<GroupItem {group} {users} />
+					</div>
 				{/each}
 			</div>
 		{/if}
 
-		<hr class="my-2 border-gray-50 dark:border-gray-850" />
+		<hr class="mb-2 border-gray-50 dark:border-gray-850" />
 
 		<GroupModal bind:show={showDefaultPermissionsModal} tabs={['permissions']} custom={false} />
 

+ 25 - 23
src/lib/components/admin/Users/Groups/AddGroupModal.svelte

@@ -72,33 +72,35 @@
 						submitHandler();
 					}}
 				>
-					<div class="flex gap-2">
-						<div class="flex flex-col w-full">
-							<div class=" mb-0.5 text-xs text-gray-500">{$i18n.t('Name')}</div>
-
-							<div class="flex-1">
-								<input
-									class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
-									type="text"
-									bind:value={name}
-									placeholder={$i18n.t('Group Name')}
-									autocomplete="off"
-									required
-								/>
+					<div class="px-1 flex flex-col w-full">
+						<div class="flex gap-2">
+							<div class="flex flex-col w-full">
+								<div class=" mb-0.5 text-xs text-gray-500">{$i18n.t('Name')}</div>
+
+								<div class="flex-1">
+									<input
+										class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+										type="text"
+										bind:value={name}
+										placeholder={$i18n.t('Group Name')}
+										autocomplete="off"
+										required
+									/>
+								</div>
 							</div>
 						</div>
-					</div>
 
-					<div class="flex flex-col w-full mt-2">
-						<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Description')}</div>
+						<div class="flex flex-col w-full mt-2">
+							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Description')}</div>
 
-						<div class="flex-1">
-							<Textarea
-								className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none resize-none"
-								rows={2}
-								bind:value={description}
-								placeholder={$i18n.t('Group Description')}
-							/>
+							<div class="flex-1">
+								<Textarea
+									className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none resize-none"
+									rows={2}
+									bind:value={description}
+									placeholder={$i18n.t('Group Description')}
+								/>
+							</div>
 						</div>
 					</div>
 

+ 101 - 14
src/lib/components/admin/Users/Groups/EditGroupModal.svelte

@@ -13,6 +13,7 @@
 	import Display from './Display.svelte';
 	import Permissions from './Permissions.svelte';
 	import Users from './Users.svelte';
+	import UsersSolid from '$lib/components/icons/UsersSolid.svelte';
 
 	export let onSubmit: Function = () => {};
 	export let onDelete: Function = () => {};
@@ -25,9 +26,9 @@
 
 	export let custom = true;
 
-	export let tabs = ['display', 'permissions', 'users'];
+	export let tabs = ['general', 'permissions', 'users'];
 
-	let selectedTab = 'display';
+	let selectedTab = 'general';
 
 	let name = '';
 	let description = '';
@@ -78,7 +79,7 @@
 	});
 </script>
 
-<Modal size="sm" bind:show>
+<Modal size="md" bind:show>
 	<div>
 		<div class=" flex justify-between dark:text-gray-100 px-5 pt-4 mb-1.5">
 			<div class=" text-lg font-medium self-center font-primary">
@@ -120,7 +121,102 @@
 						submitHandler();
 					}}
 				>
-					<div
+					<div class="flex flex-col lg:flex-row w-full h-full pb-2 lg:space-x-4">
+						<div
+							id="admin-settings-tabs-container"
+							class="tabs flex flex-row overflow-x-auto gap-2.5 max-w-full lg:gap-1 lg:flex-col lg:flex-none lg:w-40 dark:text-gray-200 text-sm font-medium text-left scrollbar-none"
+						>
+							{#if tabs.includes('general')}
+								<button
+									class="px-0.5 py-1 max-w-fit w-fit rounded-lg flex-1 lg:flex-none flex text-right transition {selectedTab ===
+									'general'
+										? ''
+										: ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
+									on:click={() => {
+										selectedTab = 'general';
+									}}
+									type="button"
+								>
+									<div class=" self-center mr-2">
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											viewBox="0 0 16 16"
+											fill="currentColor"
+											class="w-4 h-4"
+										>
+											<path
+												fill-rule="evenodd"
+												d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
+												clip-rule="evenodd"
+											/>
+										</svg>
+									</div>
+									<div class=" self-center">{$i18n.t('General')}</div>
+								</button>
+							{/if}
+
+							{#if tabs.includes('permissions')}
+								<button
+									class="px-0.5 py-1 max-w-fit w-fit rounded-lg flex-1 lg:flex-none flex text-right transition {selectedTab ===
+									'permissions'
+										? ''
+										: ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
+									on:click={() => {
+										selectedTab = 'permissions';
+									}}
+									type="button"
+								>
+									<div class=" self-center mr-2">
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											viewBox="0 0 16 16"
+											fill="currentColor"
+											class="w-4 h-4"
+										>
+											<path
+												fill-rule="evenodd"
+												d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
+												clip-rule="evenodd"
+											/>
+										</svg>
+									</div>
+									<div class=" self-center">{$i18n.t('Permissions')}</div>
+								</button>
+							{/if}
+
+							{#if tabs.includes('users')}
+								<button
+									class="px-0.5 py-1 max-w-fit w-fit rounded-lg flex-1 lg:flex-none flex text-right transition {selectedTab ===
+									'users'
+										? ''
+										: ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
+									on:click={() => {
+										selectedTab = 'users';
+									}}
+									type="button"
+								>
+									<div class=" self-center mr-2">
+										<UsersSolid />
+									</div>
+									<div class=" self-center">{$i18n.t('Users')} ({userIds.length})</div>
+								</button>
+							{/if}
+						</div>
+
+						<div
+							class="flex-1 mt-1 lg:mt-1 lg:h-[22rem] lg:max-h-[22rem] overflow-y-auto scrollbar-hidden"
+						>
+							{#if selectedTab == 'general'}
+								<Display bind:name bind:description />
+							{:else if selectedTab == 'permissions'}
+								<Permissions bind:permissions {custom} />
+							{:else if selectedTab == 'users'}
+								<Users bind:userIds bind:adminIds {users} />
+							{/if}
+						</div>
+					</div>
+
+					<!-- <div
 						class=" tabs flex flex-row overflow-x-auto gap-2.5 text-sm font-medium border-b border-b-gray-800 scrollbar-hidden"
 					>
 						{#if tabs.includes('display')}
@@ -167,16 +263,7 @@
 								{$i18n.t('Users')} ({userIds.length})
 							</button>
 						{/if}
-					</div>
-					<div class="px-1 h-96 lg:max-h-96 overflow-y-auto scrollbar-hidden mt-2.5">
-						{#if selectedTab == 'display'}
-							<Display bind:name bind:description />
-						{:else if selectedTab == 'permissions'}
-							<Permissions bind:permissions {custom} />
-						{:else if selectedTab == 'users'}
-							<Users bind:userIds bind:adminIds {users} />
-						{/if}
-					</div>
+					</div> -->
 
 					<div class="flex justify-end pt-3 text-sm font-medium gap-1.5">
 						{#if edit}

+ 1 - 1
src/lib/components/admin/Users/Groups/GroupItem.svelte

@@ -39,7 +39,7 @@
 				showEdit = true;
 			}}
 		>
-			<Pencil />
+			<Pencil className="size-3.5" />
 		</button>
 	</div>
 </div>

+ 15 - 13
src/lib/components/admin/Users/Groups/Users.svelte

@@ -97,19 +97,21 @@
 						</div>
 
 						<div class="flex w-full items-center justify-between">
-							<div class="flex">
-								<img
-									class=" rounded-full w-6 h-6 object-cover mr-2.5"
-									src={user.profile_image_url.startsWith(WEBUI_BASE_URL) ||
-									user.profile_image_url.startsWith('https://www.gravatar.com/avatar/') ||
-									user.profile_image_url.startsWith('data:')
-										? user.profile_image_url
-										: `/user.png`}
-									alt="user"
-								/>
-
-								<div class=" font-medium self-center">{user.name}</div>
-							</div>
+							<Tooltip content={user.email} placement="top-start">
+								<div class="flex">
+									<img
+										class=" rounded-full w-6 h-6 object-cover mr-2.5"
+										src={user.profile_image_url.startsWith(WEBUI_BASE_URL) ||
+										user.profile_image_url.startsWith('https://www.gravatar.com/avatar/') ||
+										user.profile_image_url.startsWith('data:')
+											? user.profile_image_url
+											: `/user.png`}
+										alt="user"
+									/>
+
+									<div class=" font-medium self-center">{user.name}</div>
+								</div>
+							</Tooltip>
 
 							{#if userIds.includes(user.id)}
 								<button