Top assistants page (#740)
Browse files* 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>
- src/lib/components/AssistantSettings.svelte +4 -2
- src/lib/components/NavConversationItem.svelte +2 -2
- src/lib/components/NavMenu.svelte +15 -8
- src/lib/components/chat/AssistantIntroduction.svelte +6 -6
- src/lib/server/database.ts +1 -0
- src/lib/types/Assistant.ts +2 -0
- src/lib/utils/isHuggingChat.ts +3 -0
- src/routes/assistant/[assistantId]/+page.svelte +9 -7
- src/routes/assistants/+page.server.ts +22 -0
- src/routes/assistants/+page.svelte +94 -0
- src/routes/settings/+layout.svelte +21 -16
- src/routes/settings/+page.svelte +16 -6
- src/routes/settings/assistants/[assistantId]/+page.server.ts +13 -3
- src/routes/settings/assistants/[assistantId]/+page.svelte +14 -16
- src/routes/settings/assistants/[assistantId]/edit/+page.server.ts +10 -8
- src/routes/settings/assistants/new/+page.server.ts +2 -1
src/lib/components/AssistantSettings.svelte
CHANGED
@@ -106,7 +106,9 @@
|
|
106 |
{:else}
|
107 |
<h2 class="text-xl font-semibold">Create new assistant</h2>
|
108 |
<p class="mb-6 text-sm text-gray-500">
|
109 |
-
|
|
|
|
|
110 |
</p>
|
111 |
{/if}
|
112 |
|
@@ -196,7 +198,7 @@
|
|
196 |
<label>
|
197 |
<span class="mb-1 text-sm font-semibold">Model</span>
|
198 |
<select name="modelId" class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2">
|
199 |
-
{#each models as model}
|
200 |
<option
|
201 |
value={model.id}
|
202 |
selected={assistant
|
|
|
106 |
{:else}
|
107 |
<h2 class="text-xl font-semibold">Create new assistant</h2>
|
108 |
<p class="mb-6 text-sm text-gray-500">
|
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 |
+
>
|
112 |
</p>
|
113 |
{/if}
|
114 |
|
|
|
198 |
<label>
|
199 |
<span class="mb-1 text-sm font-semibold">Model</span>
|
200 |
<select name="modelId" class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2">
|
201 |
+
{#each models.filter((model) => !model.unlisted) as model}
|
202 |
<option
|
203 |
value={model.id}
|
204 |
selected={assistant
|
src/lib/components/NavConversationItem.svelte
CHANGED
@@ -41,12 +41,12 @@
|
|
41 |
<img
|
42 |
src="{base}/settings/assistants/{conv.assistantId}/avatar?hash={conv.avatarHash}"
|
43 |
alt="Assistant avatar"
|
44 |
-
class="mr-1.5 inline size-4 rounded-full object-cover"
|
45 |
/>
|
46 |
{conv.title.replace(/\p{Emoji}/gu, "")}
|
47 |
{:else if conv.assistantId}
|
48 |
<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"
|
50 |
/>
|
51 |
{conv.title.replace(/\p{Emoji}/gu, "")}
|
52 |
{:else}
|
|
|
41 |
<img
|
42 |
src="{base}/settings/assistants/{conv.assistantId}/avatar?hash={conv.avatarHash}"
|
43 |
alt="Assistant avatar"
|
44 |
+
class="mr-1.5 inline size-4 flex-none rounded-full object-cover"
|
45 |
/>
|
46 |
{conv.title.replace(/\p{Emoji}/gu, "")}
|
47 |
{:else if conv.assistantId}
|
48 |
<div
|
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"
|
50 |
/>
|
51 |
{conv.title.replace(/\p{Emoji}/gu, "")}
|
52 |
{:else}
|
src/lib/components/NavMenu.svelte
CHANGED
@@ -8,6 +8,8 @@
|
|
8 |
import NavConversationItem from "./NavConversationItem.svelte";
|
9 |
import type { LayoutData } from "../../routes/$types";
|
10 |
import type { ConvSidebar } from "$lib/types/ConvSidebar";
|
|
|
|
|
11 |
|
12 |
export let conversations: ConvSidebar[] = [];
|
13 |
export let canLogin: boolean;
|
@@ -107,6 +109,19 @@
|
|
107 |
>
|
108 |
Theme
|
109 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
110 |
<a
|
111 |
href="{base}/settings"
|
112 |
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"
|
@@ -114,14 +129,6 @@
|
|
114 |
Settings
|
115 |
</a>
|
116 |
{#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>
|
125 |
<a
|
126 |
href="{base}/privacy"
|
127 |
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"
|
|
|
8 |
import NavConversationItem from "./NavConversationItem.svelte";
|
9 |
import type { LayoutData } from "../../routes/$types";
|
10 |
import type { ConvSidebar } from "$lib/types/ConvSidebar";
|
11 |
+
import { page } from "$app/stores";
|
12 |
+
import { isHuggingChat } from "$lib/utils/isHuggingChat";
|
13 |
|
14 |
export let conversations: ConvSidebar[] = [];
|
15 |
export let canLogin: boolean;
|
|
|
109 |
>
|
110 |
Theme
|
111 |
</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 |
+
|
125 |
<a
|
126 |
href="{base}/settings"
|
127 |
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"
|
|
|
129 |
Settings
|
130 |
</a>
|
131 |
{#if PUBLIC_APP_NAME === "HuggingChat"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
132 |
<a
|
133 |
href="{base}/privacy"
|
134 |
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
CHANGED
@@ -16,31 +16,31 @@
|
|
16 |
<div
|
17 |
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"
|
18 |
>
|
19 |
-
<div class="flex items-center gap-4 p-4 pr-10 md:p-8 md:pt-10">
|
20 |
{#if assistant.avatar}
|
21 |
<img
|
22 |
src={`${base}/settings/assistants/${assistant._id.toString()}/avatar?hash=${
|
23 |
assistant.avatar
|
24 |
}`}
|
25 |
alt="avatar"
|
26 |
-
class="size-16 flex-none rounded-full object-cover md:size-32"
|
27 |
/>
|
28 |
{:else}
|
29 |
<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:
|
31 |
>
|
32 |
{assistant?.name[0]}
|
33 |
</div>
|
34 |
{/if}
|
35 |
|
36 |
-
<div class="flex h-full flex-col">
|
37 |
<p
|
38 |
-
class="
|
39 |
>
|
40 |
Assistant
|
41 |
</p>
|
42 |
<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">
|
44 |
{assistant.description}
|
45 |
</p>
|
46 |
|
|
|
16 |
<div
|
17 |
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"
|
18 |
>
|
19 |
+
<div class="flex items-center gap-4 p-4 pr-10 md:p-8 md:pt-10 xl:gap-8">
|
20 |
{#if assistant.avatar}
|
21 |
<img
|
22 |
src={`${base}/settings/assistants/${assistant._id.toString()}/avatar?hash=${
|
23 |
assistant.avatar
|
24 |
}`}
|
25 |
alt="avatar"
|
26 |
+
class="size-16 flex-none rounded-full object-cover max-sm:self-start md:size-32"
|
27 |
/>
|
28 |
{:else}
|
29 |
<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 max-sm:self-start sm:text-4xl md:size-32 dark:bg-gray-600"
|
31 |
>
|
32 |
{assistant?.name[0]}
|
33 |
</div>
|
34 |
{/if}
|
35 |
|
36 |
+
<div class="flex h-full flex-col gap-2">
|
37 |
<p
|
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"
|
39 |
>
|
40 |
Assistant
|
41 |
</p>
|
42 |
<p class="text-xl font-bold sm:text-2xl">{assistant.name}</p>
|
43 |
+
<p class="line-clamp-6 text-balance text-sm text-gray-500 dark:text-gray-400">
|
44 |
{assistant.description}
|
45 |
</p>
|
46 |
|
src/lib/server/database.ts
CHANGED
@@ -73,5 +73,6 @@ client.on("open", () => {
|
|
73 |
sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }).catch(console.error);
|
74 |
sessions.createIndex({ sessionId: 1 }, { unique: true }).catch(console.error);
|
75 |
assistants.createIndex({ createdBy: 1 }).catch(console.error);
|
|
|
76 |
reports.createIndex({ assistantId: 1 }).catch(console.error);
|
77 |
});
|
|
|
73 |
sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }).catch(console.error);
|
74 |
sessions.createIndex({ sessionId: 1 }, { unique: true }).catch(console.error);
|
75 |
assistants.createIndex({ createdBy: 1 }).catch(console.error);
|
76 |
+
assistants.createIndex({ userCount: 1 }).catch(console.error);
|
77 |
reports.createIndex({ assistantId: 1 }).catch(console.error);
|
78 |
});
|
src/lib/types/Assistant.ts
CHANGED
@@ -12,4 +12,6 @@ export interface Assistant extends Timestamps {
|
|
12 |
modelId: string;
|
13 |
exampleInputs: string[];
|
14 |
preprompt: string;
|
|
|
|
|
15 |
}
|
|
|
12 |
modelId: string;
|
13 |
exampleInputs: string[];
|
14 |
preprompt: string;
|
15 |
+
|
16 |
+
userCount?: number;
|
17 |
}
|
src/lib/utils/isHuggingChat.ts
ADDED
@@ -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
CHANGED
@@ -45,7 +45,7 @@
|
|
45 |
use:clickOutside={() => {
|
46 |
goto(previousPage);
|
47 |
}}
|
48 |
-
class="z-10 flex
|
49 |
>
|
50 |
{#if data.assistant.avatar}
|
51 |
<img
|
@@ -55,19 +55,21 @@
|
|
55 |
/>
|
56 |
{:else}
|
57 |
<div
|
58 |
-
class="flex size-
|
59 |
>
|
60 |
{data.assistant.name[0]}
|
61 |
</div>
|
62 |
{/if}
|
63 |
-
<h1 class="text-
|
64 |
{data.assistant.name}
|
65 |
</h1>
|
66 |
-
|
67 |
-
|
68 |
-
|
|
|
|
|
69 |
{#if data.assistant.createdByName}
|
70 |
-
<p class="text-sm text-gray-500">
|
71 |
Created by <a
|
72 |
class="hover:underline"
|
73 |
href="https://hf.co/{data.assistant.createdByName}"
|
|
|
45 |
use:clickOutside={() => {
|
46 |
goto(previousPage);
|
47 |
}}
|
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"
|
49 |
>
|
50 |
{#if data.assistant.avatar}
|
51 |
<img
|
|
|
55 |
/>
|
56 |
{:else}
|
57 |
<div
|
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"
|
59 |
>
|
60 |
{data.assistant.name[0]}
|
61 |
</div>
|
62 |
{/if}
|
63 |
+
<h1 class="text-balance text-xl font-bold">
|
64 |
{data.assistant.name}
|
65 |
</h1>
|
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}
|
71 |
{#if data.assistant.createdByName}
|
72 |
+
<p class="mt-2 text-sm text-gray-500">
|
73 |
Created by <a
|
74 |
class="hover:underline"
|
75 |
href="https://hf.co/{data.assistant.createdByName}"
|
src/routes/assistants/+page.server.ts
ADDED
@@ -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
ADDED
@@ -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>
|
src/routes/settings/+layout.svelte
CHANGED
@@ -5,12 +5,13 @@
|
|
5 |
import { page } from "$app/stores";
|
6 |
import { useSettingsStore } from "$lib/stores/settings";
|
7 |
import CarbonClose from "~icons/carbon/close";
|
|
|
8 |
import CarbonCheckmark from "~icons/carbon/checkmark";
|
9 |
import CarbonAdd from "~icons/carbon/add";
|
10 |
|
11 |
import UserIcon from "~icons/carbon/user";
|
12 |
import { fade, fly } from "svelte/transition";
|
13 |
-
import {
|
14 |
export let data;
|
15 |
|
16 |
let previousPage: string = base;
|
@@ -22,8 +23,6 @@
|
|
22 |
});
|
23 |
|
24 |
const settings = useSettingsStore();
|
25 |
-
|
26 |
-
const isHuggingChat = PUBLIC_APP_ASSETS === "huggingchat";
|
27 |
</script>
|
28 |
|
29 |
<div
|
@@ -73,11 +72,22 @@
|
|
73 |
<!-- if its huggingchat, the number of assistants owned by the user must be non-zero to show the UI -->
|
74 |
{#if data.enableAssistants && (!isHuggingChat || data.assistants.length >= 1)}
|
75 |
<h3 class="pb-3 pl-3 pt-5 text-[.8rem] text-gray-800 sm:pl-1">Assistants</h3>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
76 |
{#each data.assistants as assistant}
|
77 |
<a
|
78 |
href="{base}/settings/assistants/{assistant._id.toString()}"
|
79 |
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
|
80 |
-
|
81 |
>
|
82 |
{#if assistant.avatar}
|
83 |
<img
|
@@ -102,17 +112,12 @@
|
|
102 |
{/if}
|
103 |
</a>
|
104 |
{/each}
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
>
|
112 |
-
<CarbonAdd />
|
113 |
-
<div class="truncate">Create new assistant</div>
|
114 |
-
</a>
|
115 |
-
{/if}
|
116 |
{/if}
|
117 |
|
118 |
<a
|
@@ -120,7 +125,7 @@
|
|
120 |
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
|
121 |
{$page.url.pathname === `${base}/settings` ? '!bg-gray-100 !text-gray-800' : ''}"
|
122 |
>
|
123 |
-
<UserIcon class="text-
|
124 |
Application Settings
|
125 |
</a>
|
126 |
</div>
|
|
|
5 |
import { page } from "$app/stores";
|
6 |
import { useSettingsStore } from "$lib/stores/settings";
|
7 |
import CarbonClose from "~icons/carbon/close";
|
8 |
+
import CarbonArrowUpRight from "~icons/carbon/ArrowUpRight";
|
9 |
import CarbonCheckmark from "~icons/carbon/checkmark";
|
10 |
import CarbonAdd from "~icons/carbon/add";
|
11 |
|
12 |
import UserIcon from "~icons/carbon/user";
|
13 |
import { fade, fly } from "svelte/transition";
|
14 |
+
import { isHuggingChat } from "$lib/utils/isHuggingChat";
|
15 |
export let data;
|
16 |
|
17 |
let previousPage: string = base;
|
|
|
23 |
});
|
24 |
|
25 |
const settings = useSettingsStore();
|
|
|
|
|
26 |
</script>
|
27 |
|
28 |
<div
|
|
|
72 |
<!-- if its huggingchat, the number of assistants owned by the user must be non-zero to show the UI -->
|
73 |
{#if data.enableAssistants && (!isHuggingChat || data.assistants.length >= 1)}
|
74 |
<h3 class="pb-3 pl-3 pt-5 text-[.8rem] text-gray-800 sm:pl-1">Assistants</h3>
|
75 |
+
|
76 |
+
{#if !data.loginEnabled || (data.loginEnabled && !!data.user)}
|
77 |
+
<a
|
78 |
+
href="{base}/settings/assistants/new"
|
79 |
+
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
|
80 |
+
{$page.url.pathname === `${base}/settings/assistants/new` ? '!bg-gray-100 !text-gray-800' : ''}"
|
81 |
+
>
|
82 |
+
<CarbonAdd />
|
83 |
+
<div class="truncate">Create new assistant</div>
|
84 |
+
</a>
|
85 |
+
{/if}
|
86 |
{#each data.assistants as assistant}
|
87 |
<a
|
88 |
href="{base}/settings/assistants/{assistant._id.toString()}"
|
89 |
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
|
90 |
+
{assistant._id.toString() === $page.params.assistantId ? '!bg-gray-100 !text-gray-800' : ''}"
|
91 |
>
|
92 |
{#if assistant.avatar}
|
93 |
<img
|
|
|
112 |
{/if}
|
113 |
</a>
|
114 |
{/each}
|
115 |
+
<a
|
116 |
+
href="{base}/assistants"
|
117 |
+
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"
|
118 |
+
><CarbonArrowUpRight class="mr-1.5 shrink-0 text-xs " />
|
119 |
+
<div class="truncate">Browse Assistants</div>
|
120 |
+
</a>
|
|
|
|
|
|
|
|
|
|
|
121 |
{/if}
|
122 |
|
123 |
<a
|
|
|
125 |
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
|
126 |
{$page.url.pathname === `${base}/settings` ? '!bg-gray-100 !text-gray-800' : ''}"
|
127 |
>
|
128 |
+
<UserIcon class="text-sm" />
|
129 |
Application Settings
|
130 |
</a>
|
131 |
</div>
|
src/routes/settings/+page.svelte
CHANGED
@@ -4,6 +4,7 @@
|
|
4 |
import Modal from "$lib/components/Modal.svelte";
|
5 |
import CarbonClose from "~icons/carbon/close";
|
6 |
import CarbonTrashCan from "~icons/carbon/trash-can";
|
|
|
7 |
|
8 |
import { enhance } from "$app/forms";
|
9 |
import { base } from "$app/paths";
|
@@ -49,12 +50,21 @@
|
|
49 |
</div>
|
50 |
</label>
|
51 |
|
52 |
-
<
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
</div>
|
59 |
|
60 |
{#if isConfirmingDeletion}
|
|
|
4 |
import Modal from "$lib/components/Modal.svelte";
|
5 |
import CarbonClose from "~icons/carbon/close";
|
6 |
import CarbonTrashCan from "~icons/carbon/trash-can";
|
7 |
+
import CarbonArrowUpRight from "~icons/carbon/arrow-up-right";
|
8 |
|
9 |
import { enhance } from "$app/forms";
|
10 |
import { base } from "$app/paths";
|
|
|
50 |
</div>
|
51 |
</label>
|
52 |
|
53 |
+
<div class="mt-12 flex flex-col gap-3">
|
54 |
+
<a
|
55 |
+
href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions"
|
56 |
+
target="_blank"
|
57 |
+
rel="noreferrer"
|
58 |
+
class="flex items-center underline decoration-gray-300 underline-offset-2 hover:decoration-gray-700"
|
59 |
+
><CarbonArrowUpRight class="mr-1.5 shrink-0 text-xs " /> Give your feedback on HuggingChat</a
|
60 |
+
>
|
61 |
+
<button
|
62 |
+
on:click|preventDefault={() => (isConfirmingDeletion = true)}
|
63 |
+
type="submit"
|
64 |
+
class="flex items-center underline decoration-gray-300 underline-offset-2 hover:decoration-gray-700"
|
65 |
+
><CarbonTrashCan class="mr-2 inline text-sm text-red-500" />Delete all conversations</button
|
66 |
+
>
|
67 |
+
</div>
|
68 |
</div>
|
69 |
|
70 |
{#if isConfirmingDeletion}
|
src/routes/settings/assistants/[assistantId]/+page.server.ts
CHANGED
@@ -90,10 +90,15 @@ export const actions: Actions = {
|
|
90 |
return fail(400, { error: true, message: "Already subscribed" });
|
91 |
}
|
92 |
|
93 |
-
await collections.settings.updateOne(authCondition(locals), {
|
94 |
-
$
|
95 |
});
|
96 |
|
|
|
|
|
|
|
|
|
|
|
97 |
return { from: "subscribe", ok: true, message: "Assistant added" };
|
98 |
},
|
99 |
|
@@ -106,10 +111,15 @@ export const actions: Actions = {
|
|
106 |
return fail(404, { error: true, message: "Assistant not found" });
|
107 |
}
|
108 |
|
109 |
-
await collections.settings.updateOne(authCondition(locals), {
|
110 |
$pull: { assistants: assistant._id },
|
111 |
});
|
112 |
|
|
|
|
|
|
|
|
|
|
|
113 |
throw redirect(302, `${base}/settings`);
|
114 |
},
|
115 |
};
|
|
|
90 |
return fail(400, { error: true, message: "Already subscribed" });
|
91 |
}
|
92 |
|
93 |
+
const result = await collections.settings.updateOne(authCondition(locals), {
|
94 |
+
$addToSet: { assistants: assistant._id },
|
95 |
});
|
96 |
|
97 |
+
// reduce count only if push succeeded
|
98 |
+
if (result.modifiedCount > 0) {
|
99 |
+
await collections.assistants.updateOne({ _id: assistant._id }, { $inc: { userCount: 1 } });
|
100 |
+
}
|
101 |
+
|
102 |
return { from: "subscribe", ok: true, message: "Assistant added" };
|
103 |
},
|
104 |
|
|
|
111 |
return fail(404, { error: true, message: "Assistant not found" });
|
112 |
}
|
113 |
|
114 |
+
const result = await collections.settings.updateOne(authCondition(locals), {
|
115 |
$pull: { assistants: assistant._id },
|
116 |
});
|
117 |
|
118 |
+
// reduce count only if pull succeeded
|
119 |
+
if (result.modifiedCount > 0) {
|
120 |
+
await collections.assistants.updateOne({ _id: assistant._id }, { $inc: { userCount: -1 } });
|
121 |
+
}
|
122 |
+
|
123 |
throw redirect(302, `${base}/settings`);
|
124 |
},
|
125 |
};
|
src/routes/settings/assistants/[assistantId]/+page.svelte
CHANGED
@@ -44,18 +44,27 @@
|
|
44 |
{/if}
|
45 |
|
46 |
<div class="flex-1">
|
47 |
-
<
|
48 |
-
|
49 |
-
|
|
|
|
|
|
|
|
|
|
|
50 |
|
51 |
{#if assistant?.description}
|
52 |
-
<p class="mb-
|
53 |
{assistant.description}
|
54 |
</p>
|
55 |
{/if}
|
56 |
|
57 |
<p class="text-sm text-gray-500">
|
58 |
Model: <span class="font-semibold"> {assistant?.modelId} </span>
|
|
|
|
|
|
|
|
|
59 |
</p>
|
60 |
<div
|
61 |
class="flex items-center gap-4 whitespace-nowrap text-sm text-gray-500 hover:*:text-gray-800"
|
@@ -111,18 +120,7 @@
|
|
111 |
<div>
|
112 |
<h2 class="text-lg font-semibold">Direct URL</h2>
|
113 |
|
114 |
-
<p class="pb-2 text-sm text-gray-500">
|
115 |
-
People with this link will be able to use your assistant.
|
116 |
-
{#if !assistant?.createdByMe && assistant?.createdByName}
|
117 |
-
Created by <a
|
118 |
-
class="underline"
|
119 |
-
target="_blank"
|
120 |
-
href={"https://hf.co/" + assistant?.createdByName}
|
121 |
-
>
|
122 |
-
{assistant?.createdByName}
|
123 |
-
</a>
|
124 |
-
{/if}
|
125 |
-
</p>
|
126 |
|
127 |
<div
|
128 |
class="flex flex-row gap-2 rounded-lg border-2 border-gray-200 bg-gray-100 py-2 pl-3 pr-1.5"
|
|
|
44 |
{/if}
|
45 |
|
46 |
<div class="flex-1">
|
47 |
+
<div class="mb-1.5">
|
48 |
+
<h1 class="mr-2 inline text-xl font-semibold">
|
49 |
+
{assistant?.name}
|
50 |
+
</h1>
|
51 |
+
<span class="rounded-full border px-2 py-0.5 text-sm leading-none text-gray-500"
|
52 |
+
>public</span
|
53 |
+
>
|
54 |
+
</div>
|
55 |
|
56 |
{#if assistant?.description}
|
57 |
+
<p class="mb-2 line-clamp-2 text-sm text-gray-500">
|
58 |
{assistant.description}
|
59 |
</p>
|
60 |
{/if}
|
61 |
|
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" target="_blank" href={"https://hf.co/" + assistant?.createdByName}>
|
66 |
+
{assistant?.createdByName}
|
67 |
+
</a>
|
68 |
</p>
|
69 |
<div
|
70 |
class="flex items-center gap-4 whitespace-nowrap text-sm text-gray-500 hover:*:text-gray-800"
|
|
|
120 |
<div>
|
121 |
<h2 class="text-lg font-semibold">Direct URL</h2>
|
122 |
|
123 |
+
<p class="pb-2 text-sm text-gray-500">Share this link for people to use your assistant.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
|
125 |
<div
|
126 |
class="flex flex-row gap-2 rounded-lg border-2 border-gray-200 bg-gray-100 py-2 pl-3 pr-1.5"
|
src/routes/settings/assistants/[assistantId]/edit/+page.server.ts
CHANGED
@@ -112,18 +112,20 @@ export const actions: Actions = {
|
|
112 |
}
|
113 |
}
|
114 |
|
115 |
-
const { acknowledged } = await collections.assistants.
|
116 |
{
|
117 |
_id: assistant._id,
|
118 |
},
|
119 |
{
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
|
|
|
|
127 |
}
|
128 |
);
|
129 |
|
|
|
112 |
}
|
113 |
}
|
114 |
|
115 |
+
const { acknowledged } = await collections.assistants.updateOne(
|
116 |
{
|
117 |
_id: assistant._id,
|
118 |
},
|
119 |
{
|
120 |
+
$set: {
|
121 |
+
name: parse.data.name,
|
122 |
+
description: parse.data.description,
|
123 |
+
modelId: parse.data.modelId,
|
124 |
+
preprompt: parse.data.preprompt,
|
125 |
+
exampleInputs,
|
126 |
+
avatar: deleteAvatar ? undefined : hash ?? assistant.avatar,
|
127 |
+
updatedAt: new Date(),
|
128 |
+
},
|
129 |
}
|
130 |
);
|
131 |
|
src/routes/settings/assistants/new/+page.server.ts
CHANGED
@@ -99,12 +99,13 @@ export const actions: Actions = {
|
|
99 |
avatar: hash,
|
100 |
createdAt: new Date(),
|
101 |
updatedAt: new Date(),
|
|
|
102 |
});
|
103 |
|
104 |
// add insertedId to user settings
|
105 |
|
106 |
await collections.settings.updateOne(authCondition(locals), {
|
107 |
-
$
|
108 |
});
|
109 |
|
110 |
throw redirect(302, `${base}/settings/assistants/${insertedId}`);
|
|
|
99 |
avatar: hash,
|
100 |
createdAt: new Date(),
|
101 |
updatedAt: new Date(),
|
102 |
+
userCount: 1,
|
103 |
});
|
104 |
|
105 |
// add insertedId to user settings
|
106 |
|
107 |
await collections.settings.updateOne(authCondition(locals), {
|
108 |
+
$addToSet: { assistants: insertedId },
|
109 |
});
|
110 |
|
111 |
throw redirect(302, `${base}/settings/assistants/${insertedId}`);
|