Spaces:
Sleeping
Sleeping
| <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 CarbonView from "~icons/carbon/view"; | |
| import CarbonSettings from "~icons/carbon/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(); | |
| // Local filter state for model search (hyphen/space insensitive) | |
| let modelFilter = $state(""); | |
| const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, " "); | |
| let queryTokens = $derived(normalize(modelFilter).trim().split(/\s+/).filter(Boolean)); | |
| </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-2xl font-bold">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> | |
| <!-- Filter input --> | |
| <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 grid grid-cols-1 gap-3 sm:gap-5 xl:grid-cols-2"> | |
| {#each data.models | |
| .filter((el) => !el.unlisted) | |
| .filter((el) => { | |
| const haystack = normalize(`${el.id} ${el.name ?? ""} ${el.displayName ?? ""}`); | |
| return queryTokens.every((q) => haystack.includes(q)); | |
| }) as model, index (model.id)} | |
| <a | |
| href="{base}/models/{model.id}" | |
| aria-label="Model card" | |
| class="relative flex flex-col gap-2 overflow-hidden rounded-xl border bg-gray-50/50 px-6 py-5 shadow hover:bg-gray-50 hover:shadow-inner dark:border-gray-800/70 dark:bg-gray-950/20 dark:hover:bg-gray-950/40" | |
| class:omni-gradient={model.isRouter} | |
| class:active-model={model.id === $settings.activeModel} | |
| > | |
| <div class="flex items-center justify-between gap-1"> | |
| {#if model.logoUrl} | |
| <img | |
| class="aspect-square size-6 rounded border bg-white dark:border-gray-700" | |
| src={model.logoUrl} | |
| alt="" | |
| /> | |
| {:else} | |
| <div | |
| class="size-6 rounded border border-transparent bg-gray-300 dark:bg-gray-800" | |
| aria-hidden="true" | |
| ></div> | |
| {/if} | |
| <div class="flex items-center gap-1"> | |
| {#if $settings.multimodalOverrides?.[model.id] ?? model.multimodal} | |
| <span | |
| title="This model is multimodal and supports image inputs natively." | |
| class="ml-auto flex size-[21px] items-center justify-center rounded-lg border border-blue-700 dark:border-blue-500" | |
| aria-label="Model is multimodal" | |
| role="img" | |
| > | |
| <CarbonView class="text-xxs text-blue-700 dark:text-blue-500" /> | |
| </span> | |
| {/if} | |
| <button | |
| type="button" | |
| title="Model settings" | |
| aria-label="Model settings for {model.displayName}" | |
| class="flex size-[21px] items-center justify-center rounded-md border border-gray-300 text-xs text-gray-600 hover:bg-gray-100 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700" | |
| onclick={(e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| goto(`${base}/settings/${model.id}`); | |
| }} | |
| > | |
| <CarbonSettings class="text-xs" /> | |
| </button> | |
| {#if model.id === $settings.activeModel} | |
| <span | |
| class="rounded-full bg-black px-2 py-0.5 text-xs text-white dark:bg-white dark:text-black" | |
| > | |
| Active | |
| </span> | |
| {:else if index === 0 && model.id === "omni"} | |
| <span | |
| class="rounded-full border border-gray-300 px-2 py-0.5 text-xs text-gray-500 dark:border-gray-500 dark:text-gray-400" | |
| > | |
| Default | |
| </span> | |
| {/if} | |
| </div> | |
| </div> | |
| <span class="flex items-center gap-2 font-semibold"> | |
| {model.displayName} | |
| </span> | |
| <span class="line-clamp-4 whitespace-pre-wrap text-sm text-gray-500 dark:text-gray-400"> | |
| {model.isRouter ? "Routes your messages to the best model for your request." : model.description || "-"} | |
| </span> | |
| </a> | |
| {/each} | |
| </div> | |
| </div> | |
| </div> | |
| <style> | |
| /* Subtle highlight for the router (Omni) tile */ | |
| .omni-gradient { | |
| /* layered gradients to keep readable on both themes */ | |
| background-image: | |
| radial-gradient(900px 300px at -10% -20%, rgba(59, 130, 246, 0.16), transparent 60%), | |
| radial-gradient(700px 240px at 110% 120%, rgba(16, 185, 129, 0.16), transparent 60%), | |
| linear-gradient(135deg, rgba(236, 72, 153, 0.10), rgba(59, 130, 246, 0.08)); | |
| box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 6px 18px rgba(59, 130, 246, 0.12), 0 2px 8px rgba(236, 72, 153, 0.10); | |
| } | |
| :global(.dark) .omni-gradient { | |
| background-image: | |
| radial-gradient(900px 300px at -10% -20%, rgba(59, 130, 246, 0.12), transparent 60%), | |
| radial-gradient(700px 240px at 110% 120%, rgba(16, 185, 129, 0.12), transparent 60%), | |
| linear-gradient(135deg, rgba(236, 72, 153, 0.08), rgba(59, 130, 246, 0.06)); | |
| box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 10px 28px rgba(0, 0, 0, 0.25); | |
| } | |
| /* Active border handled via Tailwind utilities (see .active-model in src/styles/main.css) */ | |
| </style> | |