Skip to content

Commit 786115c

Browse files
nsarrazingary149
andauthored
Top assistants page (#740)
* Basic top assistants page * remove featured check * fix edit * ui update * ui * mishig review * fix * move feedback link to settings * misc * Hide unlisted models in assistants flow * settings + public label * tweak * font-size * Hide top assistant page for soft release * always show author --------- Co-authored-by: Victor Mustar <victor.mustar@gmail.com>
1 parent a03d289 commit 786115c

File tree

16 files changed

+234
-75
lines changed

16 files changed

+234
-75
lines changed

src/lib/components/AssistantSettings.svelte

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@
106106
{:else}
107107
<h2 class="text-xl font-semibold">Create new assistant</h2>
108108
<p class="mb-6 text-sm text-gray-500">
109-
Assistants are public, and can be accessed by anyone with the link.
109+
Create and share your own AI Assistant. All assistants are <span
110+
class="rounded-full border px-2 py-0.5 leading-none">public</span
111+
>
110112
</p>
111113
{/if}
112114

@@ -196,7 +198,7 @@
196198
<label>
197199
<span class="mb-1 text-sm font-semibold">Model</span>
198200
<select name="modelId" class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2">
199-
{#each models as model}
201+
{#each models.filter((model) => !model.unlisted) as model}
200202
<option
201203
value={model.id}
202204
selected={assistant

src/lib/components/NavConversationItem.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@
4141
<img
4242
src="{base}/settings/assistants/{conv.assistantId}/avatar?hash={conv.avatarHash}"
4343
alt="Assistant avatar"
44-
class="mr-1.5 inline size-4 rounded-full object-cover"
44+
class="mr-1.5 inline size-4 flex-none rounded-full object-cover"
4545
/>
4646
{conv.title.replace(/\p{Emoji}/gu, "")}
4747
{:else if conv.assistantId}
4848
<div
49-
class="mr-1.5 flex size-4 items-center justify-center rounded-full bg-gray-300 text-xs font-bold uppercase text-gray-500"
49+
class="mr-1.5 flex size-4 flex-none items-center justify-center rounded-full bg-gray-300 text-xs font-bold uppercase text-gray-500"
5050
/>
5151
{conv.title.replace(/\p{Emoji}/gu, "")}
5252
{:else}

src/lib/components/NavMenu.svelte

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import NavConversationItem from "./NavConversationItem.svelte";
99
import type { LayoutData } from "../../routes/$types";
1010
import type { ConvSidebar } from "$lib/types/ConvSidebar";
11+
import { page } from "$app/stores";
12+
import { isHuggingChat } from "$lib/utils/isHuggingChat";
1113
1214
export let conversations: ConvSidebar[] = [];
1315
export let canLogin: boolean;
@@ -107,21 +109,26 @@
107109
>
108110
Theme
109111
</button>
112+
{#if $page.data.enableAssistants && (!isHuggingChat || $page.data.settings.assistants?.length >= 1)}
113+
<a
114+
href="{base}/assistants"
115+
class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
116+
>
117+
Assistants
118+
<span
119+
class="ml-auto rounded-full border border-gray-300 bg-white px-2 py-0.5 text-xs text-gray-500 dark:border-gray-500 dark:bg-transparent dark:text-gray-400"
120+
>New</span
121+
>
122+
</a>
123+
{/if}
124+
110125
<a
111126
href="{base}/settings"
112127
class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
113128
>
114129
Settings
115130
</a>
116131
{#if PUBLIC_APP_NAME === "HuggingChat"}
117-
<a
118-
href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions"
119-
target="_blank"
120-
rel="noreferrer"
121-
class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
122-
>
123-
Feedback
124-
</a>
125132
<a
126133
href="{base}/privacy"
127134
class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"

src/lib/components/chat/AssistantIntroduction.svelte

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,31 @@
1616
<div
1717
class="relative mt-auto rounded-2xl bg-gray-100 text-gray-600 dark:border-gray-800 dark:bg-gray-800/60 dark:text-gray-300"
1818
>
19-
<div class="flex items-center gap-4 p-4 pr-10 md:p-8 md:pt-10">
19+
<div class="flex items-center gap-4 p-4 pr-10 md:p-8 md:pt-10 xl:gap-8">
2020
{#if assistant.avatar}
2121
<img
2222
src={`${base}/settings/assistants/${assistant._id.toString()}/avatar?hash=${
2323
assistant.avatar
2424
}`}
2525
alt="avatar"
26-
class="size-16 flex-none rounded-full object-cover md:size-32"
26+
class="size-16 flex-none rounded-full object-cover max-sm:self-start md:size-32"
2727
/>
2828
{:else}
2929
<div
30-
class="flex size-12 flex-none items-center justify-center rounded-full bg-gray-300 object-cover text-xl font-bold uppercase text-gray-500 sm:text-4xl md:h-32 md:w-32 dark:bg-gray-600"
30+
class="flex size-12 flex-none items-center justify-center rounded-full bg-gray-300 object-cover text-xl font-bold uppercase text-gray-500 max-sm:self-start sm:text-4xl md:size-32 dark:bg-gray-600"
3131
>
3232
{assistant?.name[0]}
3333
</div>
3434
{/if}
3535

36-
<div class="flex h-full flex-col">
36+
<div class="flex h-full flex-col gap-2">
3737
<p
38-
class="mb-2 w-fit truncate text-ellipsis rounded-full bg-gray-200 px-3 py-1 text-xs text-gray-600 dark:bg-gray-700 dark:text-gray-400"
38+
class="w-fit truncate text-ellipsis rounded-full border bg-white px-3 py-1 text-xs text-gray-800 dark:border-gray-700 dark:bg-gray-700 dark:text-gray-400"
3939
>
4040
Assistant
4141
</p>
4242
<p class="text-xl font-bold sm:text-2xl">{assistant.name}</p>
43-
<p class="text-balance text-sm text-gray-500 dark:text-gray-400">
43+
<p class="line-clamp-6 text-balance text-sm text-gray-500 dark:text-gray-400">
4444
{assistant.description}
4545
</p>
4646

src/lib/server/database.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,6 @@ client.on("open", () => {
7373
sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }).catch(console.error);
7474
sessions.createIndex({ sessionId: 1 }, { unique: true }).catch(console.error);
7575
assistants.createIndex({ createdBy: 1 }).catch(console.error);
76+
assistants.createIndex({ userCount: 1 }).catch(console.error);
7677
reports.createIndex({ assistantId: 1 }).catch(console.error);
7778
});

src/lib/types/Assistant.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ export interface Assistant extends Timestamps {
1212
modelId: string;
1313
exampleInputs: string[];
1414
preprompt: string;
15+
16+
userCount?: number;
1517
}

src/lib/utils/isHuggingChat.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { PUBLIC_APP_ASSETS } from "$env/static/public";
2+
3+
export const isHuggingChat = PUBLIC_APP_ASSETS === "huggingchat";

src/routes/assistant/[assistantId]/+page.svelte

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
use:clickOutside={() => {
4646
goto(previousPage);
4747
}}
48-
class="z-10 flex max-w-[90dvw] flex-col content-center items-center gap-x-10 gap-y-2 overflow-hidden rounded-2xl bg-white p-4 text-center shadow-2xl outline-none max-sm:px-6 md:w-96 md:grid-cols-3 md:grid-rows-[auto,1fr] md:p-8"
48+
class="z-10 flex flex-col content-center items-center gap-x-10 gap-y-3 overflow-hidden rounded-2xl bg-white p-4 pt-6 text-center shadow-2xl outline-none max-sm:w-[85dvw] max-sm:px-6 md:w-96 md:grid-cols-3 md:grid-rows-[auto,1fr] md:p-8"
4949
>
5050
{#if data.assistant.avatar}
5151
<img
@@ -55,19 +55,21 @@
5555
/>
5656
{:else}
5757
<div
58-
class="flex size-24 flex-none items-center justify-center rounded-full bg-gray-300 font-bold uppercase text-gray-500"
58+
class="flex size-16 flex-none items-center justify-center rounded-full bg-gray-300 text-2xl font-bold uppercase text-gray-500 sm:size-24"
5959
>
6060
{data.assistant.name[0]}
6161
</div>
6262
{/if}
63-
<h1 class="text-2xl font-bold">
63+
<h1 class="text-balance text-xl font-bold">
6464
{data.assistant.name}
6565
</h1>
66-
<h3 class="text-balance text-sm text-gray-700">
67-
{data.assistant.description}
68-
</h3>
66+
{#if data.assistant.description}
67+
<h3 class="line-clamp-6 text-balance text-sm text-gray-500">
68+
{data.assistant.description}
69+
</h3>
70+
{/if}
6971
{#if data.assistant.createdByName}
70-
<p class="text-sm text-gray-500">
72+
<p class="mt-2 text-sm text-gray-500">
7173
Created by <a
7274
class="hover:underline"
7375
href="https://hf.co/{data.assistant.createdByName}"

src/routes/assistants/+page.server.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
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+
7+
export const load = async ({ url }) => {
8+
if (!ENABLE_ASSISTANTS) {
9+
throw redirect(302, `${base}/`);
10+
}
11+
12+
const modelId = url.searchParams.get("modelId");
13+
14+
// fetch the top 10 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
15+
const assistants = await collections.assistants
16+
.find({ userCount: { $gt: 1 }, modelId: modelId ?? { $exists: true } })
17+
.sort({ userCount: -1 })
18+
.limit(10)
19+
.toArray();
20+
21+
return { assistants: JSON.parse(JSON.stringify(assistants)) as Array<Assistant> };
22+
};

src/routes/assistants/+page.svelte

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<script lang="ts">
2+
import type { PageData } from "./$types";
3+
4+
import { goto } from "$app/navigation";
5+
import { base } from "$app/paths";
6+
import { page } from "$app/stores";
7+
8+
import CarbonAdd from "~icons/carbon/add";
9+
10+
export let data: PageData;
11+
12+
let selectedModel = $page.url.searchParams.get("modelId") ?? "";
13+
14+
const onModelChange = (e: Event) => {
15+
const newUrl = new URL($page.url);
16+
if ((e.target as HTMLSelectElement).value === "") {
17+
newUrl.searchParams.delete("modelId");
18+
} else {
19+
newUrl.searchParams.set("modelId", (e.target as HTMLSelectElement).value);
20+
}
21+
goto(newUrl);
22+
};
23+
</script>
24+
25+
<div class="scrollbar-custom mr-1 h-full overflow-y-auto py-12 md:py-24">
26+
<div class="pt-42 mx-auto flex flex-col px-5 xl:w-[60rem] 2xl:w-[64rem]">
27+
<h1 class="text-2xl font-bold">Assistants</h1>
28+
<h3 class="text-gray-500">Browse popular assistants made by the community</h3>
29+
<div class="mt-6 flex justify-between gap-2 max-sm:flex-col sm:items-center">
30+
<select
31+
class="mt-1 rounded-lg border border-gray-300 bg-gray-50 p-2 text-xs text-gray-900 focus:border-blue-700 focus:ring-blue-700 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
32+
bind:value={selectedModel}
33+
on:change={onModelChange}
34+
>
35+
<option value="">All models</option>
36+
{#each data.models.filter((model) => !model.unlisted) as model}
37+
<option value={model.name}>{model.name}</option>
38+
{/each}
39+
</select>
40+
41+
<a
42+
href={`${base}/settings/assistants/new`}
43+
class="flex items-center gap-1 whitespace-nowrap rounded-lg border bg-white py-1 pl-1.5 pr-2.5 text-center shadow-sm hover:bg-gray-50 hover:shadow-none dark:border-gray-600 dark:bg-gray-700 dark:hover:bg-gray-700"
44+
>
45+
<CarbonAdd class="text-orange-600" />Create New assistant
46+
</a>
47+
</div>
48+
<div class="mt-10 grid grid-cols-2 gap-4 sm:gap-5 md:grid-cols-3 lg:grid-cols-4">
49+
{#each data.assistants as assistant}
50+
<a
51+
href="{base}/assistant/{assistant._id}"
52+
class="flex flex-col items-center justify-center overflow-hidden 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"
53+
>
54+
{#if assistant.avatar}
55+
<img
56+
src="{base}/settings/assistants/{assistant._id}/avatar"
57+
alt="Avatar"
58+
class="mb-2 aspect-square size-12 flex-none rounded-full object-cover sm:mb-6 sm:size-20"
59+
/>
60+
{:else}
61+
<div
62+
class="mb-2 flex aspect-square size-12 flex-none items-center justify-center rounded-full bg-gray-300 text-2xl font-bold uppercase text-gray-500 sm:mb-6 sm:size-20 dark:bg-gray-800"
63+
>
64+
{assistant.name[0]}
65+
</div>
66+
{/if}
67+
<h3
68+
class="mb-2 line-clamp-2 max-w-full break-words text-center text-[.8rem] font-semibold leading-snug sm:text-sm"
69+
>
70+
{assistant.name}
71+
</h3>
72+
<p
73+
class="line-clamp-4 text-xxs text-gray-700 sm:line-clamp-2 sm:text-xs dark:text-gray-500"
74+
>
75+
{assistant.description}
76+
</p>
77+
{#if assistant.createdByName}
78+
<p class="mt-auto pt-2 text-xxs text-gray-400 sm:text-xs dark:text-gray-500">
79+
Created by <a
80+
class="hover:underline"
81+
href="https://hf.co/{assistant.createdByName}"
82+
target="_blank"
83+
>
84+
{assistant.createdByName}
85+
</a>
86+
</p>
87+
{/if}
88+
</a>
89+
{:else}
90+
No assistants found
91+
{/each}
92+
</div>
93+
</div>
94+
</div>

0 commit comments

Comments
 (0)