mirror of
https://github.com/open-webui/open-webui.git
synced 2026-03-09 07:18:29 -05:00
enh: create subfolder
Co-Authored-By: Colin Chen <1207878+silenceroom@users.noreply.github.com>
This commit is contained in:
@@ -71,6 +71,7 @@ class FolderForm(BaseModel):
|
||||
name: str
|
||||
data: Optional[dict] = None
|
||||
meta: Optional[dict] = None
|
||||
parent_id: Optional[str] = None
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ def create_folder(
|
||||
db: Session = Depends(get_session),
|
||||
):
|
||||
folder = Folders.get_folder_by_parent_id_and_user_id_and_name(
|
||||
None, user.id, form_data.name, db=db
|
||||
form_data.parent_id, user.id, form_data.name, db=db
|
||||
)
|
||||
|
||||
if folder:
|
||||
@@ -129,7 +129,7 @@ def create_folder(
|
||||
)
|
||||
|
||||
try:
|
||||
folder = Folders.insert_new_folder(user.id, form_data, db=db)
|
||||
folder = Folders.insert_new_folder(user.id, form_data, form_data.parent_id, db=db)
|
||||
return folder
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@@ -317,7 +317,7 @@ async def delete_folder_by_id(
|
||||
folder = folders.pop()
|
||||
if folder:
|
||||
try:
|
||||
folder_ids = Folders.delete_folder_by_id_and_user_id(id, user.id, db=db)
|
||||
folder_ids = Folders.delete_folder_by_id_and_user_id(folder.id, user.id, db=db)
|
||||
|
||||
for folder_id in folder_ids:
|
||||
if delete_contents:
|
||||
|
||||
@@ -4,6 +4,7 @@ type FolderForm = {
|
||||
name?: string;
|
||||
data?: Record<string, any>;
|
||||
meta?: Record<string, any>;
|
||||
parent_id?: string | null;
|
||||
};
|
||||
|
||||
export const createNewFolder = async (token: string, folderForm: FolderForm) => {
|
||||
|
||||
@@ -142,19 +142,24 @@
|
||||
}
|
||||
};
|
||||
|
||||
const createFolder = async ({ name, data }) => {
|
||||
const createFolder = async ({ name, data, parent_id }) => {
|
||||
name = name?.trim();
|
||||
if (!name) {
|
||||
toast.error($i18n.t('Folder name cannot be empty.'));
|
||||
return;
|
||||
}
|
||||
|
||||
const rootFolders = Object.values(folders).filter((folder) => folder.parent_id === null);
|
||||
if (rootFolders.find((folder) => folder.name.toLowerCase() === name.toLowerCase())) {
|
||||
// Check for duplicate names in the same parent
|
||||
const siblings = Object.values(folders).filter(
|
||||
(folder) => folder.parent_id === parent_id
|
||||
);
|
||||
if (siblings.find((folder) => folder.name.toLowerCase() === name.toLowerCase())) {
|
||||
// If a folder with the same name already exists, append a number to the name
|
||||
let i = 1;
|
||||
while (
|
||||
rootFolders.find((folder) => folder.name.toLowerCase() === `${name} ${i}`.toLowerCase())
|
||||
siblings.find(
|
||||
(folder) => folder.name.toLowerCase() === `${name} ${i}`.toLowerCase()
|
||||
)
|
||||
) {
|
||||
i++;
|
||||
}
|
||||
@@ -166,9 +171,10 @@
|
||||
const tempId = uuidv4();
|
||||
folders = {
|
||||
...folders,
|
||||
tempId: {
|
||||
[tempId]: {
|
||||
id: tempId,
|
||||
name: name,
|
||||
parent_id: parent_id,
|
||||
created_at: Date.now(),
|
||||
updated_at: Date.now()
|
||||
}
|
||||
@@ -176,7 +182,8 @@
|
||||
|
||||
const res = await createNewFolder(localStorage.token, {
|
||||
name,
|
||||
data
|
||||
data,
|
||||
parent_id
|
||||
}).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
|
||||
@@ -11,11 +11,13 @@
|
||||
import Pencil from '$lib/components/icons/Pencil.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Download from '$lib/components/icons/Download.svelte';
|
||||
import Folder from '$lib/components/icons/Folder.svelte';
|
||||
|
||||
export let align: 'start' | 'end' = 'start';
|
||||
export let onEdit = () => {};
|
||||
export let onExport = () => {};
|
||||
export let onDelete = () => {};
|
||||
export let onCreateSub = () => {};
|
||||
|
||||
let show = false;
|
||||
</script>
|
||||
@@ -47,6 +49,18 @@
|
||||
{align}
|
||||
transition={flyAndScale}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
||||
on:click={() => {
|
||||
onCreateSub();
|
||||
}}
|
||||
>
|
||||
<Folder />
|
||||
<div class="flex items-center">{$i18n.t('Create Folder')}</div>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<hr class="border-gray-50/30 dark:border-gray-800/30 my-1" />
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
||||
on:click={() => {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
export let onSubmit: Function = (e) => {};
|
||||
|
||||
export let folderId = null;
|
||||
export let parentId = null;
|
||||
export let edit = false;
|
||||
|
||||
let folder = null;
|
||||
@@ -55,7 +56,8 @@
|
||||
await onSubmit({
|
||||
name,
|
||||
meta,
|
||||
data
|
||||
data,
|
||||
parent_id: edit ? undefined : parentId
|
||||
});
|
||||
show = false;
|
||||
loading = false;
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
updateFolderIsExpandedById,
|
||||
updateFolderById,
|
||||
updateFolderParentIdById,
|
||||
getFolderById
|
||||
getFolderById,
|
||||
createNewFolder
|
||||
} from '$lib/apis/folders';
|
||||
import {
|
||||
getChatById,
|
||||
@@ -64,6 +65,9 @@
|
||||
let showFolderModal = false;
|
||||
let edit = false;
|
||||
|
||||
let showCreateSubFolderModal = false;
|
||||
let createSubFolderParentId = null;
|
||||
|
||||
let draggedOver = false;
|
||||
let dragged = false;
|
||||
|
||||
@@ -414,6 +418,30 @@
|
||||
|
||||
saveAs(blob, `folder-${folders[folderId].name}-export-${Date.now()}.json`);
|
||||
};
|
||||
|
||||
const createSubFolderHandler = async ({ name, meta, data, parent_id }) => {
|
||||
if (name === '') {
|
||||
toast.error($i18n.t('Folder name cannot be empty.'));
|
||||
return;
|
||||
}
|
||||
|
||||
name = name.trim();
|
||||
|
||||
const res = await createNewFolder(localStorage.token, {
|
||||
name,
|
||||
data,
|
||||
meta,
|
||||
parent_id
|
||||
}).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res) {
|
||||
toast.success($i18n.t('Folder created successfully'));
|
||||
dispatch('update');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<DeleteConfirmDialog
|
||||
@@ -444,6 +472,12 @@
|
||||
|
||||
<FolderModal bind:show={showFolderModal} edit={true} {folderId} onSubmit={updateHandler} />
|
||||
|
||||
<FolderModal
|
||||
bind:show={showCreateSubFolderModal}
|
||||
parentId={createSubFolderParentId}
|
||||
onSubmit={createSubFolderHandler}
|
||||
/>
|
||||
|
||||
{#if dragged && x && y}
|
||||
<DragGhost {x} {y}>
|
||||
<div class=" bg-black/80 backdrop-blur-2xl px-2 py-1 rounded-lg w-fit max-w-40">
|
||||
@@ -593,6 +627,10 @@
|
||||
onExport={() => {
|
||||
exportHandler();
|
||||
}}
|
||||
onCreateSub={() => {
|
||||
createSubFolderParentId = folderId;
|
||||
showCreateSubFolderModal = true;
|
||||
}}
|
||||
>
|
||||
<div class="p-1 dark:hover:bg-gray-850 rounded-lg touch-auto">
|
||||
<EllipsisHorizontal className="size-4" strokeWidth="2.5" />
|
||||
|
||||
@@ -897,6 +897,7 @@
|
||||
"Focus Chat Input": "",
|
||||
"Folder": "",
|
||||
"Folder Background Image": "",
|
||||
"Folder created successfully": "",
|
||||
"Folder deleted successfully": "",
|
||||
"Folder Max File Count": "",
|
||||
"Folder name": "",
|
||||
@@ -2135,4 +2136,4 @@
|
||||
"YouTube": "",
|
||||
"Youtube Language": "",
|
||||
"Youtube Proxy URL": ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user