mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-31 07:13:56 +00:00
refac
This commit is contained in:
parent
385d8c1d02
commit
2262073146
5 changed files with 81 additions and 11 deletions
|
|
@ -2531,7 +2531,8 @@ class ChatTable:
|
|||
chat = await session.get(Chat, id)
|
||||
chat.folder_id = folder_id
|
||||
chat.updated_at = int(time.time())
|
||||
chat.last_read_at = int(time.time())
|
||||
if chat.user_id == user_id:
|
||||
chat.last_read_at = int(time.time())
|
||||
chat.pinned = False
|
||||
if folder_id is not None:
|
||||
# Folder listings only show unarchived chats, so moving an archived
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ from open_webui.models.tags import TagModel, Tags
|
|||
from open_webui.socket.main import get_event_emitter
|
||||
from open_webui.tasks import get_response_streams_by_chat_id, has_active_tasks, stop_item_tasks
|
||||
from open_webui.utils.access_control import filter_allowed_access_grants, has_permission
|
||||
from open_webui.utils.access_control.folders import has_folder_write_access
|
||||
from open_webui.utils.access_control.folders import has_chat_move_access, has_folder_write_access
|
||||
from open_webui.utils.auth import bearer_security, get_admin_user, get_current_user, get_verified_user
|
||||
from open_webui.utils.chat_fork import build_fork_history
|
||||
from open_webui.utils.context_compaction import compact_chat_branch, get_chat_context_usage
|
||||
|
|
@ -1452,7 +1452,20 @@ async def update_chat_by_id(
|
|||
user=Depends(get_verified_user),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
chat = await Chats.get_chat_by_id(id, db=db)
|
||||
if chat and chat.user_id != user.id:
|
||||
folder = await Folders.get_folder_by_id_and_user_id(chat.folder_id, user.id, db=db) if chat.folder_id else None
|
||||
if (
|
||||
not folder
|
||||
or set(form_data.chat) != {'title'}
|
||||
or form_data.variables is not None
|
||||
or form_data.folder_id is not None
|
||||
):
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED)
|
||||
title = form_data.chat['title']
|
||||
if not isinstance(title, str) or not title.strip():
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT('Invalid title'))
|
||||
form_data.chat['title'] = title.strip()
|
||||
if chat:
|
||||
touch = 'history' in form_data.chat or 'messages' in form_data.chat
|
||||
chat = await Chats.update_chat_by_id(id, form_data.chat, db=db, touch=touch)
|
||||
|
|
@ -1474,6 +1487,8 @@ async def update_chat_by_id(
|
|||
subject_id=id,
|
||||
data={'title': chat.title},
|
||||
)
|
||||
if chat.user_id != user.id:
|
||||
return await shared_chat_response(chat, user, db=db)
|
||||
return ChatResponse.model_validate(chat, from_attributes=True)
|
||||
else:
|
||||
raise HTTPException(
|
||||
|
|
@ -2291,7 +2306,10 @@ async def update_chat_folder_id_by_id(
|
|||
user=Depends(get_verified_user),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
chat = await Chats.get_chat_by_id(id, db=db)
|
||||
if chat and chat.user_id != user.id:
|
||||
if not await has_chat_move_access(user.id, chat.folder_id, form_data.folder_id, db=db):
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT())
|
||||
if chat:
|
||||
# None is allowed: it moves the chat out of any folder.
|
||||
if form_data.folder_id is not None and not await has_folder_write_access(user.id, form_data.folder_id, db=db):
|
||||
|
|
@ -2308,6 +2326,8 @@ async def update_chat_folder_id_by_id(
|
|||
subject_id=id,
|
||||
data={'folder_id': form_data.folder_id},
|
||||
)
|
||||
if chat.user_id != user.id:
|
||||
return await shared_chat_response(chat, user, db=db)
|
||||
return ChatResponse.model_validate(chat, from_attributes=True)
|
||||
else:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT())
|
||||
|
|
|
|||
|
|
@ -32,3 +32,28 @@ async def has_folder_write_access(user_id: str, folder_id: str, db: AsyncSession
|
|||
if not folder:
|
||||
return False
|
||||
return await has_folder_access(user_id, folder, 'write', db)
|
||||
|
||||
|
||||
async def has_chat_move_access(
|
||||
user_id: str, source_folder_id: str | None, target_folder_id: str | None, db: AsyncSession | None = None
|
||||
) -> bool:
|
||||
"""Only the owner may reorganize another user's chat within the same folder tree."""
|
||||
|
||||
async def get_owned_folder_root_id(folder_id):
|
||||
seen_ids = set()
|
||||
shared_root_id = None
|
||||
while folder_id and folder_id not in seen_ids:
|
||||
seen_ids.add(folder_id)
|
||||
folder = await Folders.get_folder_by_id(folder_id, db=db)
|
||||
if not folder or folder.user_id != user_id:
|
||||
return None
|
||||
if await AccessGrants.get_grants_by_resource('folder', folder.id, db=db):
|
||||
shared_root_id = folder.id
|
||||
if not folder.parent_id:
|
||||
# A private parent must not let moves escape a shared subtree.
|
||||
return shared_root_id or folder.id
|
||||
folder_id = folder.parent_id
|
||||
return None
|
||||
|
||||
source_root_id = await get_owned_folder_root_id(source_folder_id)
|
||||
return source_root_id is not None and source_root_id == await get_owned_folder_root_id(target_folder_id)
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@
|
|||
import ChatParticipants from '$lib/components/common/ChatParticipants.svelte';
|
||||
import ChatIcon from './icons/Chat.svelte';
|
||||
import MoreHorizontalIcon from './icons/MoreHorizontal.svelte';
|
||||
import EditPencilIcon from './icons/EditPencil.svelte';
|
||||
import SparklesIcon from './icons/Sparkles.svelte';
|
||||
import ArchiveBoxIcon from '$lib/components/icons/ArchiveBox.svelte';
|
||||
import GarbageBinIcon from '$lib/components/icons/GarbageBin.svelte';
|
||||
|
|
@ -96,6 +97,8 @@
|
|||
export let selected = false;
|
||||
export let shiftKey = false;
|
||||
export let readonly = false;
|
||||
export let canMove = false;
|
||||
export let canRename = false;
|
||||
|
||||
export let ownerName: string | null = null;
|
||||
export let ownerUserId: string | null = null;
|
||||
|
|
@ -211,7 +214,7 @@
|
|||
};
|
||||
|
||||
const renameChatFromDoubleClick = async (e: MouseEvent) => {
|
||||
if (readonly || selecting || e.shiftKey) return;
|
||||
if ((readonly && !canRename) || selecting || e.shiftKey) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
|
@ -249,9 +252,13 @@
|
|||
if (!title) {
|
||||
toast.error($i18n.t('Title cannot be an empty string.'));
|
||||
} else {
|
||||
await updateChatById(localStorage.token, id, {
|
||||
const res = await updateChatById(localStorage.token, id, {
|
||||
title: title
|
||||
}).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
if (!res) return;
|
||||
|
||||
if (id === $chatId) {
|
||||
_chatTitle.set(title);
|
||||
|
|
@ -573,7 +580,9 @@
|
|||
dir="auto"
|
||||
class="text-left self-center overflow-hidden w-full h-5 truncate {unread
|
||||
? 'font-normal text-gray-800 dark:text-gray-200'
|
||||
: ''} {($mobile || showInlineActions) && !readonly && !selecting ? 'pr-12' : ''}"
|
||||
: ''} {($mobile || showInlineActions) && (!readonly || canRename) && !selecting
|
||||
? 'pr-12'
|
||||
: ''}"
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
|
|
@ -588,7 +597,7 @@
|
|||
{/if}
|
||||
|
||||
<!-- Time ago indicator -->
|
||||
{#if (updatedAt ?? createdAt) && !showInlineActions && !($mobile && !readonly)}
|
||||
{#if (updatedAt ?? createdAt) && !showInlineActions && !($mobile && (!readonly || canRename))}
|
||||
<div
|
||||
class="shrink-0 self-center text-[0.625rem] text-gray-400 dark:text-gray-500 pl-2"
|
||||
aria-hidden="true"
|
||||
|
|
@ -632,7 +641,7 @@
|
|||
data-sidebar-chat-readonly={readonly}
|
||||
bind:this={itemElement}
|
||||
class=" w-full {className} relative group"
|
||||
draggable={!confirmEdit && !readonly && !selecting}
|
||||
draggable={!confirmEdit && (!readonly || canMove) && !selecting}
|
||||
on:mouseenter={() => {
|
||||
mouseOver = true;
|
||||
}}
|
||||
|
|
@ -763,7 +772,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !readonly && !selecting}
|
||||
{#if (!readonly || canRename) && !selecting}
|
||||
<div
|
||||
id="sidebar-chat-item-menu"
|
||||
class="{$mobile
|
||||
|
|
@ -774,7 +783,20 @@
|
|||
? 'right-[0.5rem]'
|
||||
: 'right-1'} inset-y-0 mr-1.5 flex items-center"
|
||||
>
|
||||
{#if confirmEdit}
|
||||
{#if readonly}
|
||||
{#if !confirmEdit}
|
||||
<Tooltip content={$i18n.t('Rename')} className="flex self-center z-10 items-end">
|
||||
<button
|
||||
type="button"
|
||||
aria-label={$i18n.t('Rename')}
|
||||
class="flex size-5 items-center justify-center self-center dark:hover:text-white transition"
|
||||
on:click={renameHandler}
|
||||
>
|
||||
<EditPencilIcon className="size-3.5" strokeWidth="1.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
{:else if confirmEdit}
|
||||
<div
|
||||
class="flex self-center items-center space-x-1.5 z-10 translate-y-[0.5px] -translate-x-[0.5px]"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1013,6 +1013,8 @@
|
|||
? chat.user_id
|
||||
: null}
|
||||
readonly={chat.user_id !== $user?.id}
|
||||
canMove={folders[folderId]?.user_id === $user?.id}
|
||||
canRename={folders[folderId]?.user_id === $user?.id}
|
||||
{shiftKey}
|
||||
onReadStateChange={applyReadState}
|
||||
on:change={(e) => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue