Skip to content

Commit

Permalink
Top assistants page (#740)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
nsarrazin and gary149 authored Feb 1, 2024
1 parent a03d289 commit 786115c
Show file tree
Hide file tree
Showing 16 changed files with 234 additions and 75 deletions.
6 changes: 4 additions & 2 deletions src/lib/components/AssistantSettings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@
{:else}
<h2 class="text-xl font-semibold">Create new assistant</h2>
<p class="mb-6 text-sm text-gray-500">
Assistants are public, and can be accessed by anyone with the link.
Create and share your own AI Assistant. All assistants are <span
class="rounded-full border px-2 py-0.5 leading-none">public</span
>
</p>
{/if}

Expand Down Expand Up @@ -196,7 +198,7 @@
<label>
<span class="mb-1 text-sm font-semibold">Model</span>
<select name="modelId" class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2">
{#each models as model}
{#each models.filter((model) => !model.unlisted) as model}
<option
value={model.id}
selected={assistant
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/NavConversationItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@
<img
src="{base}/settings/assistants/{conv.assistantId}/avatar?hash={conv.avatarHash}"
alt="Assistant avatar"
class="mr-1.5 inline size-4 rounded-full object-cover"
class="mr-1.5 inline size-4 flex-none rounded-full object-cover"
/>
{conv.title.replace(/\p{Emoji}/gu, "")}
{:else if conv.assistantId}
<div
class="mr-1.5 flex size-4 items-center justify-center rounded-full bg-gray-300 text-xs font-bold uppercase text-gray-500"
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"
/>
{conv.title.replace(/\p{Emoji}/gu, "")}
{:else}
Expand Down
23 changes: 15 additions & 8 deletions src/lib/components/NavMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import NavConversationItem from "./NavConversationItem.svelte";
import type { LayoutData } from "../../routes/$types";
import type { ConvSidebar } from "$lib/types/ConvSidebar";
import { page } from "$app/stores";
import { isHuggingChat } from "$lib/utils/isHuggingChat";
export let conversations: ConvSidebar[] = [];
export let canLogin: boolean;
Expand Down Expand Up @@ -107,21 +109,26 @@
>
Theme
</button>
{#if $page.data.enableAssistants && (!isHuggingChat || $page.data.settings.assistants?.length >= 1)}
<a
href="{base}/assistants"
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"
>
Assistants
<span
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"
>New</span
>
</a>
{/if}

<a
href="{base}/settings"
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"
>
Settings
</a>
{#if PUBLIC_APP_NAME === "HuggingChat"}
<a
href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions"
target="_blank"
rel="noreferrer"
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"
>
Feedback
</a>
<a
href="{base}/privacy"
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"
Expand Down
12 changes: 6 additions & 6 deletions src/lib/components/chat/AssistantIntroduction.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,31 @@
<div
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"
>
<div class="flex items-center gap-4 p-4 pr-10 md:p-8 md:pt-10">
<div class="flex items-center gap-4 p-4 pr-10 md:p-8 md:pt-10 xl:gap-8">
{#if assistant.avatar}
<img
src={`${base}/settings/assistants/${assistant._id.toString()}/avatar?hash=${
assistant.avatar
}`}
alt="avatar"
class="size-16 flex-none rounded-full object-cover md:size-32"
class="size-16 flex-none rounded-full object-cover max-sm:self-start md:size-32"
/>
{:else}
<div
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"
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"
>
{assistant?.name[0]}
</div>
{/if}

<div class="flex h-full flex-col">
<div class="flex h-full flex-col gap-2">
<p
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"
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"
>
Assistant
</p>
<p class="text-xl font-bold sm:text-2xl">{assistant.name}</p>
<p class="text-balance text-sm text-gray-500 dark:text-gray-400">
<p class="line-clamp-6 text-balance text-sm text-gray-500 dark:text-gray-400">
{assistant.description}
</p>

Expand Down
1 change: 1 addition & 0 deletions src/lib/server/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,6 @@ client.on("open", () => {
sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }).catch(console.error);
sessions.createIndex({ sessionId: 1 }, { unique: true }).catch(console.error);
assistants.createIndex({ createdBy: 1 }).catch(console.error);
assistants.createIndex({ userCount: 1 }).catch(console.error);
reports.createIndex({ assistantId: 1 }).catch(console.error);
});
2 changes: 2 additions & 0 deletions src/lib/types/Assistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ export interface Assistant extends Timestamps {
modelId: string;
exampleInputs: string[];
preprompt: string;

userCount?: number;
}
3 changes: 3 additions & 0 deletions src/lib/utils/isHuggingChat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { PUBLIC_APP_ASSETS } from "$env/static/public";

export const isHuggingChat = PUBLIC_APP_ASSETS === "huggingchat";
16 changes: 9 additions & 7 deletions src/routes/assistant/[assistantId]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
use:clickOutside={() => {
goto(previousPage);
}}
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"
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"
>
{#if data.assistant.avatar}
<img
Expand All @@ -55,19 +55,21 @@
/>
{:else}
<div
class="flex size-24 flex-none items-center justify-center rounded-full bg-gray-300 font-bold uppercase text-gray-500"
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"
>
{data.assistant.name[0]}
</div>
{/if}
<h1 class="text-2xl font-bold">
<h1 class="text-balance text-xl font-bold">
{data.assistant.name}
</h1>
<h3 class="text-balance text-sm text-gray-700">
{data.assistant.description}
</h3>
{#if data.assistant.description}
<h3 class="line-clamp-6 text-balance text-sm text-gray-500">
{data.assistant.description}
</h3>
{/if}
{#if data.assistant.createdByName}
<p class="text-sm text-gray-500">
<p class="mt-2 text-sm text-gray-500">
Created by <a
class="hover:underline"
href="https://hf.co/{data.assistant.createdByName}"
Expand Down
22 changes: 22 additions & 0 deletions src/routes/assistants/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { base } from "$app/paths";
import { ENABLE_ASSISTANTS } from "$env/static/private";
import { collections } from "$lib/server/database.js";
import type { Assistant } from "$lib/types/Assistant";
import { redirect } from "@sveltejs/kit";

export const load = async ({ url }) => {
if (!ENABLE_ASSISTANTS) {
throw redirect(302, `${base}/`);
}

const modelId = url.searchParams.get("modelId");

// 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
const assistants = await collections.assistants
.find({ userCount: { $gt: 1 }, modelId: modelId ?? { $exists: true } })
.sort({ userCount: -1 })
.limit(10)
.toArray();

return { assistants: JSON.parse(JSON.stringify(assistants)) as Array<Assistant> };
};
94 changes: 94 additions & 0 deletions src/routes/assistants/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<script lang="ts">
import type { PageData } from "./$types";
import { goto } from "$app/navigation";
import { base } from "$app/paths";
import { page } from "$app/stores";
import CarbonAdd from "~icons/carbon/add";
export let data: PageData;
let selectedModel = $page.url.searchParams.get("modelId") ?? "";
const onModelChange = (e: Event) => {
const newUrl = new URL($page.url);
if ((e.target as HTMLSelectElement).value === "") {
newUrl.searchParams.delete("modelId");
} else {
newUrl.searchParams.set("modelId", (e.target as HTMLSelectElement).value);
}
goto(newUrl);
};
</script>

<div class="scrollbar-custom mr-1 h-full overflow-y-auto py-12 md:py-24">
<div class="pt-42 mx-auto flex flex-col px-5 xl:w-[60rem] 2xl:w-[64rem]">
<h1 class="text-2xl font-bold">Assistants</h1>
<h3 class="text-gray-500">Browse popular assistants made by the community</h3>
<div class="mt-6 flex justify-between gap-2 max-sm:flex-col sm:items-center">
<select
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"
bind:value={selectedModel}
on:change={onModelChange}
>
<option value="">All models</option>
{#each data.models.filter((model) => !model.unlisted) as model}
<option value={model.name}>{model.name}</option>
{/each}
</select>

<a
href={`${base}/settings/assistants/new`}
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"
>
<CarbonAdd class="text-orange-600" />Create New assistant
</a>
</div>
<div class="mt-10 grid grid-cols-2 gap-4 sm:gap-5 md:grid-cols-3 lg:grid-cols-4">
{#each data.assistants as assistant}
<a
href="{base}/assistant/{assistant._id}"
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"
>
{#if assistant.avatar}
<img
src="{base}/settings/assistants/{assistant._id}/avatar"
alt="Avatar"
class="mb-2 aspect-square size-12 flex-none rounded-full object-cover sm:mb-6 sm:size-20"
/>
{:else}
<div
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"
>
{assistant.name[0]}
</div>
{/if}
<h3
class="mb-2 line-clamp-2 max-w-full break-words text-center text-[.8rem] font-semibold leading-snug sm:text-sm"
>
{assistant.name}
</h3>
<p
class="line-clamp-4 text-xxs text-gray-700 sm:line-clamp-2 sm:text-xs dark:text-gray-500"
>
{assistant.description}
</p>
{#if assistant.createdByName}
<p class="mt-auto pt-2 text-xxs text-gray-400 sm:text-xs dark:text-gray-500">
Created by <a
class="hover:underline"
href="https://hf.co/{assistant.createdByName}"
target="_blank"
>
{assistant.createdByName}
</a>
</p>
{/if}
</a>
{:else}
No assistants found
{/each}
</div>
</div>
</div>
37 changes: 21 additions & 16 deletions src/routes/settings/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
import { page } from "$app/stores";
import { useSettingsStore } from "$lib/stores/settings";
import CarbonClose from "~icons/carbon/close";
import CarbonArrowUpRight from "~icons/carbon/ArrowUpRight";
import CarbonCheckmark from "~icons/carbon/checkmark";
import CarbonAdd from "~icons/carbon/add";
import UserIcon from "~icons/carbon/user";
import { fade, fly } from "svelte/transition";
import { PUBLIC_APP_ASSETS } from "$env/static/public";
import { isHuggingChat } from "$lib/utils/isHuggingChat";
export let data;
let previousPage: string = base;
Expand All @@ -22,8 +23,6 @@
});
const settings = useSettingsStore();
const isHuggingChat = PUBLIC_APP_ASSETS === "huggingchat";
</script>

<div
Expand Down Expand Up @@ -73,11 +72,22 @@
<!-- if its huggingchat, the number of assistants owned by the user must be non-zero to show the UI -->
{#if data.enableAssistants && (!isHuggingChat || data.assistants.length >= 1)}
<h3 class="pb-3 pl-3 pt-5 text-[.8rem] text-gray-800 sm:pl-1">Assistants</h3>

{#if !data.loginEnabled || (data.loginEnabled && !!data.user)}
<a
href="{base}/settings/assistants/new"
class="group flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl
{$page.url.pathname === `${base}/settings/assistants/new` ? '!bg-gray-100 !text-gray-800' : ''}"
>
<CarbonAdd />
<div class="truncate">Create new assistant</div>
</a>
{/if}
{#each data.assistants as assistant}
<a
href="{base}/settings/assistants/{assistant._id.toString()}"
class="group flex h-10 flex-none items-center gap-2 pl-2 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl
{assistant._id.toString() === $page.params.assistantId ? '!bg-gray-100 !text-gray-800' : ''}"
{assistant._id.toString() === $page.params.assistantId ? '!bg-gray-100 !text-gray-800' : ''}"
>
{#if assistant.avatar}
<img
Expand All @@ -102,25 +112,20 @@
{/if}
</a>
{/each}

{#if !data.loginEnabled || (data.loginEnabled && !!data.user)}
<a
href="{base}/settings/assistants/new"
class="group flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl
{$page.url.pathname === `${base}/settings/assistants/new` ? '!bg-gray-100 !text-gray-800' : ''}"
>
<CarbonAdd />
<div class="truncate">Create new assistant</div>
</a>
{/if}
<a
href="{base}/assistants"
class="group flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl"
><CarbonArrowUpRight class="mr-1.5 shrink-0 text-xs " />
<div class="truncate">Browse Assistants</div>
</a>
{/if}

<a
href="{base}/settings"
class="group mt-auto flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 max-md:order-first md:rounded-xl
{$page.url.pathname === `${base}/settings` ? '!bg-gray-100 !text-gray-800' : ''}"
>
<UserIcon class="text-lg" />
<UserIcon class="text-sm" />
Application Settings
</a>
</div>
Expand Down
Loading

0 comments on commit 786115c

Please sign in to comment.