nsarrazin HF staff victor HF staff commited on
Commit
786115c
1 Parent(s): a03d289

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 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
- Assistants are public, and can be accessed by anyone with the link.
 
 
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:text-4xl md:h-32 md:w-32 dark:bg-gray-600"
31
  >
32
  {assistant?.name[0]}
33
  </div>
34
  {/if}
35
 
36
- <div class="flex h-full flex-col">
37
  <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"
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 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"
49
  >
50
  {#if data.assistant.avatar}
51
  <img
@@ -55,19 +55,21 @@
55
  />
56
  {:else}
57
  <div
58
- class="flex size-24 flex-none items-center justify-center rounded-full bg-gray-300 font-bold uppercase text-gray-500"
59
  >
60
  {data.assistant.name[0]}
61
  </div>
62
  {/if}
63
- <h1 class="text-2xl font-bold">
64
  {data.assistant.name}
65
  </h1>
66
- <h3 class="text-balance text-sm text-gray-700">
67
- {data.assistant.description}
68
- </h3>
 
 
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 { PUBLIC_APP_ASSETS } from "$env/static/public";
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
- {assistant._id.toString() === $page.params.assistantId ? '!bg-gray-100 !text-gray-800' : ''}"
81
  >
82
  {#if assistant.avatar}
83
  <img
@@ -102,17 +112,12 @@
102
  {/if}
103
  </a>
104
  {/each}
105
-
106
- {#if !data.loginEnabled || (data.loginEnabled && !!data.user)}
107
- <a
108
- href="{base}/settings/assistants/new"
109
- 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
110
- {$page.url.pathname === `${base}/settings/assistants/new` ? '!bg-gray-100 !text-gray-800' : ''}"
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-lg" />
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
- <button
53
- on:click|preventDefault={() => (isConfirmingDeletion = true)}
54
- type="submit"
55
- class="mt-6 flex items-center underline decoration-gray-300 underline-offset-2 hover:decoration-gray-700"
56
- ><CarbonTrashCan class="mr-2 inline text-sm text-red-500" />Delete all conversations</button
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
- $push: { assistants: assistant._id },
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
- <h1 class="text-xl font-semibold">
48
- {assistant?.name}
49
- </h1>
 
 
 
 
 
50
 
51
  {#if assistant?.description}
52
- <p class="mb-1 text-sm text-gray-500">
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.replaceOne(
116
  {
117
  _id: assistant._id,
118
  },
119
  {
120
- createdById: assistant?.createdById,
121
- createdByName: locals.user?.username ?? locals.user?.name,
122
- ...parse.data,
123
- exampleInputs,
124
- avatar: deleteAvatar ? undefined : hash ?? assistant.avatar,
125
- createdAt: new Date(),
126
- updatedAt: new Date(),
 
 
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
- $push: { assistants: insertedId },
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}`);