| <script lang="ts"> |
| import type { PageData } from "./$types"; |
| import { usePublicConfig } from "$lib/utils/PublicConfig.svelte"; |
| |
| import { base } from "$app/paths"; |
| import { page } from "$app/state"; |
| |
| import CarbonHelpFilled from "~icons/carbon/help-filled"; |
| import LucideHammer from "~icons/lucide/hammer"; |
| import LucideImage from "~icons/lucide/image"; |
| import LucideSettings from "~icons/lucide/settings"; |
| import { useSettingsStore } from "$lib/stores/settings"; |
| import { goto } from "$app/navigation"; |
| interface Props { |
| data: PageData; |
| } |
| |
| let { data }: Props = $props(); |
| |
| const settings = useSettingsStore(); |
| |
| const publicConfig = usePublicConfig(); |
| |
| |
| let modelFilter = $state(""); |
| const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, " "); |
| let queryTokens = $derived(normalize(modelFilter).trim().split(/\s+/).filter(Boolean)); |
| |
| |
| let filteredModels = $derived( |
| data.models |
| .filter((el) => !el.unlisted) |
| .filter((el) => { |
| const haystack = normalize(`${el.id} ${el.name ?? ""} ${el.displayName ?? ""}`); |
| return queryTokens.every((q) => haystack.includes(q)); |
| }) |
| ); |
| </script> |
|
|
| <svelte:head> |
| {#if publicConfig.isHuggingChat} |
| <title>HuggingChat - Models</title> |
| <meta property="og:title" content="HuggingChat - Models" /> |
| <meta property="og:type" content="link" /> |
| <meta property="og:description" content="Browse HuggingChat available models" /> |
| <meta property="og:url" content={page.url.href} /> |
| {/if} |
| </svelte:head> |
|
|
| <div class="scrollbar-custom 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-xl font-bold sm:text-2xl">Models</h1> |
| {#if publicConfig.isHuggingChat} |
| <a |
| href="https://huggingface.co/docs/inference-providers" |
| class="ml-auto text-gray-500 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-300" |
| target="_blank" |
| aria-label="Hub discussion about models" |
| > |
| <CarbonHelpFilled /> |
| </a> |
| {/if} |
| </div> |
| <h2 class="text-gray-500"> |
| All models available{#if publicConfig.isHuggingChat} via <a |
| target="_blank" |
| href="https://huggingface.co/inference/models" |
| class="underline decoration-gray-300 hover:decoration-gray-500 dark:decoration-gray-600 dark:hover:decoration-gray-500" |
| >Inference Providers</a |
| >{/if} |
| </h2> |
|
|
| |
| <input |
| type="search" |
| bind:value={modelFilter} |
| placeholder="Search by name" |
| aria-label="Search models by name or id" |
| class="mt-4 w-full rounded-3xl border border-gray-300 bg-white px-5 py-2 text-[15px] |
| placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-300 |
| dark:border-gray-700 dark:bg-gray-900 dark:focus:ring-gray-700" |
| /> |
|
|
| <div class="mt-6 min-h-[50vh]"> |
| <div |
| class="overflow-hidden rounded-2xl border border-gray-200/60 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900" |
| > |
| {#each filteredModels as model, index (model.id)} |
| {@const isActive = model.id === $settings.activeModel} |
| {@const isLast = index === filteredModels.length - 1} |
| <a |
| href="{base}/models/{model.id}" |
| aria-label="Model card for {model.displayName}" |
| class="group flex cursor-pointer items-center gap-2 p-3 sm:gap-4 sm:p-4 |
| {isActive |
| ? 'bg-gray-50 dark:bg-gray-800' |
| : 'bg-white hover:bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800'} |
| {isLast ? '' : 'border-b border-gray-100 dark:border-gray-800'}" |
| > |
| |
| <div class="flex-shrink-0"> |
| {#if model.logoUrl} |
| <img |
| alt={model.displayName} |
| class="size-8 rounded-lg border border-gray-100 bg-gray-50 object-cover dark:border-gray-700 dark:bg-gray-100 sm:size-10" |
| src={model.logoUrl} |
| /> |
| {:else} |
| <div |
| class="h-10 w-10 rounded-lg border border-gray-100 bg-gray-200 dark:border-gray-700 dark:bg-gray-700" |
| aria-hidden="true" |
| ></div> |
| {/if} |
| </div> |
|
|
| |
| <div class="min-w-0 flex-1"> |
| <div class="flex items-center gap-2"> |
| <h3 |
| class="truncate font-medium text-gray-900 dark:text-gray-200 max-sm:text-xs" |
| class:font-bold={isActive} |
| class:dark:text-white={isActive} |
| > |
| {model.displayName} |
| </h3> |
| {#if index === 0 && model.isRouter && !isActive} |
| <span |
| class="rounded border border-gray-200 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-gray-500 dark:border-gray-700 dark:text-gray-400" |
| > |
| Default |
| </span> |
| {/if} |
| </div> |
| <p class="truncate pr-4 text-xs text-gray-500 dark:text-gray-400 sm:text-[13px]"> |
| {model.isRouter |
| ? "Routes your messages to the best model for your request." |
| : model.description || "-"} |
| </p> |
| </div> |
|
|
| |
| <div class="flex flex-shrink-0 items-center gap-1.5"> |
| {#if $settings.toolsOverrides?.[model.id] ?? (model as { supportsTools?: boolean }).supportsTools} |
| <div |
| title="This model supports tool calling (functions)." |
| class="rounded-md bg-purple-50 p-1.5 text-purple-600 dark:bg-purple-900/20 dark:text-purple-400" |
| > |
| <LucideHammer class="size-3 sm:size-3.5" /> |
| </div> |
| {/if} |
| {#if $settings.multimodalOverrides?.[model.id] ?? model.multimodal} |
| <div |
| title="This model is multimodal and supports image inputs natively." |
| class="rounded-md bg-blue-50 p-1.5 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400" |
| > |
| <LucideImage class="size-3 sm:size-3.5" /> |
| </div> |
| {/if} |
| <button |
| type="button" |
| title="Model settings" |
| aria-label="Model settings for {model.displayName}" |
| class="rounded-md border border-gray-200 p-1.5 text-gray-500 hover:bg-gray-100 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700" |
| onclick={(e) => { |
| e.preventDefault(); |
| e.stopPropagation(); |
| goto(`${base}/settings/${model.id}`); |
| }} |
| > |
| <LucideSettings class="size-3 sm:size-3.5" /> |
| </button> |
| {#if isActive} |
| <span |
| class="rounded-full bg-black px-2.5 py-1 text-xs font-bold text-white shadow-md dark:bg-white dark:text-black" |
| > |
| Active |
| </span> |
| {/if} |
| </div> |
| </a> |
| {/each} |
| </div> |
| </div> |
| </div> |
| </div> |
|
|