enh: user chat edit permission

This commit is contained in:
Timothy J. Baek 2024-08-19 16:49:40 +02:00
parent dfa5041b6f
commit cbadf39d7d
8 changed files with 178 additions and 53 deletions

View File

@ -808,10 +808,24 @@ USER_PERMISSIONS_CHAT_DELETION = (
os.environ.get("USER_PERMISSIONS_CHAT_DELETION", "True").lower() == "true" os.environ.get("USER_PERMISSIONS_CHAT_DELETION", "True").lower() == "true"
) )
USER_PERMISSIONS_CHAT_EDITING = (
os.environ.get("USER_PERMISSIONS_CHAT_EDITING", "True").lower() == "true"
)
USER_PERMISSIONS_CHAT_TEMPORARY = (
os.environ.get("USER_PERMISSIONS_CHAT_TEMPORARY", "True").lower() == "true"
)
USER_PERMISSIONS = PersistentConfig( USER_PERMISSIONS = PersistentConfig(
"USER_PERMISSIONS", "USER_PERMISSIONS",
"ui.user_permissions", "ui.user_permissions",
{"chat": {"deletion": USER_PERMISSIONS_CHAT_DELETION}}, {
"chat": {
"deletion": USER_PERMISSIONS_CHAT_DELETION,
"editing": USER_PERMISSIONS_CHAT_EDITING,
"temporary": USER_PERMISSIONS_CHAT_TEMPORARY,
}
},
) )
ENABLE_MODEL_FILTER = PersistentConfig( ENABLE_MODEL_FILTER = PersistentConfig(

View File

@ -68,6 +68,7 @@ from utils.utils import (
get_http_authorization_cred, get_http_authorization_cred,
get_password_hash, get_password_hash,
create_token, create_token,
decode_token,
) )
from utils.task import ( from utils.task import (
title_generation_template, title_generation_template,
@ -1957,26 +1958,47 @@ async def update_pipeline_valves(
@app.get("/api/config") @app.get("/api/config")
async def get_app_config(): async def get_app_config(request: Request):
user = None
if "token" in request.cookies:
token = request.cookies.get("token")
data = decode_token(token)
if data is not None and "id" in data:
user = Users.get_user_by_id(data["id"])
return { return {
"status": True, "status": True,
"name": WEBUI_NAME, "name": WEBUI_NAME,
"version": VERSION, "version": VERSION,
"default_locale": str(DEFAULT_LOCALE), "default_locale": str(DEFAULT_LOCALE),
"default_models": webui_app.state.config.DEFAULT_MODELS, "oauth": {
"default_prompt_suggestions": webui_app.state.config.DEFAULT_PROMPT_SUGGESTIONS, "providers": {
name: config.get("name", name)
for name, config in OAUTH_PROVIDERS.items()
}
},
"features": { "features": {
"auth": WEBUI_AUTH, "auth": WEBUI_AUTH,
"auth_trusted_header": bool(webui_app.state.AUTH_TRUSTED_EMAIL_HEADER), "auth_trusted_header": bool(webui_app.state.AUTH_TRUSTED_EMAIL_HEADER),
"enable_signup": webui_app.state.config.ENABLE_SIGNUP, "enable_signup": webui_app.state.config.ENABLE_SIGNUP,
"enable_login_form": webui_app.state.config.ENABLE_LOGIN_FORM, "enable_login_form": webui_app.state.config.ENABLE_LOGIN_FORM,
**(
{
"enable_web_search": rag_app.state.config.ENABLE_RAG_WEB_SEARCH, "enable_web_search": rag_app.state.config.ENABLE_RAG_WEB_SEARCH,
"enable_image_generation": images_app.state.config.ENABLED, "enable_image_generation": images_app.state.config.ENABLED,
"enable_community_sharing": webui_app.state.config.ENABLE_COMMUNITY_SHARING, "enable_community_sharing": webui_app.state.config.ENABLE_COMMUNITY_SHARING,
"enable_message_rating": webui_app.state.config.ENABLE_MESSAGE_RATING, "enable_message_rating": webui_app.state.config.ENABLE_MESSAGE_RATING,
"enable_admin_export": ENABLE_ADMIN_EXPORT, "enable_admin_export": ENABLE_ADMIN_EXPORT,
"enable_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS, "enable_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS,
}
if user is not None
else {}
),
}, },
**(
{
"default_models": webui_app.state.config.DEFAULT_MODELS,
"default_prompt_suggestions": webui_app.state.config.DEFAULT_PROMPT_SUGGESTIONS,
"audio": { "audio": {
"tts": { "tts": {
"engine": audio_app.state.config.TTS_ENGINE, "engine": audio_app.state.config.TTS_ENGINE,
@ -1986,12 +2008,11 @@ async def get_app_config():
"engine": audio_app.state.config.STT_ENGINE, "engine": audio_app.state.config.STT_ENGINE,
}, },
}, },
"oauth": { "permissions": {**webui_app.state.config.USER_PERMISSIONS},
"providers": {
name: config.get("name", name)
for name, config in OAUTH_PROVIDERS.items()
} }
}, if user is not None
else {}
),
} }

View File

@ -665,6 +665,7 @@ export const getBackendConfig = async () => {
const res = await fetch(`${WEBUI_BASE_URL}/api/config`, { const res = await fetch(`${WEBUI_BASE_URL}/api/config`, {
method: 'GET', method: 'GET',
credentials: 'include',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }

View File

@ -18,7 +18,9 @@
let whitelistModels = ['']; let whitelistModels = [''];
let permissions = { let permissions = {
chat: { chat: {
deletion: true deletion: true,
edit: true,
temporary: true
} }
}; };
@ -92,6 +94,88 @@
{/if} {/if}
</button> </button>
</div> </div>
<div class=" flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Editing')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
on:click={() => {
permissions.chat.editing = !(permissions?.chat?.editing ?? true);
}}
type="button"
>
{#if permissions?.chat?.editing ?? true}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"
/>
</svg>
<span class="ml-2 self-center">{$i18n.t('Allow')}</span>
{:else}
<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="M8 1a3.5 3.5 0 0 0-3.5 3.5V7A1.5 1.5 0 0 0 3 8.5v5A1.5 1.5 0 0 0 4.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 11.5 7V4.5A3.5 3.5 0 0 0 8 1Zm2 6V4.5a2 2 0 1 0-4 0V7h4Z"
clip-rule="evenodd"
/>
</svg>
<span class="ml-2 self-center">{$i18n.t("Don't Allow")}</span>
{/if}
</button>
</div>
<div class=" flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Temporary Chat')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
on:click={() => {
permissions.chat.temporary = !(permissions?.chat?.temporary ?? true);
}}
type="button"
>
{#if permissions?.chat?.temporary ?? true}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"
/>
</svg>
<span class="ml-2 self-center">{$i18n.t('Allow')}</span>
{:else}
<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="M8 1a3.5 3.5 0 0 0-3.5 3.5V7A1.5 1.5 0 0 0 3 8.5v5A1.5 1.5 0 0 0 4.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 11.5 7V4.5A3.5 3.5 0 0 0 8 1Zm2 6V4.5a2 2 0 1 0-4 0V7h4Z"
clip-rule="evenodd"
/>
</svg>
<span class="ml-2 self-center">{$i18n.t("Don't Allow")}</span>
{/if}
</button>
</div>
</div> </div>
<hr class=" dark:border-gray-850 my-2" /> <hr class=" dark:border-gray-850 my-2" />

View File

@ -150,7 +150,7 @@
}` }`
: `border-gray-50 dark:border-gray-850 border-dashed ${ : `border-gray-50 dark:border-gray-850 border-dashed ${
$mobile ? 'min-w-full' : 'min-w-80' $mobile ? 'min-w-full' : 'min-w-80'
}`} transition-[width] transform duration-300 p-5 rounded-2xl" }`} transition-all p-5 rounded-2xl"
on:click={() => { on:click={() => {
if (currentMessageId != message.id) { if (currentMessageId != message.id) {
currentMessageId = message.id; currentMessageId = message.id;

View File

@ -458,6 +458,7 @@
{#if message.done} {#if message.done}
{#if !readOnly} {#if !readOnly}
{#if $user.role === 'user' ? ($config?.permissions?.chat?.editing ?? true) : true}
<Tooltip content={$i18n.t('Edit')} placement="bottom"> <Tooltip content={$i18n.t('Edit')} placement="bottom">
<button <button
class="{isLastMessage class="{isLastMessage
@ -484,6 +485,7 @@
</button> </button>
</Tooltip> </Tooltip>
{/if} {/if}
{/if}
<Tooltip content={$i18n.t('Copy')} placement="bottom"> <Tooltip content={$i18n.t('Copy')} placement="bottom">
<button <button

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { models, showSettings, settings, user, mobile } from '$lib/stores'; import { models, showSettings, settings, user, mobile, config } from '$lib/stores';
import { onMount, tick, getContext } from 'svelte'; import { onMount, tick, getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import Selector from './ModelSelector/Selector.svelte'; import Selector from './ModelSelector/Selector.svelte';
@ -46,7 +46,9 @@
label: model.name, label: model.name,
model: model model: model
}))} }))}
showTemporaryChatControl={true} showTemporaryChatControl={$user.role === 'user'
? ($config?.permissions?.chat?.temporary ?? true)
: true}
bind:value={selectedModel} bind:value={selectedModel}
/> />
</div> </div>

View File

@ -160,6 +160,7 @@
if (sessionUser) { if (sessionUser) {
// Save Session User to Store // Save Session User to Store
await user.set(sessionUser); await user.set(sessionUser);
await config.set(await getBackendConfig());
} else { } else {
// Redirect Invalid Session User to /auth Page // Redirect Invalid Session User to /auth Page
localStorage.removeItem('token'); localStorage.removeItem('token');