Spaces:
Sleeping
Sleeping
File size: 6,610 Bytes
fc69895 |
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 |
<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>
|