浏览代码

enh: user chat edit permission

Timothy J. Baek 8 月之前
父节点
当前提交
cbadf39d7d

+ 15 - 1
backend/config.py

@@ -808,10 +808,24 @@ USER_PERMISSIONS_CHAT_DELETION = (
     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",
     "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(

+ 44 - 23
backend/main.py

@@ -68,6 +68,7 @@ from utils.utils import (
     get_http_authorization_cred,
     get_password_hash,
     create_token,
+    decode_token,
 )
 from utils.task import (
     title_generation_template,
@@ -1957,41 +1958,61 @@ async def update_pipeline_valves(
 
 
 @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 {
         "status": True,
         "name": WEBUI_NAME,
         "version": VERSION,
         "default_locale": str(DEFAULT_LOCALE),
-        "default_models": webui_app.state.config.DEFAULT_MODELS,
-        "default_prompt_suggestions": webui_app.state.config.DEFAULT_PROMPT_SUGGESTIONS,
+        "oauth": {
+            "providers": {
+                name: config.get("name", name)
+                for name, config in OAUTH_PROVIDERS.items()
+            }
+        },
         "features": {
             "auth": WEBUI_AUTH,
             "auth_trusted_header": bool(webui_app.state.AUTH_TRUSTED_EMAIL_HEADER),
             "enable_signup": webui_app.state.config.ENABLE_SIGNUP,
             "enable_login_form": webui_app.state.config.ENABLE_LOGIN_FORM,
-            "enable_web_search": rag_app.state.config.ENABLE_RAG_WEB_SEARCH,
-            "enable_image_generation": images_app.state.config.ENABLED,
-            "enable_community_sharing": webui_app.state.config.ENABLE_COMMUNITY_SHARING,
-            "enable_message_rating": webui_app.state.config.ENABLE_MESSAGE_RATING,
-            "enable_admin_export": ENABLE_ADMIN_EXPORT,
-            "enable_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS,
-        },
-        "audio": {
-            "tts": {
-                "engine": audio_app.state.config.TTS_ENGINE,
-                "voice": audio_app.state.config.TTS_VOICE,
-            },
-            "stt": {
-                "engine": audio_app.state.config.STT_ENGINE,
-            },
+            **(
+                {
+                    "enable_web_search": rag_app.state.config.ENABLE_RAG_WEB_SEARCH,
+                    "enable_image_generation": images_app.state.config.ENABLED,
+                    "enable_community_sharing": webui_app.state.config.ENABLE_COMMUNITY_SHARING,
+                    "enable_message_rating": webui_app.state.config.ENABLE_MESSAGE_RATING,
+                    "enable_admin_export": ENABLE_ADMIN_EXPORT,
+                    "enable_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS,
+                }
+                if user is not None
+                else {}
+            ),
         },
-        "oauth": {
-            "providers": {
-                name: config.get("name", name)
-                for name, config in OAUTH_PROVIDERS.items()
+        **(
+            {
+                "default_models": webui_app.state.config.DEFAULT_MODELS,
+                "default_prompt_suggestions": webui_app.state.config.DEFAULT_PROMPT_SUGGESTIONS,
+                "audio": {
+                    "tts": {
+                        "engine": audio_app.state.config.TTS_ENGINE,
+                        "voice": audio_app.state.config.TTS_VOICE,
+                    },
+                    "stt": {
+                        "engine": audio_app.state.config.STT_ENGINE,
+                    },
+                },
+                "permissions": {**webui_app.state.config.USER_PERMISSIONS},
             }
-        },
+            if user is not None
+            else {}
+        ),
     }
 
 

+ 1 - 0
src/lib/apis/index.ts

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

+ 85 - 1
src/lib/components/admin/Settings/Users.svelte

@@ -18,7 +18,9 @@
 	let whitelistModels = [''];
 	let permissions = {
 		chat: {
-			deletion: true
+			deletion: true,
+			edit: true,
+			temporary: true
 		}
 	};
 
@@ -92,6 +94,88 @@
 					{/if}
 				</button>
 			</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>
 
 		<hr class=" dark:border-gray-850 my-2" />

+ 1 - 1
src/lib/components/chat/Messages/MultiResponseMessages.svelte

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

+ 26 - 24
src/lib/components/chat/Messages/ResponseMessage.svelte

@@ -458,31 +458,33 @@
 
 							{#if message.done}
 								{#if !readOnly}
-									<Tooltip content={$i18n.t('Edit')} placement="bottom">
-										<button
-											class="{isLastMessage
-												? 'visible'
-												: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
-											on:click={() => {
-												editMessageHandler();
-											}}
-										>
-											<svg
-												xmlns="http://www.w3.org/2000/svg"
-												fill="none"
-												viewBox="0 0 24 24"
-												stroke-width="2.3"
-												stroke="currentColor"
-												class="w-4 h-4"
+									{#if $user.role === 'user' ? ($config?.permissions?.chat?.editing ?? true) : true}
+										<Tooltip content={$i18n.t('Edit')} placement="bottom">
+											<button
+												class="{isLastMessage
+													? 'visible'
+													: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
+												on:click={() => {
+													editMessageHandler();
+												}}
 											>
-												<path
-													stroke-linecap="round"
-													stroke-linejoin="round"
-													d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
-												/>
-											</svg>
-										</button>
-									</Tooltip>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													fill="none"
+													viewBox="0 0 24 24"
+													stroke-width="2.3"
+													stroke="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														stroke-linecap="round"
+														stroke-linejoin="round"
+														d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
+													/>
+												</svg>
+											</button>
+										</Tooltip>
+									{/if}
 								{/if}
 
 								<Tooltip content={$i18n.t('Copy')} placement="bottom">

+ 4 - 2
src/lib/components/chat/ModelSelector.svelte

@@ -1,5 +1,5 @@
 <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 { toast } from 'svelte-sonner';
 	import Selector from './ModelSelector/Selector.svelte';
@@ -46,7 +46,9 @@
 							label: model.name,
 							model: model
 						}))}
-						showTemporaryChatControl={true}
+						showTemporaryChatControl={$user.role === 'user'
+							? ($config?.permissions?.chat?.temporary ?? true)
+							: true}
 						bind:value={selectedModel}
 					/>
 				</div>

+ 1 - 0
src/routes/+layout.svelte

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