Timothy Jaeryang Baek 2 недель назад
Родитель
Сommit
ac879513e5

+ 4 - 0
backend/open_webui/models/channels.py

@@ -57,6 +57,10 @@ class ChannelModel(BaseModel):
 ####################
 
 
+class ChannelResponse(ChannelModel):
+    write_access: bool = False
+
+
 class ChannelForm(BaseModel):
     name: str
     description: Optional[str] = None

+ 24 - 7
backend/open_webui/routers/channels.py

@@ -10,7 +10,13 @@ from pydantic import BaseModel
 from open_webui.socket.main import sio, get_user_ids_from_room
 from open_webui.models.users import Users, UserNameResponse
 
-from open_webui.models.channels import Channels, ChannelModel, ChannelForm
+from open_webui.models.groups import Groups
+from open_webui.models.channels import (
+    Channels,
+    ChannelModel,
+    ChannelForm,
+    ChannelResponse,
+)
 from open_webui.models.messages import (
     Messages,
     MessageModel,
@@ -80,7 +86,7 @@ async def create_new_channel(form_data: ChannelForm, user=Depends(get_admin_user
 ############################
 
 
-@router.get("/{id}", response_model=Optional[ChannelModel])
+@router.get("/{id}", response_model=Optional[ChannelResponse])
 async def get_channel_by_id(id: str, user=Depends(get_verified_user)):
     channel = Channels.get_channel_by_id(id)
     if not channel:
@@ -95,7 +101,16 @@ async def get_channel_by_id(id: str, user=Depends(get_verified_user)):
             status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
         )
 
-    return ChannelModel(**channel.model_dump())
+    write_access = has_access(
+        user.id, type="write", access_control=channel.access_control, strict=False
+    )
+
+    return ChannelResponse(
+        **{
+            **channel.model_dump(),
+            "write_access": write_access or user.role == "admin",
+        }
+    )
 
 
 ############################
@@ -362,7 +377,7 @@ async def new_message_handler(
         )
 
     if user.role != "admin" and not has_access(
-        user.id, type="read", access_control=channel.access_control
+        user.id, type="write", access_control=channel.access_control, strict=False
     ):
         raise HTTPException(
             status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
@@ -658,7 +673,7 @@ async def add_reaction_to_message(
         )
 
     if user.role != "admin" and not has_access(
-        user.id, type="read", access_control=channel.access_control
+        user.id, type="write", access_control=channel.access_control, strict=False
     ):
         raise HTTPException(
             status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
@@ -724,7 +739,7 @@ async def remove_reaction_by_id_and_user_id_and_name(
         )
 
     if user.role != "admin" and not has_access(
-        user.id, type="read", access_control=channel.access_control
+        user.id, type="write", access_control=channel.access_control, strict=False
     ):
         raise HTTPException(
             status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
@@ -806,7 +821,9 @@ async def delete_message_by_id(
     if (
         user.role != "admin"
         and message.user_id != user.id
-        and not has_access(user.id, type="read", access_control=channel.access_control)
+        and not has_access(
+            user.id, type="write", access_control=channel.access_control, strict=False
+        )
     ):
         raise HTTPException(
             status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()

+ 5 - 1
backend/open_webui/utils/access_control.py

@@ -110,9 +110,13 @@ def has_access(
     type: str = "write",
     access_control: Optional[dict] = None,
     user_group_ids: Optional[Set[str]] = None,
+    strict: bool = True,
 ) -> bool:
     if access_control is None:
-        return type == "read"
+        if strict:
+            return type == "read"
+        else:
+            return True
 
     if user_group_ids is None:
         user_groups = Groups.get_groups_by_member_id(user_id)

+ 5 - 0
src/lib/components/channel/Channel.svelte

@@ -14,6 +14,7 @@
 	import Drawer from '../common/Drawer.svelte';
 	import EllipsisVertical from '../icons/EllipsisVertical.svelte';
 	import Thread from './Thread.svelte';
+	import i18n from '$lib/i18n';
 
 	export let id = '';
 
@@ -252,6 +253,10 @@
 					{typingUsers}
 					userSuggestions={true}
 					channelSuggestions={true}
+					disabled={!channel?.write_access}
+					placeholder={!channel?.write_access
+						? $i18n.t('You do not have permission to send messages in this channel.')
+						: $i18n.t('Type here...')}
 					{onChange}
 					onSubmit={submitHandler}
 					{scrollToBottom}

+ 7 - 2
src/lib/components/channel/MessageInput.svelte

@@ -38,7 +38,7 @@
 	import MentionList from './MessageInput/MentionList.svelte';
 	import Skeleton from '../chat/Messages/Skeleton.svelte';
 
-	export let placeholder = $i18n.t('Send a Message');
+	export let placeholder = $i18n.t('Type here...');
 
 	export let id = null;
 	export let chatInputElement;
@@ -53,6 +53,7 @@
 	export let scrollEnd = true;
 	export let scrollToBottom: Function = () => {};
 
+	export let disabled = false;
 	export let acceptFiles = true;
 	export let showFormattingToolbar = true;
 
@@ -731,7 +732,9 @@
 				</div>
 			</div>
 
-			<div class="">
+			<div
+				class="{disabled ? 'opacity-50 pointer-events-none cursor-not-allowed' : ''} relative z-20"
+			>
 				{#if recording}
 					<VoiceRecording
 						bind:recording
@@ -836,6 +839,8 @@
 											bind:this={chatInputElement}
 											json={true}
 											messageInput={true}
+											editable={!disabled}
+											{placeholder}
 											richText={$settings?.richTextInput ?? true}
 											showFormattingToolbar={$settings?.showFormattingToolbar ?? false}
 											shiftEnter={!($settings?.ctrlEnterToSend ?? false) &&

+ 4 - 0
src/lib/components/channel/Thread.svelte

@@ -201,6 +201,10 @@
 			<div class=" pb-[1rem] px-2.5 w-full">
 				<MessageInput
 					id={threadId}
+					disabled={!channel?.write_access}
+					placeholder={!channel?.write_access
+						? $i18n.t('You do not have permission to send messages in this thread.')
+						: $i18n.t('Reply to thread...')}
 					typingUsersClassName="from-gray-50 dark:from-gray-850"
 					{typingUsers}
 					userSuggestions={true}

+ 8 - 3
src/lib/components/common/RichTextInput.svelte

@@ -168,7 +168,7 @@
 	export let documentId = '';
 
 	export let className = 'input-prose';
-	export let placeholder = 'Type here...';
+	export let placeholder = $i18n.t('Type here...');
 	let _placeholder = placeholder;
 
 	$: if (placeholder !== _placeholder) {
@@ -689,7 +689,7 @@
 					link: link
 				}),
 				...(dragHandle ? [ListItemDragHandle] : []),
-				Placeholder.configure({ placeholder: () => _placeholder }),
+				Placeholder.configure({ placeholder: () => _placeholder, showOnlyWhenEditable: false }),
 				SelectionDecoration,
 
 				...(richText
@@ -1123,4 +1123,9 @@
 	</div>
 {/if}
 
-<div bind:this={element} class="relative w-full min-w-full h-full min-h-fit {className}" />
+<div
+	bind:this={element}
+	class="relative w-full min-w-full h-full min-h-fit {className} {!editable
+		? 'cursor-not-allowed'
+		: ''}"
+/>

+ 1 - 1
src/lib/components/layout/Sidebar/ChannelModal.svelte

@@ -126,7 +126,7 @@
 
 					<div class="my-2 -mx-2">
 						<div class="px-4 py-3 bg-gray-50 dark:bg-gray-950 rounded-3xl">
-							<AccessControl bind:accessControl />
+							<AccessControl bind:accessControl accessRoles={['read', 'write']} />
 						</div>
 					</div>