Timothy J. Baek il y a 9 mois
Parent
commit
a2f9f7c975

+ 10 - 7
src/lib/components/chat/Settings/Chats.svelte

@@ -12,7 +12,12 @@
 		getAllUserChats,
 		getChatList
 	} from '$lib/apis/chats';
-	import { getImportOrigin, convertOpenAIChats, disablePagination } from '$lib/utils';
+	import {
+		getImportOrigin,
+		convertOpenAIChats,
+		disablePagination,
+		enablePagination
+	} from '$lib/utils';
 	import { onMount, getContext } from 'svelte';
 	import { goto } from '$app/navigation';
 	import { toast } from 'svelte-sonner';
@@ -61,8 +66,7 @@
 				await createNewChat(localStorage.token, chat);
 			}
 		}
-		disablePagination();
-		await chats.set(await getChatList(localStorage.token));
+		enablePagination();
 	};
 
 	const exportChats = async () => {
@@ -77,8 +81,7 @@
 		await archiveAllChats(localStorage.token).catch((error) => {
 			toast.error(error);
 		});
-		disablePagination();
-		await chats.set(await getChatList(localStorage.token));
+		enablePagination();
 	};
 
 	const deleteAllChatsHandler = async () => {
@@ -86,8 +89,8 @@
 		await deleteAllChats(localStorage.token).catch((error) => {
 			toast.error(error);
 		});
-		disablePagination();
-		await chats.set(await getChatList(localStorage.token));
+
+		enablePagination();
 	};
 
 	const toggleSaveChatHistory = async () => {

+ 3 - 2
src/lib/components/chat/Tags.svelte

@@ -8,12 +8,13 @@
 		getTagsById,
 		updateChatById
 	} from '$lib/apis/chats';
-	import { tags as _tags, chats, pinnedChats, pageSkip, pageLimit, tagView } from '$lib/stores';
+	import { tags as _tags, chats, pinnedChats, pageSkip, pageLimit } from '$lib/stores';
 	import { createEventDispatcher, onMount } from 'svelte';
 
 	const dispatch = createEventDispatcher();
 
 	import Tags from '../common/Tags.svelte';
+	import { enablePagination } from '$lib/utils';
 
 	export let chatId = '';
 	let tags = [];
@@ -59,7 +60,7 @@
 			}
 		} else {
 			// if the tag we deleted is no longer a valid tag, return to main chat list view
-			tagView.set(false);
+			enablePagination();
 			await chats.set(
 				await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit)
 			);

+ 30 - 0
src/lib/components/common/Loader.svelte

@@ -0,0 +1,30 @@
+<script lang="ts">
+	import { createEventDispatcher, onMount } from 'svelte';
+	const dispatch = createEventDispatcher();
+
+	let loaderElement: HTMLElement;
+
+	onMount(() => {
+		const observer = new IntersectionObserver(
+			(entries, observer) => {
+				entries.forEach((entry) => {
+					if (entry.isIntersecting) {
+						dispatch('visible');
+						// observer.unobserve(loaderElement); // Stop observing until content is loaded
+					}
+				});
+			},
+			{
+				root: null, // viewport
+				rootMargin: '0px',
+				threshold: 1.0 // When 100% of the loader is visible
+			}
+		);
+
+		observer.observe(loaderElement);
+	});
+</script>
+
+<div bind:this={loaderElement}>
+	<slot />
+</div>

+ 36 - 61
src/lib/components/layout/Sidebar.svelte

@@ -14,13 +14,12 @@
 		pinnedChats,
 		pageSkip,
 		pageLimit,
-		scrollPaginationEnabled,
-		tagView
+		scrollPaginationEnabled
 	} from '$lib/stores';
 	import { onMount, getContext, tick } from 'svelte';
 
 	const i18n = getContext('i18n');
-	import { disablePagination } from '$lib/utils';
+	import { disablePagination, enablePagination } from '$lib/utils';
 
 	import { updateUserSettings } from '$lib/apis/users';
 	import {
@@ -40,6 +39,7 @@
 	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;
 
@@ -55,9 +55,10 @@
 	let showDropdown = false;
 
 	let filteredChatList = [];
-	let paginationScrollThreashold = 0.6;
-	let nextPageLoading = false;
-	let chatPagniationComplete = false;
+
+	// Pagination variables
+	let chatListLoading = false;
+	let allChatsLoaded = false;
 
 	pageLimit.set(20);
 
@@ -81,6 +82,18 @@
 		}
 	});
 
