refac/enh: folder optimization
This commit is contained in:
parent
54beeeaf72
commit
c80bb31968
|
@ -810,7 +810,7 @@ class ChatTable:
|
|||
return [ChatModel.model_validate(chat) for chat in all_chats]
|
||||
|
||||
def get_chats_by_folder_id_and_user_id(
|
||||
self, folder_id: str, user_id: str
|
||||
self, folder_id: str, user_id: str, skip: int = 0, limit: int = 60
|
||||
) -> list[ChatModel]:
|
||||
with get_db() as db:
|
||||
query = db.query(Chat).filter_by(folder_id=folder_id, user_id=user_id)
|
||||
|
@ -819,6 +819,11 @@ class ChatTable:
|
|||
|
||||
query = query.order_by(Chat.updated_at.desc())
|
||||
|
||||
if skip:
|
||||
query = query.offset(skip)
|
||||
if limit:
|
||||
query = query.limit(limit)
|
||||
|
||||
all_chats = query.all()
|
||||
return [ChatModel.model_validate(chat) for chat in all_chats]
|
||||
|
||||
|
|
|
@ -50,6 +50,20 @@ class FolderModel(BaseModel):
|
|||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class FolderMetadataResponse(BaseModel):
|
||||
icon: Optional[str] = None
|
||||
|
||||
|
||||
class FolderNameIdResponse(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
meta: Optional[FolderMetadataResponse] = None
|
||||
parent_id: Optional[str] = None
|
||||
is_expanded: bool = False
|
||||
created_at: int
|
||||
updated_at: int
|
||||
|
||||
|
||||
####################
|
||||
# Forms
|
||||
####################
|
||||
|
|
|
@ -218,6 +218,28 @@ async def get_chats_by_folder_id(folder_id: str, user=Depends(get_verified_user)
|
|||
]
|
||||
|
||||
|
||||
@router.get("/folder/{folder_id}/list")
|
||||
async def get_chat_list_by_folder_id(
|
||||
folder_id: str, page: Optional[int] = 1, user=Depends(get_verified_user)
|
||||
):
|
||||
try:
|
||||
limit = 60
|
||||
skip = (page - 1) * limit
|
||||
|
||||
return [
|
||||
{"title": chat.title, "id": chat.id, "updated_at": chat.updated_at}
|
||||
for chat in Chats.get_chats_by_folder_id_and_user_id(
|
||||
folder_id, user.id, skip=skip, limit=limit
|
||||
)
|
||||
]
|
||||
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
|
||||
|
||||
############################
|
||||
# GetPinnedChats
|
||||
############################
|
||||
|
|
|
@ -12,6 +12,7 @@ from open_webui.models.folders import (
|
|||
FolderForm,
|
||||
FolderUpdateForm,
|
||||
FolderModel,
|
||||
FolderNameIdResponse,
|
||||
Folders,
|
||||
)
|
||||
from open_webui.models.chats import Chats
|
||||
|
@ -44,7 +45,7 @@ router = APIRouter()
|
|||
############################
|
||||
|
||||
|
||||
@router.get("/", response_model=list[FolderModel])
|
||||
@router.get("/", response_model=list[FolderNameIdResponse])
|
||||
async def get_folders(user=Depends(get_verified_user)):
|
||||
folders = Folders.get_folders_by_user_id(user.id)
|
||||
|
||||
|
@ -76,14 +77,6 @@ async def get_folders(user=Depends(get_verified_user)):
|
|||
return [
|
||||
{
|
||||
**folder.model_dump(),
|
||||
"items": {
|
||||
"chats": [
|
||||
{"title": chat.title, "id": chat.id, "updated_at": chat.updated_at}
|
||||
for chat in Chats.get_chats_by_folder_id_and_user_id(
|
||||
folder.id, user.id
|
||||
)
|
||||
]
|
||||
},
|
||||
}
|
||||
for folder in folders
|
||||
]
|
||||
|
|
|
@ -327,6 +327,45 @@ export const getChatsByFolderId = async (token: string, folderId: string) => {
|
|||
return res;
|
||||
};
|
||||
|
||||
export const getChatListByFolderId = async (token: string, folderId: string, page: number = 1) => {
|
||||
let error = null;
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
if (page !== null) {
|
||||
searchParams.append('page', `${page}`);
|
||||
}
|
||||
|
||||
const res = await fetch(
|
||||
`${WEBUI_API_BASE_URL}/chats/folder/${folderId}/list?${searchParams.toString()}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
...(token && { authorization: `Bearer ${token}` })
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.then((json) => {
|
||||
return json;
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getAllArchivedChats = async (token: string) => {
|
||||
let error = null;
|
||||
|
||||
|
|
|
@ -51,6 +51,8 @@
|
|||
export let selected = false;
|
||||
export let shiftKey = false;
|
||||
|
||||
export let onDragEnd = () => {};
|
||||
|
||||
let chat = null;
|
||||
|
||||
let mouseOver = false;
|
||||
|
@ -201,11 +203,13 @@
|
|||
y = event.clientY;
|
||||
};
|
||||
|
||||
const onDragEnd = (event) => {
|
||||
const onDragEndHandler = (event) => {
|
||||
event.stopPropagation();
|
||||
|
||||
itemElement.style.opacity = '1'; // Reset visual cue after drag
|
||||
dragged = false;
|
||||
|
||||
onDragEnd(event);
|
||||
};
|
||||
|
||||
const onClickOutside = (event) => {
|
||||
|
@ -225,7 +229,7 @@
|
|||
// Event listener for when dragging occurs (optional)
|
||||
itemElement.addEventListener('drag', onDrag);
|
||||
// Event listener for when dragging ends
|
||||
itemElement.addEventListener('dragend', onDragEnd);
|
||||
itemElement.addEventListener('dragend', onDragEndHandler);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -235,7 +239,7 @@
|
|||
|
||||
itemElement.removeEventListener('dragstart', onDragStart);
|
||||
itemElement.removeEventListener('drag', onDrag);
|
||||
itemElement.removeEventListener('dragend', onDragEnd);
|
||||
itemElement.removeEventListener('dragend', onDragEndHandler);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -18,15 +18,27 @@
|
|||
sensitivity: 'base'
|
||||
})
|
||||
);
|
||||
|
||||
let folderRegistry = {};
|
||||
|
||||
const onItemMove = (e) => {
|
||||
console.log(`onItemMove`, e, folderRegistry);
|
||||
|
||||
if (e.originFolderId) {
|
||||
folderRegistry[e.originFolderId]?.setFolderItems();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#each folderList as folderId (folderId)}
|
||||
<RecursiveFolder
|
||||
className=""
|
||||
bind:folderRegistry
|
||||
{folders}
|
||||
{folderId}
|
||||
{shiftKey}
|
||||
{onDelete}
|
||||
{onItemMove}
|
||||
on:import={(e) => {
|
||||
dispatch('import', e.detail);
|
||||
}}
|
||||
|
|
|
@ -59,8 +59,6 @@
|
|||
system_prompt: '',
|
||||
files: []
|
||||
};
|
||||
|
||||
console.log(folder);
|
||||
};
|
||||
|
||||
const focusInput = async () => {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import fileSaver from 'file-saver';
|
||||
const { saveAs } = fileSaver;
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { chatId, mobile, selectedFolder, showSidebar } from '$lib/stores';
|
||||
|
@ -21,6 +22,7 @@
|
|||
import {
|
||||
getChatById,
|
||||
getChatsByFolderId,
|
||||
getChatListByFolderId,
|
||||
importChat,
|
||||
updateChatFolderIdById
|
||||
} from '$lib/apis/chats';
|
||||
|
@ -37,9 +39,10 @@
|
|||
import FolderMenu from './Folders/FolderMenu.svelte';
|
||||
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
import FolderModal from './Folders/FolderModal.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import Emoji from '$lib/components/common/Emoji.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
|
||||
export let folderRegistry = {};
|
||||
export let open = false;
|
||||
|
||||
export let folders;
|
||||
|
@ -51,6 +54,7 @@
|
|||
export let parentDragged = false;
|
||||
|
||||
export let onDelete = (e) => {};
|
||||
export let onItemMove = (e) => {};
|
||||
|
||||
let folderElement;
|
||||
|
||||
|
@ -171,6 +175,12 @@
|
|||
return null;
|
||||
});
|
||||
|
||||
onItemMove({
|
||||
originFolderId: chat.folder_id,
|
||||
targetFolderId: folderId,
|
||||
e
|
||||
});
|
||||
|
||||
if (res) {
|
||||
dispatch('update');
|
||||
}
|
||||
|
@ -182,6 +192,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
setFolderItems();
|
||||
draggedOver = false;
|
||||
}
|
||||
};
|
||||
|
@ -234,6 +245,10 @@
|
|||
};
|
||||
|
||||
onMount(async () => {
|
||||
folderRegistry[folderId] = {
|
||||
setFolderItems: () => setFolderItems()
|
||||
};
|
||||
|
||||
open = folders[folderId].is_expanded;
|
||||
if (folderElement) {
|
||||
folderElement.addEventListener('dragover', onDragOver);
|
||||
|
@ -250,7 +265,6 @@
|
|||
|
||||
if (folders[folderId]?.new) {
|
||||
delete folders[folderId].new;
|
||||
|
||||
await tick();
|
||||
renameHandler();
|
||||
}
|
||||
|
@ -339,6 +353,21 @@
|
|||
}, 500);
|
||||
};
|
||||
|
||||
let chats = null;
|
||||
export const setFolderItems = async () => {
|
||||
await tick();
|
||||
if (open) {
|
||||
chats = await getChatListByFolderId(localStorage.token, folderId).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return [];
|
||||
});
|
||||
} else {
|
||||
chats = null;
|
||||
}
|
||||
};
|
||||
|
||||
$: setFolderItems(open);
|
||||
|
||||
const renameHandler = async () => {
|
||||
console.log('Edit');
|
||||
await tick();
|
||||
|
@ -419,8 +448,6 @@
|
|||
bind:open
|
||||
className="w-full"
|
||||
buttonClassName="w-full"
|
||||
hide={(folders[folderId]?.childrenIds ?? []).length === 0 &&
|
||||
(folders[folderId].items?.chats ?? []).length === 0}
|
||||
onChange={(state) => {
|
||||
dispatch('open', state);
|
||||
}}
|
||||
|
@ -466,6 +493,7 @@
|
|||
class="text-gray-500 dark:text-gray-500 transition-all p-1 hover:bg-gray-200 dark:hover:bg-gray-850 rounded-lg"
|
||||
on:click={(e) => {
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
open = !open;
|
||||
isExpandedUpdateDebounceHandler();
|
||||
}}
|
||||
|
@ -548,7 +576,7 @@
|
|||
</div>
|
||||
|
||||
<div slot="content" class="w-full">
|
||||
{#if (folders[folderId]?.childrenIds ?? []).length > 0 || (folders[folderId].items?.chats ?? []).length > 0}
|
||||
{#if (folders[folderId]?.childrenIds ?? []).length > 0 || (chats ?? []).length > 0}
|
||||
<div
|
||||
class="ml-3 pl-1 mt-[1px] flex flex-col overflow-y-auto scrollbar-hidden border-s border-gray-100 dark:border-gray-900"
|
||||
>
|
||||
|
@ -564,10 +592,12 @@
|
|||
|
||||
{#each children as childFolder (`${folderId}-${childFolder.id}`)}
|
||||
<svelte:self
|
||||
bind:folderRegistry
|
||||
{folders}
|
||||
folderId={childFolder.id}
|
||||
{shiftKey}
|
||||
parentDragged={dragged}
|
||||
{onItemMove}
|
||||
{onDelete}
|
||||
on:import={(e) => {
|
||||
dispatch('import', e.detail);
|
||||
|
@ -582,18 +612,22 @@
|
|||
{/each}
|
||||
{/if}
|
||||
|
||||
{#if folders[folderId].items?.chats}
|
||||
{#each folders[folderId].items.chats as chat (chat.id)}
|
||||
<ChatItem
|
||||
id={chat.id}
|
||||
title={chat.title}
|
||||
{shiftKey}
|
||||
on:change={(e) => {
|
||||
dispatch('change', e.detail);
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#each chats ?? [] as chat (chat.id)}
|
||||
<ChatItem
|
||||
id={chat.id}
|
||||
title={chat.title}
|
||||
{shiftKey}
|
||||
on:change={(e) => {
|
||||
dispatch('change', e.detail);
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if chats === null}
|
||||
<div class="flex justify-center items-center p-2">
|
||||
<Spinner className="size-4 text-gray-500" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue