Spaces:
Running
Running
File size: 10,641 Bytes
786115c 8fe2aef 786115c 9574269 08e8583 150c8c1 10dbbd6 25a7ba5 150c8c1 08e8583 10dbbd6 482a094 3f5871c 09eb258 a4c3fca 786115c 08e8583 10dbbd6 7f7375d a4c3fca 10dbbd6 786115c 08e8583 09eb258 786115c 482a094 09eb258 7f7375d 21c9b41 10dbbd6 09eb258 7f7375d 10dbbd6 a4c3fca 482a094 786115c 8fe2aef 8ffc49e 786115c 1f50904 9574269 1f50904 9574269 786115c 8fe2aef 2128ce0 786115c 8fe2aef 786115c 4538f1d 786115c 08e8583 a4c3fca 08e8583 10dbbd6 08e8583 09eb258 08e8583 69684cd 08e8583 10dbbd6 08e8583 09eb258 08e8583 69684cd 10dbbd6 69684cd 09eb258 10dbbd6 69684cd 08e8583 10dbbd6 5a792a0 10dbbd6 7f7375d 10dbbd6 a4c3fca 9a4b7ac a4c3fca 08e8583 3f5871c 6887755 3f5871c 482a094 150c8c1 482a094 786115c 150c8c1 69684cd 150c8c1 3f5871c 786115c 91ec91f 786115c 8fe2aef 786115c 8fe2aef 786115c 08e8583 786115c 482a094 786115c 25a7ba5 786115c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
<script lang="ts">
import type { PageData } from "./$types";
import { PUBLIC_APP_ASSETS, PUBLIC_ORIGIN } from "$env/static/public";
import { isHuggingChat } from "$lib/utils/isHuggingChat";
import { goto } from "$app/navigation";
import { base } from "$app/paths";
import { page } from "$app/stores";
import CarbonAdd from "~icons/carbon/add";
import CarbonHelpFilled from "~icons/carbon/help-filled";
import CarbonClose from "~icons/carbon/close";
import CarbonArrowUpRight from "~icons/carbon/arrow-up-right";
import CarbonEarthAmerica from "~icons/carbon/earth-americas-filled";
import CarbonUserMultiple from "~icons/carbon/user-multiple";
import CarbonSearch from "~icons/carbon/search";
import Pagination from "$lib/components/Pagination.svelte";
import { formatUserCount } from "$lib/utils/formatUserCount";
import { getHref } from "$lib/utils/getHref";
import { debounce } from "$lib/utils/debounce";
import { useSettingsStore } from "$lib/stores/settings";
import IconInternet from "$lib/components/icons/IconInternet.svelte";
import { isDesktop } from "$lib/utils/isDesktop";
import { SortKey } from "$lib/types/Assistant";
export let data: PageData;
$: assistantsCreator = $page.url.searchParams.get("user");
$: createdByMe = data.user?.username && data.user.username === assistantsCreator;
const SEARCH_DEBOUNCE_DELAY = 400;
let filterInputEl: HTMLInputElement;
let filterValue = data.query;
let isFilterInPorgress = false;
let sortValue = data.sort as SortKey;
const onModelChange = (e: Event) => {
const newUrl = getHref($page.url, {
newKeys: { modelId: (e.target as HTMLSelectElement).value },
existingKeys: { behaviour: "delete_except", keys: ["user"] },
});
resetFilter();
goto(newUrl);
};
const resetFilter = () => {
filterValue = "";
isFilterInPorgress = false;
};
const filterOnName = debounce(async (value: string) => {
filterValue = value;
if (isFilterInPorgress) {
return;
}
isFilterInPorgress = true;
const newUrl = getHref($page.url, {
newKeys: { q: value },
existingKeys: { behaviour: "delete", keys: ["p"] },
});
await goto(newUrl);
if (isDesktop(window)) {
setTimeout(() => filterInputEl.focus(), 0);
}
isFilterInPorgress = false;
// there was a new filter query before server returned response
if (filterValue !== value) {
filterOnName(filterValue);
}
}, SEARCH_DEBOUNCE_DELAY);
const sortAssistants = () => {
const newUrl = getHref($page.url, {
newKeys: { sort: sortValue },
existingKeys: { behaviour: "delete", keys: ["p"] },
});
goto(newUrl);
};
const settings = useSettingsStore();
</script>
<svelte:head>
{#if isHuggingChat}
<title>HuggingChat - Assistants</title>
<meta property="og:title" content="HuggingChat - Assistants" />
<meta property="og:type" content="link" />
<meta
property="og:description"
content="Browse HuggingChat assistants made by the community."
/>
<meta
property="og:image"
content="{PUBLIC_ORIGIN ||
$page.url.origin}{base}/{PUBLIC_APP_ASSETS}/assistants-thumbnail.png"
/>
<meta property="og:url" content={$page.url.href} />
{/if}
</svelte:head>
<div class="scrollbar-custom mr-1 h-full overflow-y-auto py-12 max-sm:pt-8 md:py-24">
<div class="pt-42 mx-auto flex flex-col px-5 xl:w-[60rem] 2xl:w-[64rem]">
<div class="flex items-center">
<h1 class="text-2xl font-bold">Assistants</h1>
{#if isHuggingChat}
<div class="5 ml-1.5 rounded-lg text-xxs uppercase text-gray-500 dark:text-gray-500">
beta
</div>
<a
href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions/357"
class="ml-auto dark:text-gray-400 dark:hover:text-gray-300"
target="_blank"
>
<CarbonHelpFilled />
</a>
{/if}
</div>
<h3 class="text-gray-500">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 h-[34px] rounded-lg border border-gray-300 bg-gray-50 px-2 text-sm 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={data.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 shadow-sm hover:bg-gray-50 hover:shadow-none dark:border-gray-600 dark:bg-gray-700 dark:hover:bg-gray-700"
>
<CarbonAdd />Create new assistant
</a>
</div>
<div class="mt-7 flex flex-wrap items-center gap-x-2 gap-y-3 text-sm">
{#if assistantsCreator && !createdByMe}
<div
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"
>
{assistantsCreator}'s Assistants
<a
href={getHref($page.url, {
existingKeys: { behaviour: "delete", keys: ["user", "modelId", "p", "q"] },
})}
on:click={resetFilter}
class="group"
><CarbonClose
class="text-xs group-hover:text-gray-800 dark:group-hover:text-gray-300"
/></a
>
</div>
{#if isHuggingChat}
<a
href="https://hf.co/{assistantsCreator}"
target="_blank"
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"
><CarbonArrowUpRight class="mr-1 flex-none text-[0.58rem]" target="_blank" />View {assistantsCreator}
on HF</a
>
{/if}
{:else}
<a
href={getHref($page.url, {
existingKeys: { behaviour: "delete", keys: ["user", "modelId", "p", "q"] },
})}
on:click={resetFilter}
class="flex items-center gap-1.5 rounded-full border px-3 py-1 {!assistantsCreator
? 'border-gray-300 bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white'
: 'border-transparent text-gray-400 hover:text-gray-800 dark:hover:text-gray-300'}"
>
<CarbonEarthAmerica class="text-xs" />
Community
</a>
{#if data.user?.username}
<a
href={getHref($page.url, {
newKeys: { user: data.user.username },
existingKeys: { behaviour: "delete", keys: ["modelId", "p", "q"] },
})}
on:click={resetFilter}
class="flex items-center gap-1.5 truncate rounded-full border px-3 py-1 {assistantsCreator &&
createdByMe
? 'border-gray-300 bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white'
: 'border-transparent text-gray-400 hover:text-gray-800 dark:hover:text-gray-300'}"
>{data.user.username}
</a>
{/if}
{/if}
<div
class="relative ml-auto flex h-[30px] w-40 items-center rounded-full border px-2 has-[:focus]:border-gray-400 sm:w-64 dark:border-gray-600"
>
<CarbonSearch class="pointer-events-none absolute left-2 text-xs text-gray-400" />
<input
class="h-[30px] w-full bg-transparent pl-5 focus:outline-none"
placeholder="Filter by name"
value={filterValue}
on:input={(e) => filterOnName(e.currentTarget.value)}
bind:this={filterInputEl}
maxlength="150"
type="search"
/>
</div>
<select
bind:value={sortValue}
on:change={sortAssistants}
class="rounded-lg border border-gray-300 bg-gray-50 px-2 py-1 text-sm 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"
>
<option value={SortKey.POPULAR}>{SortKey.POPULAR}</option>
<option value={SortKey.TRENDING}>{SortKey.TRENDING}</option>
</select>
</div>
<div class="mt-8 grid grid-cols-2 gap-3 sm:gap-5 md:grid-cols-3 lg:grid-cols-4">
{#each data.assistants as assistant (assistant._id)}
{@const hasRag =
assistant?.rag?.allowAllDomains ||
!!assistant?.rag?.allowedDomains?.length ||
!!assistant?.rag?.allowedLinks?.length ||
!!assistant?.dynamicPrompt}
<button
class="relative 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 xl:pt-8 dark:border-gray-800/70 dark:bg-gray-950/20 dark:hover:bg-gray-950/40"
on:click={() => {
if (data.settings.assistants.includes(assistant._id.toString())) {
settings.instantSet({ activeModel: assistant._id.toString() });
goto(`${base}` || "/");
} else {
goto(`${base}/assistant/${assistant._id}`);
}
}}
>
{#if assistant.userCount && assistant.userCount > 1}
<div
class="absolute right-3 top-3 flex items-center gap-1 text-xs text-gray-400"
title="Number of users"
>
<CarbonUserMultiple class="text-xxs" />{formatUserCount(assistant.userCount)}
</div>
{/if}
{#if hasRag}
<div
class="absolute left-3 top-3 grid size-5 place-items-center rounded-full bg-blue-500/10"
title="This assistant uses the websearch."
>
<IconInternet classNames="text-sm text-blue-600" />
</div>
{/if}
{#if assistant.avatar}
<img
src="{base}/settings/assistants/{assistant._id}/avatar.jpg"
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-xs text-gray-700 sm:line-clamp-2 dark:text-gray-400">
{assistant.description}
</p>
{#if assistant.createdByName}
<p class="mt-auto pt-2 text-xs text-gray-400 dark:text-gray-500">
Created by <a
class="hover:underline"
href="{base}/assistants?user={assistant.createdByName}"
>
{assistant.createdByName}
</a>
</p>
{/if}
</button>
{:else}
No assistants found
{/each}
</div>
<Pagination
classNames="w-full flex justify-center mt-14 mb-4"
numItemsPerPage={data.numItemsPerPage}
numTotalItems={data.numTotalItems}
/>
</div>
</div>
|