[Assitants] Community | User tabs (#773)
Browse files* [Assistants] Add pagination
* [Assitants] Community | User tabs
* format
* minimize diff
* Update src/routes/assistants/+page.svelte
Co-authored-by: Victor Muštar <victor.mustar@gmail.com>
* use data.user
* rm unused import
* Use a href & simplify overall
* lint
* it will never be a button
* statements need to be reactive
* format
* set filter differntly
* Revert "set filter differntly"
This reverts commit fc8a2fc67463fb5ad39be3c3ae16ce74caa1f05d.
* use inline
* avoid ugly image change
* fix query
* ui update
* update link to profiles
* link to HF profile
* dark mode
* Update +page.svelte
* refactor getHref to be more descriptive
* Preserve `user` searchQuery when chaning model ids
* `getHref` utility
* dont show single "Community" btn for non-logged in users
* fix iterator
---------
Co-authored-by: Victor Muštar <victor.mustar@gmail.com>
- src/lib/components/Pagination.svelte +4 -9
- src/lib/components/chat/AssistantIntroduction.svelte +1 -2
- src/lib/utils/getHref.ts +41 -0
- src/routes/assistant/[assistantId]/+page.svelte +1 -2
- src/routes/assistants/+page.server.ts +10 -1
- src/routes/assistants/+page.svelte +66 -9
- src/routes/settings/assistants/[assistantId]/+page.svelte +1 -1
@@ -1,5 +1,6 @@
|
|
1 |
<script lang="ts">
|
2 |
import { page } from "$app/stores";
|
|
|
3 |
import PaginationArrow from "./PaginationArrow.svelte";
|
4 |
|
5 |
export let classNames = "";
|
@@ -12,12 +13,6 @@
|
|
12 |
$: pageIndex = parseInt($page.url.searchParams.get("p") ?? "0");
|
13 |
$: pageIndexes = getPageIndexes(pageIndex, numTotalPages);
|
14 |
|
15 |
-
function getHref(url: URL | string, pageIdx: number) {
|
16 |
-
const newUrl = new URL(url);
|
17 |
-
newUrl.searchParams.set("p", pageIdx.toString());
|
18 |
-
return newUrl.toString();
|
19 |
-
}
|
20 |
-
|
21 |
function getPageIndexes(pageIdx: number, nTotalPages: number) {
|
22 |
let pageIdxs: number[] = [];
|
23 |
|
@@ -66,7 +61,7 @@
|
|
66 |
>
|
67 |
<li>
|
68 |
<PaginationArrow
|
69 |
-
href={getHref($page.url, pageIndex - 1)}
|
70 |
direction="previous"
|
71 |
isDisabled={pageIndex - 1 < 0}
|
72 |
/>
|
@@ -81,7 +76,7 @@
|
|
81 |
: ''}
|
82 |
"
|
83 |
class:pointer-events-none={pageIdx === ELLIPSIS_IDX || pageIndex === pageIdx}
|
84 |
-
href={getHref($page.url, pageIdx)}
|
85 |
>
|
86 |
{pageIdx === ELLIPSIS_IDX ? "..." : pageIdx + 1}
|
87 |
</a>
|
@@ -89,7 +84,7 @@
|
|
89 |
{/each}
|
90 |
<li>
|
91 |
<PaginationArrow
|
92 |
-
href={getHref($page.url, pageIndex + 1)}
|
93 |
direction="next"
|
94 |
isDisabled={pageIndex + 1 >= numTotalPages}
|
95 |
/>
|
|
|
1 |
<script lang="ts">
|
2 |
import { page } from "$app/stores";
|
3 |
+
import { getHref } from "$lib/utils/getHref";
|
4 |
import PaginationArrow from "./PaginationArrow.svelte";
|
5 |
|
6 |
export let classNames = "";
|
|
|
13 |
$: pageIndex = parseInt($page.url.searchParams.get("p") ?? "0");
|
14 |
$: pageIndexes = getPageIndexes(pageIndex, numTotalPages);
|
15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
function getPageIndexes(pageIdx: number, nTotalPages: number) {
|
17 |
let pageIdxs: number[] = [];
|
18 |
|
|
|
61 |
>
|
62 |
<li>
|
63 |
<PaginationArrow
|
64 |
+
href={getHref($page.url, { newKeys: { p: (pageIndex - 1).toString() } })}
|
65 |
direction="previous"
|
66 |
isDisabled={pageIndex - 1 < 0}
|
67 |
/>
|
|
|
76 |
: ''}
|
77 |
"
|
78 |
class:pointer-events-none={pageIdx === ELLIPSIS_IDX || pageIndex === pageIdx}
|
79 |
+
href={getHref($page.url, { newKeys: { p: pageIdx.toString() } })}
|
80 |
>
|
81 |
{pageIdx === ELLIPSIS_IDX ? "..." : pageIdx + 1}
|
82 |
</a>
|
|
|
84 |
{/each}
|
85 |
<li>
|
86 |
<PaginationArrow
|
87 |
+
href={getHref($page.url, { newKeys: { p: (pageIndex + 1).toString() } })}
|
88 |
direction="next"
|
89 |
isDisabled={pageIndex + 1 >= numTotalPages}
|
90 |
/>
|
@@ -47,8 +47,7 @@
|
|
47 |
<p class="pt-2 text-sm text-gray-400 dark:text-gray-500">
|
48 |
Created by <a
|
49 |
class="hover:underline"
|
50 |
-
href="
|
51 |
-
target="_blank"
|
52 |
>
|
53 |
{assistant.createdByName}
|
54 |
</a>
|
|
|
47 |
<p class="pt-2 text-sm text-gray-400 dark:text-gray-500">
|
48 |
Created by <a
|
49 |
class="hover:underline"
|
50 |
+
href="{base}/assistants?user={assistant.createdByName}"
|
|
|
51 |
>
|
52 |
{assistant.createdByName}
|
53 |
</a>
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export function getHref(
|
2 |
+
url: URL | string,
|
3 |
+
modifications: {
|
4 |
+
newKeys?: Record<string, string | undefined | null>;
|
5 |
+
existingKeys?: { behaviour: "delete_except" | "delete"; keys: string[] };
|
6 |
+
}
|
7 |
+
) {
|
8 |
+
const newUrl = new URL(url);
|
9 |
+
const { newKeys, existingKeys } = modifications;
|
10 |
+
|
11 |
+
// exsiting keys logic
|
12 |
+
if (existingKeys) {
|
13 |
+
const { behaviour, keys } = existingKeys;
|
14 |
+
if (behaviour === "delete") {
|
15 |
+
for (const key of keys) {
|
16 |
+
newUrl.searchParams.delete(key);
|
17 |
+
}
|
18 |
+
} else {
|
19 |
+
// delete_except
|
20 |
+
const keysToPreserve = keys;
|
21 |
+
for (const key of [...newUrl.searchParams.keys()]) {
|
22 |
+
if (!keysToPreserve.includes(key)) {
|
23 |
+
newUrl.searchParams.delete(key);
|
24 |
+
}
|
25 |
+
}
|
26 |
+
}
|
27 |
+
}
|
28 |
+
|
29 |
+
// new keys logic
|
30 |
+
if (newKeys) {
|
31 |
+
for (const [key, val] of Object.entries(newKeys)) {
|
32 |
+
if (val) {
|
33 |
+
newUrl.searchParams.set(key, val);
|
34 |
+
} else {
|
35 |
+
newUrl.searchParams.delete(key);
|
36 |
+
}
|
37 |
+
}
|
38 |
+
}
|
39 |
+
|
40 |
+
return newUrl.toString();
|
41 |
+
}
|
@@ -73,8 +73,7 @@
|
|
73 |
<p class="mt-2 text-sm text-gray-500">
|
74 |
Created by <a
|
75 |
class="hover:underline"
|
76 |
-
href="
|
77 |
-
target="_blank"
|
78 |
>
|
79 |
{data.assistant.createdByName}
|
80 |
</a>
|
|
|
73 |
<p class="mt-2 text-sm text-gray-500">
|
74 |
Created by <a
|
75 |
class="hover:underline"
|
76 |
+
href="{base}/assistants?user={data.assistant.createdByName}"
|
|
|
77 |
>
|
78 |
{data.assistant.createdByName}
|
79 |
</a>
|
@@ -2,7 +2,7 @@ import { base } from "$app/paths";
|
|
2 |
import { ENABLE_ASSISTANTS } from "$env/static/private";
|
3 |
import { collections } from "$lib/server/database.js";
|
4 |
import type { Assistant } from "$lib/types/Assistant";
|
5 |
-
import { redirect } from "@sveltejs/kit";
|
6 |
import type { Filter } from "mongodb";
|
7 |
|
8 |
const NUM_PER_PAGE = 24;
|
@@ -14,12 +14,21 @@ export const load = async ({ url }) => {
|
|
14 |
|
15 |
const modelId = url.searchParams.get("modelId");
|
16 |
const pageIndex = parseInt(url.searchParams.get("p") ?? "0");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
// fetch the top assistants sorted by user count from biggest to smallest, filter out all assistants with only 1 users. filter by model too if modelId is provided
|
19 |
const filter: Filter<Assistant> = {
|
20 |
userCount: { $gt: 1 },
|
21 |
modelId: modelId ?? { $exists: true },
|
22 |
featured: true,
|
|
|
23 |
};
|
24 |
const assistants = await collections.assistants
|
25 |
.find(filter)
|
|
|
2 |
import { ENABLE_ASSISTANTS } from "$env/static/private";
|
3 |
import { collections } from "$lib/server/database.js";
|
4 |
import type { Assistant } from "$lib/types/Assistant";
|
5 |
+
import { error, redirect } from "@sveltejs/kit";
|
6 |
import type { Filter } from "mongodb";
|
7 |
|
8 |
const NUM_PER_PAGE = 24;
|
|
|
14 |
|
15 |
const modelId = url.searchParams.get("modelId");
|
16 |
const pageIndex = parseInt(url.searchParams.get("p") ?? "0");
|
17 |
+
const createdByName = url.searchParams.get("user");
|
18 |
+
|
19 |
+
if (createdByName) {
|
20 |
+
const existingUser = await collections.users.findOne({ username: createdByName });
|
21 |
+
if (!existingUser) {
|
22 |
+
throw error(404, `User "${createdByName}" doesn't exist`);
|
23 |
+
}
|
24 |
+
}
|
25 |
|
26 |
// fetch the top assistants sorted by user count from biggest to smallest, filter out all assistants with only 1 users. filter by model too if modelId is provided
|
27 |
const filter: Filter<Assistant> = {
|
28 |
userCount: { $gt: 1 },
|
29 |
modelId: modelId ?? { $exists: true },
|
30 |
featured: true,
|
31 |
+
...(createdByName && { createdByName }),
|
32 |
};
|
33 |
const assistants = await collections.assistants
|
34 |
.find(filter)
|
@@ -10,16 +10,22 @@
|
|
10 |
|
11 |
import CarbonAdd from "~icons/carbon/add";
|
12 |
import CarbonHelpFilled from "~icons/carbon/help-filled";
|
|
|
|
|
|
|
13 |
import Pagination from "$lib/components/Pagination.svelte";
|
|
|
14 |
|
15 |
export let data: PageData;
|
16 |
|
|
|
|
|
|
|
17 |
const onModelChange = (e: Event) => {
|
18 |
-
const newUrl =
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
}
|
23 |
goto(newUrl);
|
24 |
};
|
25 |
</script>
|
@@ -79,8 +85,60 @@
|
|
79 |
<CarbonAdd />Create New assistant
|
80 |
</a>
|
81 |
</div>
|
82 |
-
|
83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
<a
|
85 |
href="{base}/assistant/{assistant._id}"
|
86 |
class="flex flex-col items-center justify-center overflow-hidden text-balance rounded-xl border bg-gray-50/50 px-4 py-6 text-center shadow hover:bg-gray-50 hover:shadow-inner max-sm:px-4 sm:h-64 sm:pb-4 dark:border-gray-800/70 dark:bg-gray-950/20 dark:hover:bg-gray-950/40"
|
@@ -110,8 +168,7 @@
|
|
110 |
<p class="mt-auto pt-2 text-xs text-gray-400 dark:text-gray-500">
|
111 |
Created by <a
|
112 |
class="hover:underline"
|
113 |
-
href="
|
114 |
-
target="_blank"
|
115 |
>
|
116 |
{assistant.createdByName}
|
117 |
</a>
|
|
|
10 |
|
11 |
import CarbonAdd from "~icons/carbon/add";
|
12 |
import CarbonHelpFilled from "~icons/carbon/help-filled";
|
13 |
+
import CarbonClose from "~icons/carbon/close";
|
14 |
+
import CarbonArrowUpRight from "~icons/carbon/arrow-up-right";
|
15 |
+
import CarbonEarthAmerica from "~icons/carbon/earth-americas-filled";
|
16 |
import Pagination from "$lib/components/Pagination.svelte";
|
17 |
+
import { getHref } from "$lib/utils/getHref";
|
18 |
|
19 |
export let data: PageData;
|
20 |
|
21 |
+
$: assistantsCreator = $page.url.searchParams.get("user");
|
22 |
+
$: createdByMe = data.user?.username && data.user.username === assistantsCreator;
|
23 |
+
|
24 |
const onModelChange = (e: Event) => {
|
25 |
+
const newUrl = getHref($page.url, {
|
26 |
+
newKeys: { modelId: (e.target as HTMLSelectElement).value },
|
27 |
+
existingKeys: { behaviour: "delete_except", keys: ["user"] },
|
28 |
+
});
|
|
|
29 |
goto(newUrl);
|
30 |
};
|
31 |
</script>
|
|
|
85 |
<CarbonAdd />Create New assistant
|
86 |
</a>
|
87 |
</div>
|
88 |
+
|
89 |
+
<div class="mt-7 flex items-center gap-x-2 text-sm">
|
90 |
+
{#if assistantsCreator && !createdByMe}
|
91 |
+
<div
|
92 |
+
class="flex items-center gap-1.5 rounded-full border border-gray-300 bg-gray-50 px-3 py-1 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
93 |
+
>
|
94 |
+
{assistantsCreator}'s Assistants
|
95 |
+
<a
|
96 |
+
href={getHref($page.url, {
|
97 |
+
existingKeys: { behaviour: "delete", keys: ["user", "modelId", "p"] },
|
98 |
+
})}
|
99 |
+
class="group"
|
100 |
+
><CarbonClose
|
101 |
+
class="text-xs group-hover:text-gray-800 dark:group-hover:text-gray-300"
|
102 |
+
/></a
|
103 |
+
>
|
104 |
+
</div>
|
105 |
+
{#if isHuggingChat}
|
106 |
+
<a
|
107 |
+
href="https://hf.co/{assistantsCreator}"
|
108 |
+
target="_blank"
|
109 |
+
class="ml-auto flex items-center text-xs text-gray-500 underline hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-300"
|
110 |
+
><CarbonArrowUpRight class="mr-1 flex-none text-[0.58rem]" target="_blank" />View {assistantsCreator}
|
111 |
+
on HF</a
|
112 |
+
>
|
113 |
+
{/if}
|
114 |
+
{:else if data.user?.username}
|
115 |
+
<a
|
116 |
+
href={getHref($page.url, {
|
117 |
+
existingKeys: { behaviour: "delete", keys: ["user", "modelId", "p"] },
|
118 |
+
})}
|
119 |
+
class="flex items-center gap-1.5 rounded-full border px-3 py-1 {!assistantsCreator
|
120 |
+
? 'border-gray-300 bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
121 |
+
: 'border-transparent text-gray-400 hover:text-gray-800 dark:hover:text-gray-300'}"
|
122 |
+
>
|
123 |
+
<CarbonEarthAmerica class="text-xs" />
|
124 |
+
Community
|
125 |
+
</a>
|
126 |
+
<a
|
127 |
+
href={getHref($page.url, {
|
128 |
+
newKeys: { user: data.user.username },
|
129 |
+
existingKeys: { behaviour: "delete", keys: ["modelId", "p"] },
|
130 |
+
})}
|
131 |
+
class="flex items-center gap-1.5 rounded-full border px-3 py-1 {assistantsCreator &&
|
132 |
+
createdByMe
|
133 |
+
? 'border-gray-300 bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
134 |
+
: 'border-transparent text-gray-400 hover:text-gray-800 dark:hover:text-gray-300'}"
|
135 |
+
>{data.user.username}
|
136 |
+
</a>
|
137 |
+
{/if}
|
138 |
+
</div>
|
139 |
+
|
140 |
+
<div class="mt-8 grid grid-cols-2 gap-3 sm:gap-5 md:grid-cols-3 lg:grid-cols-4">
|
141 |
+
{#each data.assistants as assistant (assistant._id)}
|
142 |
<a
|
143 |
href="{base}/assistant/{assistant._id}"
|
144 |
class="flex flex-col items-center justify-center overflow-hidden text-balance rounded-xl border bg-gray-50/50 px-4 py-6 text-center shadow hover:bg-gray-50 hover:shadow-inner max-sm:px-4 sm:h-64 sm:pb-4 dark:border-gray-800/70 dark:bg-gray-950/20 dark:hover:bg-gray-950/40"
|
|
|
168 |
<p class="mt-auto pt-2 text-xs text-gray-400 dark:text-gray-500">
|
169 |
Created by <a
|
170 |
class="hover:underline"
|
171 |
+
href="{base}/assistants?user={assistant.createdByName}"
|
|
|
172 |
>
|
173 |
{assistant.createdByName}
|
174 |
</a>
|
@@ -62,7 +62,7 @@
|
|
62 |
<p class="text-sm text-gray-500">
|
63 |
Model: <span class="font-semibold"> {assistant?.modelId} </span>
|
64 |
<span class="text-gray-300">•</span> Created by
|
65 |
-
<a class="underline"
|
66 |
{assistant?.createdByName}
|
67 |
</a>
|
68 |
</p>
|
|
|
62 |
<p class="text-sm text-gray-500">
|
63 |
Model: <span class="font-semibold"> {assistant?.modelId} </span>
|
64 |
<span class="text-gray-300">•</span> Created by
|
65 |
+
<a class="underline" href="{base}/assistants?user={assistant?.createdByName}">
|
66 |
{assistant?.createdByName}
|
67 |
</a>
|
68 |
</p>
|