wip: frontend

This commit is contained in:
Timothy Jaeryang Baek 2024-11-13 21:21:50 -08:00
parent baea26d9ca
commit ce9aef900c
6 changed files with 318 additions and 35 deletions

View File

@ -25,7 +25,7 @@
<div class="flex flex-col lg:flex-row w-full h-full -mt-0.5 pb-2 lg:space-x-4"> <div class="flex flex-col lg:flex-row w-full h-full -mt-0.5 pb-2 lg:space-x-4">
<div <div
id="users-tabs-container" id="users-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" class=" 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"
> >
<button <button
class="px-0.5 py-1 min-w-fit rounded-lg lg:flex-none flex text-right transition {selectedTab === class="px-0.5 py-1 min-w-fit rounded-lg lg:flex-none flex text-right transition {selectedTab ===

View File

@ -18,6 +18,7 @@
import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte'; import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte';
import User from '$lib/components/icons/User.svelte'; import User from '$lib/components/icons/User.svelte';
import UserCircleSolid from '$lib/components/icons/UserCircleSolid.svelte'; import UserCircleSolid from '$lib/components/icons/UserCircleSolid.svelte';
import GroupModal from './Groups/GroupModal.svelte';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
@ -60,6 +61,7 @@
</script> </script>
{#if loaded} {#if loaded}
<GroupModal bind:show={showCreateGroupModal} />
<div class="mt-0.5 mb-2 gap-1 flex flex-col md:flex-row justify-between"> <div class="mt-0.5 mb-2 gap-1 flex flex-col md:flex-row justify-between">
<div class="flex md:self-center text-lg font-medium px-0.5"> <div class="flex md:self-center text-lg font-medium px-0.5">
{$i18n.t('Groups')} {$i18n.t('Groups')}

View File

@ -0,0 +1,61 @@
<script lang="ts">
import { getContext } from 'svelte';
import Textarea from '$lib/components/common/Textarea.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
const i18n = getContext('i18n');
export let name = '';
export let color = '';
export let description = '';
</script>
<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 class="flex flex-col w-full mt-2">
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Color')}</div>
<div class="flex-1">
<Tooltip content={$i18n.t('Hex Color - Leave empty for default color')} placement="top-start">
<div class="flex gap-0.5">
<div class="text-gray-500">#</div>
<input
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
type="text"
bind:value={color}
placeholder={$i18n.t('Hex Color')}
autocomplete="off"
/>
</div>
</Tooltip>
</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-1">
<Textarea
className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none resize-none"
rows={4}
bind:value={description}
placeholder={$i18n.t('Group Description')}
/>
</div>
</div>

View File

@ -10,6 +10,9 @@
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte'; import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte';
import Switch from '$lib/components/common/Switch.svelte'; import Switch from '$lib/components/common/Switch.svelte';
import Display from './Display.svelte';
import Permissions from './Permissions.svelte';
import Users from './Users.svelte';
export let onSubmit: Function = () => {}; export let onSubmit: Function = () => {};
export let onDelete: Function = () => {}; export let onDelete: Function = () => {};
@ -18,9 +21,13 @@
export let edit = false; export let edit = false;
export let group = null; export let group = null;
let selectedTab = 'display';
let name = ''; let name = '';
let description = ''; let description = '';
let permissions = {}; let permissions = {};
let userIds = [];
let loading = false; let loading = false;
@ -30,7 +37,8 @@
const group = { const group = {
name, name,
description, description,
permissions permissions,
user_ids: userIds
}; };
await onSubmit(group); await onSubmit(group);
@ -40,6 +48,7 @@
name = ''; name = '';
permissions = {}; permissions = {};
userIds = [];
}; };
const init = () => { const init = () => {
@ -47,6 +56,7 @@
name = group.name; name = group.name;
description = group.description; description = group.description;
permissions = group?.permissions ?? {}; permissions = group?.permissions ?? {};
userIds = group?.user_ids ?? [];
} }
}; };
@ -59,9 +69,9 @@
}); });
</script> </script>
<Modal size="lg" bind:show> <Modal size="sm" bind:show>
<div> <div>
<div class=" flex justify-between dark:text-gray-100 px-5 pt-4 pb-2"> <div class=" flex justify-between dark:text-gray-100 px-5 pt-4 mb-1">
<div class=" text-lg font-medium self-center font-primary"> <div class=" text-lg font-medium self-center font-primary">
{#if edit} {#if edit}
{$i18n.t('Edit User Group')} {$i18n.t('Edit User Group')}
@ -97,39 +107,56 @@
submitHandler(); submitHandler();
}} }}
> >
<div class="px-1"> <div
<div class="flex gap-2"> class=" tabs flex flex-row overflow-x-auto gap-2.5 text-sm font-medium mb-3 border-b border-b-gray-800 scrollbar-hidden"
<div class="flex flex-col w-full"> >
<div class=" mb-0.5 text-xs text-gray-500">{$i18n.t('Name')}</div> <button
class="px-0.5 pb-1.5 min-w-fit flex text-right transition border-b-2 {selectedTab ===
'display'
? ' dark:border-white'
: 'border-transparent text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
on:click={() => {
selectedTab = 'display';
}}
type="button"
>
{$i18n.t('Display')}
</button>
<div class="flex-1"> <button
<input class="px-0.5 pb-1.5 min-w-fit flex text-right transition border-b-2 {selectedTab ===
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none" 'permissions'
type="text" ? ' dark:border-white'
bind:value={name} : 'border-transparent text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
placeholder={$i18n.t('User Group Name')} on:click={() => {
autocomplete="off" selectedTab = 'permissions';
required }}
/> type="button"
</div> >
</div> {$i18n.t('Permissions')}
</div> </button>
<div class="flex flex-col w-full mt-2"> <button
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Description')}</div> class="px-0.5 pb-1.5 min-w-fit flex text-right transition border-b-2 {selectedTab ===
'users'
<div class="flex-1"> ? ' dark:border-white'
<input : ' border-transparent text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none" on:click={() => {
type="text" selectedTab = 'users';
bind:value={description} }}
placeholder={$i18n.t('Enter description')} type="button"
autocomplete="off" >
/> {$i18n.t('Users')} ({userIds.length})
</button>
</div> </div>
</div> <div class="px-1 h-96 lg:max-h-96 overflow-y-auto scrollbar-hidden">
{#if selectedTab == 'display'}
<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" /> <Display bind:name bind:description />
{:else if selectedTab == 'permissions'}
<Permissions bind:permissions />
{:else if selectedTab == 'users'}
<Users bind:userIds />
{/if}
</div> </div>
<div class="flex justify-end pt-3 text-sm font-medium gap-1.5"> <div class="flex justify-end pt-3 text-sm font-medium gap-1.5">

View File

@ -0,0 +1,186 @@
<script lang="ts">
import { getContext } from 'svelte';
const i18n = getContext('i18n');
import Switch from '$lib/components/common/Switch.svelte';
import { models } from '$lib/stores';
import Minus from '$lib/components/icons/Minus.svelte';
import Plus from '$lib/components/icons/Plus.svelte';
export let permissions = {};
let defaultModelId = '';
let selectedModelId = '';
let filterEnabled = false;
let filterMode = 'include';
let filterModelIds = [];
let workspaceModelsAccess = false;
let workspaceKnowledgeAccess = false;
let workspacePromptsAccess = false;
let workspaceToolsAccess = false;
let workspaceFunctionsAccess = false;
let chatDeletion = true;
let chatEdit = true;
let chatTemporary = true;
</script>
<div>
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('Model Permissions')}</div>
<div class="mb-2">
<div class="flex justify-between items-center text-xs pr-2">
<div class=" text-xs font-medium">{$i18n.t('Model Filtering')}</div>
<Switch bind:state={filterEnabled} />
</div>
</div>
{#if filterEnabled}
<div class="mb-2">
<div class=" space-y-1.5">
<div class="flex flex-col w-full">
<div class="mb-1 flex justify-between">
<div class="text-xs text-gray-500">{$i18n.t('Model IDs')}</div>
</div>
{#if filterModelIds.length > 0}
<div class="flex flex-col">
{#each filterModelIds as modelId, modelIdx}
<div class=" flex gap-2 w-full justify-between items-center">
<div class=" text-sm flex-1 rounded-lg">
{modelId}
</div>
<div class="flex-shrink-0">
<button
type="button"
on:click={() => {
filterModelIds = filterModelIds.filter((_, idx) => idx !== modelIdx);
}}
>
<Minus strokeWidth="2" className="size-3.5" />
</button>
</div>
</div>
{/each}
</div>
{:else}
<div class="text-gray-500 text-xs text-center py-2 px-10">
{$i18n.t('No model IDs')}
</div>
{/if}
</div>
</div>
<hr class=" border-gray-100 dark:border-gray-700/10 mt-2.5 mb-1 w-full" />
<div class="flex items-center">
<select
class="w-full py-1 text-sm rounded-lg bg-transparent {selectedModelId
? ''
: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
bind:value={selectedModelId}
>
<option value="">{$i18n.t('Select a model')}</option>
{#each $models.filter((m) => m?.owned_by !== 'arena') as model}
<option value={model.id} class="bg-gray-50 dark:bg-gray-700">{model.name}</option>
{/each}
</select>
<div>
<button
type="button"
on:click={() => {
if (selectedModelId && !filterModelIds.includes(selectedModelId)) {
filterModelIds = [...filterModelIds, selectedModelId];
selectedModelId = '';
}
}}
>
<Plus className="size-3.5" strokeWidth="2" />
</button>
</div>
</div>
</div>
{/if}
<div class=" space-y-1 mb-3">
<div class="">
<div class="flex justify-between items-center text-xs">
<div class=" text-xs font-medium">{$i18n.t('Default Model')}</div>
</div>
</div>
<div class="flex-1 mr-2">
<select
class="w-full bg-transparent outline-none py-0.5 text-sm"
bind:value={defaultModelId}
placeholder="Select a model"
>
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
{#each filterEnabled ? $models.filter( (model) => filterModelIds.includes(model.id) ) : $models.filter((model) => model.id) as model}
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
{/each}
</select>
</div>
</div>
</div>
<hr class=" border-gray-50 dark:border-gray-850 my-2" />
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('Workspace Permissions')}</div>
<div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Models Access')}</div>
<Switch bind:state={workspaceModelsAccess} />
</div>
<div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Knowledge Access')}</div>
<Switch bind:state={workspaceKnowledgeAccess} />
</div>
<div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Prompts Access')}</div>
<Switch bind:state={workspacePromptsAccess} />
</div>
<!-- <div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Tools Access')}</div>
<Switch bind:state={workspaceToolsAccess} />
</div>
<div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Functions Access')}</div>
<Switch bind:state={workspaceFunctionsAccess} />
</div> -->
</div>
<hr class=" border-gray-50 dark:border-gray-850 my-2" />
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('Chat Permissions')}</div>
<div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Deletion')}</div>
<Switch bind:state={chatDeletion} />
</div>
<div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Editing')}</div>
<Switch bind:state={chatEdit} />
</div>
<div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Temporary Chat')}</div>
<Switch bind:state={chatTemporary} />
</div>
</div>
</div>

View File

@ -0,0 +1,7 @@
<script lang="ts">
export let userIds = [];
</script>
<div>
{JSON.stringify(userIds)}
</div>