From 3127f1b46255626ea92eb0b598e45078287303f1 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 14 May 2026 01:43:33 +0900 Subject: [PATCH] refac --- backend/open_webui/models/files.py | 13 ++++ backend/open_webui/routers/files.py | 40 ++++++++++++ src/lib/apis/files/index.ts | 29 +++++++++ .../workspace/Knowledge/KnowledgeBase.svelte | 18 +++++- .../KnowledgeBase/DirectoryRow.svelte | 2 +- .../Knowledge/KnowledgeBase/Files.svelte | 64 +++++++++++++++++-- 6 files changed, 157 insertions(+), 9 deletions(-) diff --git a/backend/open_webui/models/files.py b/backend/open_webui/models/files.py index dc45671280..17fe85b294 100644 --- a/backend/open_webui/models/files.py +++ b/backend/open_webui/models/files.py @@ -384,6 +384,19 @@ class FilesTable: except Exception: return None + async def update_file_name_by_id(self, id: str, name: str, db: AsyncSession | None = None) -> FileModel | None: + async with get_async_db_context(db) as db: + try: + result = await db.execute(select(File).filter_by(id=id)) + file = result.scalars().first() + file.filename = name + file.meta = {**(file.meta if file.meta else {}), 'name': name} + file.updated_at = int(time.time()) + await db.commit() + return FileModel.model_validate(file) + except Exception: + return None + async def delete_file_by_id(self, id: str, db: AsyncSession | None = None) -> bool: async with get_async_db_context(db) as db: try: diff --git a/backend/open_webui/routers/files.py b/backend/open_webui/routers/files.py index 74ba2365a6..ba71f79b3d 100644 --- a/backend/open_webui/routers/files.py +++ b/backend/open_webui/routers/files.py @@ -765,6 +765,46 @@ async def get_file_content_by_id( ) +############################ +# Rename File By Id +############################ + + +class FileRenameForm(BaseModel): + filename: str + + +@router.post('/{id}/rename') +async def rename_file_by_id( + id: str, + form_data: FileRenameForm, + user=Depends(get_verified_user), + db: AsyncSession = Depends(get_async_session), +): + file = await Files.get_file_by_id(id, db=db) + + if not file: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=ERROR_MESSAGES.NOT_FOUND, + ) + + if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'write', user, db=db): + result = await Files.update_file_name_by_id(id, form_data.filename, db=db) + if result: + return result + else: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ERROR_MESSAGES.DEFAULT('Error renaming file'), + ) + else: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=ERROR_MESSAGES.NOT_FOUND, + ) + + ############################ # Delete File By Id ############################ diff --git a/src/lib/apis/files/index.ts b/src/lib/apis/files/index.ts index 15785b354d..37a49f7c7b 100644 --- a/src/lib/apis/files/index.ts +++ b/src/lib/apis/files/index.ts @@ -306,6 +306,35 @@ export const getFileContentById = async (id: string) => { return res; }; +export const renameFileById = async (token: string, id: string, filename: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/files/${id}/rename`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ filename }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const deleteFileById = async (token: string, id: string) => { let error = null; diff --git a/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte b/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte index 647da19568..9f95de1819 100644 --- a/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte +++ b/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte @@ -22,7 +22,8 @@ updateFileDataContentById, uploadFile, deleteFileById, - getFileById + getFileById, + renameFileById } from '$lib/apis/files'; import { addFileToKnowledgeById, @@ -686,6 +687,18 @@ } }; + const renameFileHandler = async (fileId: string, name: string) => { + try { + const res = await renameFileById(localStorage.token, fileId, name); + if (res) { + toast.success($i18n.t('File renamed.')); + getItemsPage(); + } + } catch (e) { + toast.error(`${e}`); + } + }; + let debounceTimeout = null; let mediaQuery; @@ -1197,7 +1210,8 @@ deleteFileHandler(fileId); }} - onNavigateDirectory={(dirId) => navigateToDirectory(dirId)} + onRename={(fileId, name) => renameFileHandler(fileId, name)} + onNavigateDirectory={(dirId) => navigateToDirectory(dirId)} onRenameDirectory={(id, name) => renameDirectoryHandler(id, name)} onDeleteDirectory={(id) => confirmDeleteDirectory(id)} onMoveFileToDirectory={(fileId, dirId) => diff --git a/src/lib/components/workspace/Knowledge/KnowledgeBase/DirectoryRow.svelte b/src/lib/components/workspace/Knowledge/KnowledgeBase/DirectoryRow.svelte index 70de8b5444..6c36904508 100644 --- a/src/lib/components/workspace/Knowledge/KnowledgeBase/DirectoryRow.svelte +++ b/src/lib/components/workspace/Knowledge/KnowledgeBase/DirectoryRow.svelte @@ -128,7 +128,7 @@ { if (e.key === 'Enter') submitRename(); if (e.key === 'Escape') cancelRename(); diff --git a/src/lib/components/workspace/Knowledge/KnowledgeBase/Files.svelte b/src/lib/components/workspace/Knowledge/KnowledgeBase/Files.svelte index fbe5082fe3..13e72ae8ac 100644 --- a/src/lib/components/workspace/Knowledge/KnowledgeBase/Files.svelte +++ b/src/lib/components/workspace/Knowledge/KnowledgeBase/Files.svelte @@ -19,6 +19,7 @@ import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte'; import Download from '$lib/components/icons/Download.svelte'; import GarbageBin from '$lib/components/icons/GarbageBin.svelte'; + import Pencil from '$lib/components/icons/Pencil.svelte'; import Spinner from '$lib/components/common/Spinner.svelte'; import DirectoryRow from './DirectoryRow.svelte'; @@ -29,11 +30,33 @@ export let onClick = (fileId) => {}; export let onDelete = (fileId) => {}; + export let onRename = (fileId: string, name: string) => {}; export let onNavigateDirectory = (directoryId: string) => {}; export let onRenameDirectory = (id: string, name: string) => {}; export let onDeleteDirectory = (id: string) => {}; export let onMoveFileToDirectory = (fileId: string, directoryId: string) => {}; export let onMoveDirectoryToDirectory = (dirId: string, targetDirectoryId: string) => {}; + + let editingFileId: string | null = null; + let editName = ''; + let editInput: HTMLInputElement; + + const startRename = (file: any) => { + editingFileId = file?.id ?? file?.tempId; + editName = file?.name ?? file?.meta?.name ?? ''; + setTimeout(() => editInput?.select(), 0); + }; + + const submitRename = () => { + if (editingFileId && editName.trim()) { + onRename(editingFileId, editName.trim()); + } + editingFileId = null; + }; + + const cancelRename = () => { + editingFileId = null; + };
@@ -90,15 +113,34 @@ on:click={() => { onClick(file?.id ?? file?.tempId); }} + on:dblclick={() => { + if (knowledge?.write_access) startRename(file); + }} >
-
- {file?.name ?? file?.meta?.name} - {#if file?.meta?.size} - {formatFileSize(file?.meta?.size)} - {/if} -
+ {#if editingFileId === (file?.id ?? file?.tempId)} + + { + if (e.key === 'Enter') submitRename(); + if (e.key === 'Escape') cancelRename(); + }} + on:blur={submitRename} + on:click={(e) => e.stopPropagation()} + autofocus + /> + {:else} +
+ {file?.name ?? file?.meta?.name} + {#if file?.meta?.size} + {formatFileSize(file?.meta?.size)} + {/if} +
+ {/if}
@@ -143,6 +185,16 @@
+