+	const loadMoreChats = async () => {
+		chatListLoading = true;
+		pageSkip.set($pageSkip + 1);
+		const newChatList = await getChatList(localStorage.token, $pageSkip * $pageLimit, $pageLimit);
+
+		// 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) {
@@ -151,48 +164,6 @@
 		window.addEventListener('focus', onFocus);
 		window.addEventListener('blur', onBlur);
 
-		// Infinite scroll
-		const loader = document.getElementById('loader');
-
-		const observer = new IntersectionObserver(
-			(entries, observer) => {
-				entries.forEach((entry) => {
-					if (entry.isIntersecting) {
-						loadMoreContent();
-						observer.unobserve(loader); // Stop observing until content is loaded
-					}
-				});
-			},
-			{
-				root: null, // viewport
-				rootMargin: '0px',
-				threshold: 1.0 // When 100% of the loader is visible
-			}
-		);
-
-		observer.observe(loader);
-		const loadMoreContent = async () => {
-			if (!$scrollPaginationEnabled) return;
-			if ($tagView) return;
-			if (nextPageLoading) return;
-			if (chatPagniationComplete) return;
-
-			nextPageLoading = true;
-			pageSkip.set($pageSkip + 1);
-			// extend existing chats
-			const nextPageChats = await getChatList(
-				localStorage.token,
-				$pageSkip * $pageLimit,
-				$pageLimit
-			);
-			// once the bottom of the list has been reached (no results) there is no need to continue querying
-			chatPagniationComplete = nextPageChats.length === 0;
-			await chats.set([...$chats, ...nextPageChats]);
-			nextPageLoading = false;
-
-			observer.observe(loader); // Start observing again after content is loaded
-		};
-
 		return () => {
 			window.removeEventListener('keydown', onKeyDown);
 			window.removeEventListener('keyup', onKeyUp);
@@ -466,8 +437,8 @@
 						placeholder={$i18n.t('Search')}
 						bind:value={search}
 						on:focus={async () => {
-							disablePagination();
 							// TODO: migrate backend for more scalable search mechanism
+							disablePagination();
 							await chats.set(await getChatList(localStorage.token)); // when searching, load all chats
 							enrichChatsWithContent($chats);
 						}}
@@ -480,8 +451,8 @@
 					<button
 						class="px-2.5 text-xs font-medium bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
 						on:click={async () => {
-							disablePagination();
-
+							enablePagination();
+							allChatsLoaded = false;
 							await chats.set(
 								await getChatList(localStorage.token, $pageSkip * $pageLimit, $pageLimit)
 							);
@@ -493,18 +464,17 @@
 						<button
 							class="px-2.5 text-xs font-medium bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
 							on:click={async () => {
+								disablePagination();
 								let chatIds = await getChatListByTagName(localStorage.token, tag.name);
 								if (chatIds.length === 0) {
-									// no chats found in the tag
 									await tags.set(await getAllChatTags(localStorage.token));
-									disablePagination();
+
 									chatIds = await getChatList(
 										localStorage.token,
 										$pageSkip * $pageLimit,
 										$pageLimit
 									);
 								}
-								tagView.set(true);
 								await chats.set(chatIds);
 							}}
 						>
@@ -597,14 +567,19 @@
 					/>
 				{/each}
 
-				{#if !chatPagniationComplete}
-					<div
-						id="loader"
-						class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2"
+				{#if $scrollPaginationEnabled && !allChatsLoaded}
+					<Loader
+						on:visible={(e) => {
+							if (!chatListLoading) {
+								loadMoreChats();
+							}
+						}}
 					>
-						<Spinner className=" size-4" />
-						<div class=" ">Loading...</div>
-					</div>
+						<div class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2">
+							<Spinner className=" size-4" />
+							<div class=" ">Loading...</div>
+						</div>
+					</Loader>
 				{/if}
 			</div>
 		</div>

+ 0 - 1
src/lib/stores/index.ts

@@ -45,7 +45,6 @@ export const showCallOverlay = writable(false);
 export const scrollPaginationEnabled = writable(true);
 export const pageSkip = writable(0);
 export const pageLimit = writable(-1);
-export const tagView = writable(false);
 
 export type Model = OpenAIModel | OllamaModel;
 

+ 8 - 1
src/lib/utils/index.ts

@@ -1,7 +1,7 @@
 import { v4 as uuidv4 } from 'uuid';
 import sha256 from 'js-sha256';
 import { WEBUI_BASE_URL } from '$lib/constants';
-import { scrollPaginationEnabled, pageLimit, pageSkip } from '$lib/stores';
+import { scrollPaginationEnabled, pageLimit, pageSkip, chats } from '$lib/stores';
 
 //////////////////////////
 // Helper functions
@@ -781,6 +781,13 @@ export const bestMatchingLanguage = (supportedLanguages, preferredLanguages, def
 	return match || defaultLocale;
 };
 
+export const enablePagination = () => {
+	chats.set([]);
+	scrollPaginationEnabled.set(true);
+	pageLimit.set(20);
+	pageSkip.set(0);
+};
+
 export const disablePagination = () => {
 	scrollPaginationEnabled.set(false);
 	pageLimit.set(-1);