Spaces:
Running
[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>
|