diff --git a/backend/apps/webui/models/chats.py b/backend/apps/webui/models/chats.py
index abde4f2b3..d504b18c3 100644
--- a/backend/apps/webui/models/chats.py
+++ b/backend/apps/webui/models/chats.py
@@ -250,7 +250,7 @@ class ChatTable:
user_id: str,
include_archived: bool = False,
skip: int = 0,
- limit: int = 50,
+ limit: int = -1,
) -> List[ChatTitleIdResponse]:
with get_db() as db:
query = db.query(Chat).filter_by(user_id=user_id)
@@ -260,9 +260,10 @@ class ChatTable:
all_chats = (
query.order_by(Chat.updated_at.desc())
# limit cols
- .with_entities(
- Chat.id, Chat.title, Chat.updated_at, Chat.created_at
- ).all()
+ .with_entities(Chat.id, Chat.title, Chat.updated_at, Chat.created_at)
+ .limit(limit)
+ .offset(skip)
+ .all()
)
# result has to be destrctured from sqlalchemy `row` and mapped to a dict since the `ChatModel`is not the returned dataclass.
return [
diff --git a/backend/apps/webui/routers/chats.py b/backend/apps/webui/routers/chats.py
index ec8e72e0b..6e89722d3 100644
--- a/backend/apps/webui/routers/chats.py
+++ b/backend/apps/webui/routers/chats.py
@@ -43,9 +43,15 @@ router = APIRouter()
@router.get("/", response_model=List[ChatTitleIdResponse])
@router.get("/list", response_model=List[ChatTitleIdResponse])
async def get_session_user_chat_list(
- user=Depends(get_verified_user), skip: int = 0, limit: int = 50
+ user=Depends(get_verified_user), page: Optional[int] = None
):
- return Chats.get_chat_title_id_list_by_user_id(user.id, skip=skip, limit=limit)
+ if page is not None:
+ limit = 60
+ skip = (page - 1) * limit
+
+ return Chats.get_chat_title_id_list_by_user_id(user.id, skip=skip, limit=limit)
+ else:
+ return Chats.get_chat_title_id_list_by_user_id(user.id)
############################
diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts
index b046f1b10..8f4f81aea 100644
--- a/src/lib/apis/chats/index.ts
+++ b/src/lib/apis/chats/index.ts
@@ -32,10 +32,15 @@ export const createNewChat = async (token: string, chat: object) => {
return res;
};
-export const getChatList = async (token: string = '') => {
+export const getChatList = async (token: string = '', page: number | null = null) => {
let error = null;
+ const searchParams = new URLSearchParams();
- const res = await fetch(`${WEBUI_API_BASE_URL}/chats/`, {
+ if (page !== null) {
+ searchParams.append('page', `${page}`);
+ }
+
+ const res = await fetch(`${WEBUI_API_BASE_URL}/chats/?${searchParams.toString()}`, {
method: 'GET',
headers: {
Accept: 'application/json',
diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte
index 253dada19..2c42c2046 100644
--- a/src/lib/components/chat/Chat.svelte
+++ b/src/lib/components/chat/Chat.svelte
@@ -25,7 +25,8 @@
user,
socket,
showCallOverlay,
- tools
+ tools,
+ currentChatPage
} from '$lib/stores';
import {
convertMessagesToHistory,
@@ -421,7 +422,9 @@
params: params,
files: chatFiles
});
- await chats.set(await getChatList(localStorage.token));
+
+ currentChatPage.set(1);
+ await chats.set(await getChatList(localStorage.token, $currentChatPage));
}
}
};
@@ -467,7 +470,9 @@
params: params,
files: chatFiles
});
- await chats.set(await getChatList(localStorage.token));
+
+ currentChatPage.set(1);
+ await chats.set(await getChatList(localStorage.token, $currentChatPage));
}
}
};
@@ -627,7 +632,9 @@
tags: [],
timestamp: Date.now()
});
- await chats.set(await getChatList(localStorage.token));
+
+ currentChatPage.set(1);
+ await chats.set(await getChatList(localStorage.token, $currentChatPage));
await chatId.set(chat.id);
} else {
await chatId.set('local');
@@ -703,7 +710,9 @@
})
);
- await chats.set(await getChatList(localStorage.token));
+ currentChatPage.set(1);
+ await chats.set(await getChatList(localStorage.token, $currentChatPage));
+
return _responses;
};
@@ -949,7 +958,9 @@
params: params,
files: chatFiles
});
- await chats.set(await getChatList(localStorage.token));
+
+ currentChatPage.set(1);
+ await chats.set(await getChatList(localStorage.token, $currentChatPage));
}
}
} else {
@@ -1216,7 +1227,9 @@
params: params,
files: chatFiles
});
- await chats.set(await getChatList(localStorage.token));
+
+ currentChatPage.set(1);
+ await chats.set(await getChatList(localStorage.token, $currentChatPage));
}
}
} else {
@@ -1381,7 +1394,9 @@
if ($settings.saveChatHistory ?? true) {
chat = await updateChatById(localStorage.token, _chatId, { title: _title });
- await chats.set(await getChatList(localStorage.token));
+
+ currentChatPage.set(1);
+ await chats.set(await getChatList(localStorage.token, $currentChatPage));
}
};
diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte
index 319f0f170..9e3c147b1 100644
--- a/src/lib/components/chat/Messages.svelte
+++ b/src/lib/components/chat/Messages.svelte
@@ -1,6 +1,6 @@
+
+
+
+
diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte
index 49a0e2ebb..be2ebb093 100644
--- a/src/lib/components/layout/Sidebar.svelte
+++ b/src/lib/components/layout/Sidebar.svelte
@@ -11,7 +11,9 @@
showSidebar,
mobile,
showArchivedChats,
- pinnedChats
+ pinnedChats,
+ scrollPaginationEnabled,
+ currentChatPage
} from '$lib/stores';
import { onMount, getContext, tick } from 'svelte';
@@ -34,6 +36,8 @@
import UserMenu from './Sidebar/UserMenu.svelte';
import ChatItem from './Sidebar/ChatItem.svelte';
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+ import Spinner from '../common/Spinner.svelte';
+ import Loader from '../common/Loader.svelte';
const BREAKPOINT = 768;
@@ -50,6 +54,10 @@
let filteredChatList = [];
+ // Pagination variables
+ let chatListLoading = false;
+ let allChatsLoaded = false;
+
$: filteredChatList = $chats.filter((chat) => {
if (search === '') {
return true;
@@ -70,6 +78,29 @@
}
});
+ const enablePagination = async () => {
+ // Reset pagination variables
+ currentChatPage.set(1);
+ allChatsLoaded = false;
+ await chats.set(await getChatList(localStorage.token, $currentChatPage));
+
+ // Enable pagination
+ scrollPaginationEnabled.set(true);
+ };
+
+ const loadMoreChats = async () => {
+ chatListLoading = true;
+
+ currentChatPage.set($currentChatPage + 1);
+ const newChatList = await getChatList(localStorage.token, $currentChatPage);
+
+ // once the bottom of the list has been reached (no results) there is no need to continue querying
+ allChatsLoaded = newChatList.length === 0;
+ await chats.set([...$chats, ...newChatList]);
+
+ chatListLoading = false;
+ };
+
onMount(async () => {
mobile.subscribe((e) => {
if ($showSidebar && e) {
@@ -82,9 +113,8 @@
});
showSidebar.set(window.innerWidth > BREAKPOINT);
-
await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned'));
- await chats.set(await getChatList(localStorage.token));
+ await enablePagination();
let touchstart;
let touchend;
@@ -185,7 +215,11 @@
await tick();
goto('/');
}
- await chats.set(await getChatList(localStorage.token));
+
+ allChatsLoaded = false;
+ currentChatPage.set(1);
+ await chats.set(await getChatList(localStorage.token, $currentChatPage));
+
await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned'));
}
};
@@ -410,7 +444,10 @@
class="w-full rounded-r-xl py-1.5 pl-2.5 pr-4 text-sm bg-transparent dark:text-gray-300 outline-none"
placeholder={$i18n.t('Search')}
bind:value={search}
- on:focus={() => {
+ on:focus={async () => {
+ // TODO: migrate backend for more scalable search mechanism
+ scrollPaginationEnabled.set(false);
+ await chats.set(await getChatList(localStorage.token)); // when searching, load all chats
enrichChatsWithContent($chats);
}}
/>
@@ -422,7 +459,7 @@