From bed1cdac2fb4d07ffe5fdff5fa842602362bca55 Mon Sep 17 00:00:00 2001 From: KartikGS Date: Thu, 18 Apr 2024 18:06:46 +0530 Subject: [PATCH 1/3] navigation: load more discussion --- src/lib/components/InfiniteScroll.svelte | 39 ++++++++++++++ src/lib/components/NavMenu.svelte | 5 ++ src/lib/stores/convUpdate.ts | 3 ++ src/routes/+layout.svelte | 52 ++++++++++++++++++ src/routes/conversation/[id]/+page.svelte | 2 + src/routes/conversations/+server.ts | 65 +++++++++++++++++++++++ 6 files changed, 166 insertions(+) create mode 100644 src/lib/components/InfiniteScroll.svelte create mode 100644 src/lib/stores/convUpdate.ts create mode 100644 src/routes/conversations/+server.ts diff --git a/src/lib/components/InfiniteScroll.svelte b/src/lib/components/InfiniteScroll.svelte new file mode 100644 index 00000000000..75756cadde4 --- /dev/null +++ b/src/lib/components/InfiniteScroll.svelte @@ -0,0 +1,39 @@ + +
\ No newline at end of file diff --git a/src/lib/components/NavMenu.svelte b/src/lib/components/NavMenu.svelte index 52b69f72495..0d7a247d1b3 100644 --- a/src/lib/components/NavMenu.svelte +++ b/src/lib/components/NavMenu.svelte @@ -6,6 +6,7 @@ import { isAborted } from "$lib/stores/isAborted"; import { PUBLIC_APP_NAME, PUBLIC_ORIGIN } from "$env/static/public"; import NavConversationItem from "./NavConversationItem.svelte"; + import InfiniteScroll from "./InfiniteScroll.svelte"; import type { LayoutData } from "../../routes/$types"; import type { ConvSidebar } from "$lib/types/ConvSidebar"; import type { Model } from "$lib/types/Model"; @@ -14,6 +15,7 @@ export let conversations: ConvSidebar[] = []; export let canLogin: boolean; export let user: LayoutData["user"]; + export let hasMore: boolean; function handleNewChatClick() { isAborted.set(true); @@ -72,6 +74,9 @@ {/each} {/if} {/each} +
(null); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index c7c56eb2892..bfc3749144f 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -19,10 +19,12 @@ import { shareConversation } from "$lib/shareConversation"; import { UrlDependency } from "$lib/types/UrlDependency"; + import type { ConvSidebar } from "$lib/types/ConvSidebar"; import Toast from "$lib/components/Toast.svelte"; import NavMenu from "$lib/components/NavMenu.svelte"; import MobileNav from "$lib/components/MobileNav.svelte"; + import convUpdate from "$lib/stores/convUpdate"; import titleUpdate from "$lib/stores/titleUpdate"; import DisclaimerModal from "$lib/components/DisclaimerModal.svelte"; import ExpandNavigation from "$lib/components/ExpandNavigation.svelte"; @@ -31,6 +33,10 @@ let isNavOpen = false; let isNavCollapsed = false; + let hasMore = true + let extraConversation:[]=[] + let total =300 + let extra =50 let errorToastTimeout: ReturnType; let currentError: string | null; @@ -65,6 +71,7 @@ return; } + fetchData() if ($page.params.id !== id) { await invalidate(UrlDependency.ConversationList); } else { @@ -91,6 +98,7 @@ return; } + fetchData() await invalidate(UrlDependency.ConversationList); } catch (err) { console.error(err); @@ -102,6 +110,45 @@ clearTimeout(errorToastTimeout); }); + //for getting extra conversation and maintaning consistency + async function fetchData(call : string = '') { + if(call === '' && extraConversation.length === 0) return; + try { + const url = call === 'extra' ? `${base}/conversations/?limit=${extra}&skip=${total+extraConversation.length}` : `${base}/conversations/?limit=${extraConversation.length}&skip=${total}` ; + const res = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!res.ok) { + $error = "Error while fetching onversations, try again."; + return; + } + + const conversations:[]=(await res.json()).conversations + conversations.forEach((conv:ConvSidebar)=>conv.updatedAt=new Date(conv.updatedAt)) + + if(call=='extra') extraConversation=[...extraConversation,...conversations]; + else extraConversation=[...conversations] + + if(conversations.length==0) hasMore=false + else hasMore=true + + } catch (err) { + console.error(err); + $error = String(err); + } + }; + + $: data.conversations=[...data.conversations.slice(0,total),...extraConversation] + + $: if(extraConversation.findIndex(({ id }) => id === $convUpdate)!=-1){ + fetchData() + $convUpdate = null; + } + $: if ($error) onError(); $: if ($titleUpdate) { @@ -110,6 +157,7 @@ if (convIdx != -1) { data.conversations[convIdx].title = $titleUpdate?.title ?? data.conversations[convIdx].title; } + fetchData() // update data.conversations data.conversations = [...data.conversations]; @@ -200,9 +248,11 @@ conversations={data.conversations} user={data.user} canLogin={data.user === undefined && data.loginEnabled} + hasMore={hasMore} on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)} on:deleteConversation={(ev) => deleteConversation(ev.detail)} on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)} + on:loadMore={()=>{fetchData('extra')}} /> {#if currentError} diff --git a/src/routes/conversation/[id]/+page.svelte b/src/routes/conversation/[id]/+page.svelte index 04888829a06..94885822329 100644 --- a/src/routes/conversation/[id]/+page.svelte +++ b/src/routes/conversation/[id]/+page.svelte @@ -12,6 +12,7 @@ import { webSearchParameters } from "$lib/stores/webSearchParameters"; import type { Message } from "$lib/types/Message"; import type { MessageUpdate } from "$lib/types/MessageUpdate"; + import convUpdate from "$lib/stores/convUpdate"; import titleUpdate from "$lib/stores/titleUpdate"; import file2base64 from "$lib/utils/file2base64"; import { addChildren } from "$lib/utils/tree/addChildren"; @@ -264,6 +265,7 @@ } finally { loading = false; pending = false; + $convUpdate = $page.params.id; await invalidateAll(); } } diff --git a/src/routes/conversations/+server.ts b/src/routes/conversations/+server.ts new file mode 100644 index 00000000000..483acd9791c --- /dev/null +++ b/src/routes/conversations/+server.ts @@ -0,0 +1,65 @@ +import type { RequestHandler } from "./$types"; +import { collections } from "$lib/server/database"; +import type { Conversation } from "$lib/types/Conversation"; +import { ObjectId } from "mongodb"; +import { defaultModel } from "$lib/server/models"; +import { authCondition } from "$lib/server/auth"; +import type { ConvSidebar } from "$lib/types/ConvSidebar"; + +export const GET: RequestHandler = async ({ url, locals }) => { + + const settings = await collections.settings.findOne(authCondition(locals)); + const limit = parseInt(url.searchParams.get('limit') ?? '3') + const skip = parseInt(url.searchParams.get('skip') ?? '15') + + const conversations = await collections.conversations + .find(authCondition(locals)) + .skip(skip) + .sort({ updatedAt: -1 }) + .project< + Pick + >({ + title: 1, + model: 1, + _id: 1, + updatedAt: 1, + createdAt: 1, + assistantId: 1, + }) + .limit(limit) + .toArray(); + + const userAssistants = settings?.assistants?.map((assistantId) => assistantId.toString()) ?? []; + + const assistantIds = [ + ...userAssistants.map((el) => new ObjectId(el)), + ...(conversations.map((conv) => conv.assistantId).filter((el) => !!el) as ObjectId[]), + ]; + + const assistants = await collections.assistants.find({ _id: { $in: assistantIds } }).toArray(); + + return new Response( + JSON.stringify({ + conversations: conversations.map((conv) => { + if (settings?.hideEmojiOnSidebar) { + conv.title = conv.title.replace(/\p{Emoji}/gu, ""); + } + + // remove invalid unicode and trim whitespaces + conv.title = conv.title.replace(/\uFFFD/gu, "").trimStart(); + + return { + id: conv._id.toString(), + title: conv.title, + model: conv.model ?? defaultModel, + updatedAt: conv.updatedAt, + assistantId: conv.assistantId?.toString(), + avatarHash: + conv.assistantId && + assistants.find((a) => a._id.toString() === conv.assistantId?.toString())?.avatar, + }; + }) satisfies ConvSidebar[], + }), + { headers: { "Content-Type": "application/json" } } + ); +} \ No newline at end of file From c84c573c3bbdc8c016b03fd7919d67f3deef23e9 Mon Sep 17 00:00:00 2001 From: KartikGS Date: Thu, 18 Apr 2024 18:16:47 +0530 Subject: [PATCH 2/3] formatting --- src/lib/components/InfiniteScroll.svelte | 71 ++++++++++++------------ src/lib/components/NavMenu.svelte | 4 +- src/routes/+layout.svelte | 58 ++++++++++--------- src/routes/conversations/+server.ts | 47 ++++++++-------- 4 files changed, 92 insertions(+), 88 deletions(-) diff --git a/src/lib/components/InfiniteScroll.svelte b/src/lib/components/InfiniteScroll.svelte index 75756cadde4..474e55347bc 100644 --- a/src/lib/components/InfiniteScroll.svelte +++ b/src/lib/components/InfiniteScroll.svelte @@ -1,39 +1,40 @@ -
\ No newline at end of file + +
diff --git a/src/lib/components/NavMenu.svelte b/src/lib/components/NavMenu.svelte index 0d7a247d1b3..550064d81bf 100644 --- a/src/lib/components/NavMenu.svelte +++ b/src/lib/components/NavMenu.svelte @@ -74,9 +74,7 @@ {/each} {/if} {/each} - +
; let currentError: string | null; @@ -72,7 +72,7 @@ return; } - fetchData() + fetchData(); if ($page.params.id !== id) { await invalidate(UrlDependency.ConversationList); } else { @@ -99,7 +99,7 @@ return; } - fetchData() + fetchData(); await invalidate(UrlDependency.ConversationList); } catch (err) { console.error(err); @@ -112,10 +112,13 @@ }); //for getting extra conversation and maintaning consistency - async function fetchData(call : string = '') { - if(call === '' && extraConversation.length === 0) return; + async function fetchData(call: string = "") { + if (call === "" && extraConversation.length === 0) return; try { - const url = call === 'extra' ? `${base}/conversations/?limit=${extra}&skip=${total+extraConversation.length}` : `${base}/conversations/?limit=${extraConversation.length}&skip=${total}` ; + const url = + call === "extra" + ? `${base}/conversations/?limit=${extra}&skip=${total + extraConversation.length}` + : `${base}/conversations/?limit=${extraConversation.length}&skip=${total}`; const res = await fetch(url, { method: "GET", headers: { @@ -128,25 +131,24 @@ return; } - const conversations:[]=(await res.json()).conversations - conversations.forEach((conv:ConvSidebar)=>conv.updatedAt=new Date(conv.updatedAt)) - - if(call=='extra') extraConversation=[...extraConversation,...conversations]; - else extraConversation=[...conversations] + const conversations: [] = (await res.json()).conversations; + conversations.forEach((conv: ConvSidebar) => (conv.updatedAt = new Date(conv.updatedAt))); - if(conversations.length==0) hasMore=false - else hasMore=true + if (call == "extra") extraConversation = [...extraConversation, ...conversations]; + else extraConversation = [...conversations]; + if (conversations.length == 0) hasMore = false; + else hasMore = true; } catch (err) { console.error(err); $error = String(err); - } - }; + } + } - $: data.conversations=[...data.conversations.slice(0,total),...extraConversation] + $: data.conversations = [...data.conversations.slice(0, total), ...extraConversation]; - $: if(extraConversation.findIndex(({ id }) => id === $convUpdate)!=-1){ - fetchData() + $: if (extraConversation.findIndex(({ id }) => id === $convUpdate) != -1) { + fetchData(); $convUpdate = null; } @@ -158,7 +160,7 @@ if (convIdx != -1) { data.conversations[convIdx].title = $titleUpdate?.title ?? data.conversations[convIdx].title; } - fetchData() + fetchData(); // update data.conversations data.conversations = [...data.conversations]; @@ -253,11 +255,13 @@ conversations={data.conversations} user={data.user} canLogin={data.user === undefined && data.loginEnabled} - hasMore={hasMore} + {hasMore} on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)} on:deleteConversation={(ev) => deleteConversation(ev.detail)} on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)} - on:loadMore={()=>{fetchData('extra')}} + on:loadMore={() => { + fetchData("extra"); + }} /> {#if currentError} diff --git a/src/routes/conversations/+server.ts b/src/routes/conversations/+server.ts index 483acd9791c..a95b5e58b24 100644 --- a/src/routes/conversations/+server.ts +++ b/src/routes/conversations/+server.ts @@ -7,14 +7,13 @@ import { authCondition } from "$lib/server/auth"; import type { ConvSidebar } from "$lib/types/ConvSidebar"; export const GET: RequestHandler = async ({ url, locals }) => { - const settings = await collections.settings.findOne(authCondition(locals)); - const limit = parseInt(url.searchParams.get('limit') ?? '3') - const skip = parseInt(url.searchParams.get('skip') ?? '15') + const limit = parseInt(url.searchParams.get("limit") ?? "3"); + const skip = parseInt(url.searchParams.get("skip") ?? "15"); const conversations = await collections.conversations .find(authCondition(locals)) - .skip(skip) + .skip(skip) .sort({ updatedAt: -1 }) .project< Pick @@ -41,25 +40,25 @@ export const GET: RequestHandler = async ({ url, locals }) => { return new Response( JSON.stringify({ conversations: conversations.map((conv) => { - if (settings?.hideEmojiOnSidebar) { - conv.title = conv.title.replace(/\p{Emoji}/gu, ""); - } - - // remove invalid unicode and trim whitespaces - conv.title = conv.title.replace(/\uFFFD/gu, "").trimStart(); - - return { - id: conv._id.toString(), - title: conv.title, - model: conv.model ?? defaultModel, - updatedAt: conv.updatedAt, - assistantId: conv.assistantId?.toString(), - avatarHash: - conv.assistantId && - assistants.find((a) => a._id.toString() === conv.assistantId?.toString())?.avatar, - }; - }) satisfies ConvSidebar[], - }), + if (settings?.hideEmojiOnSidebar) { + conv.title = conv.title.replace(/\p{Emoji}/gu, ""); + } + + // remove invalid unicode and trim whitespaces + conv.title = conv.title.replace(/\uFFFD/gu, "").trimStart(); + + return { + id: conv._id.toString(), + title: conv.title, + model: conv.model ?? defaultModel, + updatedAt: conv.updatedAt, + assistantId: conv.assistantId?.toString(), + avatarHash: + conv.assistantId && + assistants.find((a) => a._id.toString() === conv.assistantId?.toString())?.avatar, + }; + }) satisfies ConvSidebar[], + }), { headers: { "Content-Type": "application/json" } } ); -} \ No newline at end of file +}; From 8149516c1b4f738c176559ee49bd237c3c6b6e68 Mon Sep 17 00:00:00 2001 From: KartikGS Date: Fri, 19 Apr 2024 14:57:16 +0530 Subject: [PATCH 3/3] lint and minor fix --- src/lib/components/InfiniteScroll.svelte | 4 ++-- src/routes/conversations/+server.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/components/InfiniteScroll.svelte b/src/lib/components/InfiniteScroll.svelte index 474e55347bc..ee5577cb8e4 100644 --- a/src/lib/components/InfiniteScroll.svelte +++ b/src/lib/components/InfiniteScroll.svelte @@ -27,11 +27,11 @@ }; element.addEventListener("scroll", onScroll); - element.addEventListener("resize", onScroll as EventListener); + element.addEventListener("resize", onScroll); // Cleanup to prevent memory leaks onDestroy(() => { element.removeEventListener("scroll", onScroll); - element.removeEventListener("resize", onScroll as EventListener); + element.removeEventListener("resize", onScroll); }); } } diff --git a/src/routes/conversations/+server.ts b/src/routes/conversations/+server.ts index a95b5e58b24..c1df878b2a9 100644 --- a/src/routes/conversations/+server.ts +++ b/src/routes/conversations/+server.ts @@ -8,8 +8,8 @@ import type { ConvSidebar } from "$lib/types/ConvSidebar"; export const GET: RequestHandler = async ({ url, locals }) => { const settings = await collections.settings.findOne(authCondition(locals)); - const limit = parseInt(url.searchParams.get("limit") ?? "3"); - const skip = parseInt(url.searchParams.get("skip") ?? "15"); + const limit = parseInt(url.searchParams.get("limit") ?? "300"); + const skip = parseInt(url.searchParams.get("skip") ?? "50"); const conversations = await collections.conversations .find(authCondition(locals))