Andrew
fix(ui): fix type errors in model settings page
3c1958b
<script lang="ts">
import { page } from "$app/state";
import { base } from "$app/paths";
import type { BackendModel } from "$lib/server/models";
import IconOmni from "$lib/components/icons/IconOmni.svelte";
import { useSettingsStore } from "$lib/stores/settings";
import CopyToClipBoardBtn from "$lib/components/CopyToClipBoardBtn.svelte";
import CarbonArrowUpRight from "~icons/carbon/arrow-up-right";
import CarbonCopy from "~icons/carbon/copy";
import CarbonChat from "~icons/carbon/chat";
import CarbonCode from "~icons/carbon/code";
import { goto } from "$app/navigation";
import { usePublicConfig } from "$lib/utils/PublicConfig.svelte";
import Switch from "$lib/components/Switch.svelte";
const publicConfig = usePublicConfig();
const settings = useSettingsStore();
type RouterProvider = { provider: string } & Record<string, unknown>;
let model = $derived(page.data.models.find((el: BackendModel) => el.id === page.params.model));
let providerList: RouterProvider[] = $derived((model?.providers ?? []) as RouterProvider[]);
// Initialize multimodal override for this model if not set yet
$effect(() => {
const modelId = page.params.model;
if (modelId) {
if (!$settings.multimodalOverrides) {
$settings.multimodalOverrides = {};
}
if ($settings.multimodalOverrides[modelId] === undefined && model) {
// Default to the model's advertised capability
$settings.multimodalOverrides = {
...$settings.multimodalOverrides,
[modelId]: !!model.multimodal,
};
}
}
});
// Ensure hidePromptExamples has an entry for this model so the switch can bind safely
$effect(() => {
const modelId = page.params.model;
if (modelId) {
if (!$settings.hidePromptExamples) {
$settings.hidePromptExamples = {};
}
if ($settings.hidePromptExamples[modelId] === undefined) {
$settings.hidePromptExamples = {
...$settings.hidePromptExamples,
[modelId]: false,
};
}
}
});
</script>
<div class="flex flex-col items-start">
<div class="mb-4 flex flex-col gap-0.5">
<h2 class="text-base font-semibold md:text-lg">
{model.displayName}
</h2>
{#if model.description}
<p class="line-clamp-2 whitespace-pre-wrap text-sm text-gray-600 dark:text-gray-400">
{model.description}
</p>
{/if}
</div>
<!-- Actions -->
<div class="mb-4 flex flex-wrap items-center gap-1.5">
<button
class="flex w-fit items-center rounded-full bg-black px-3 py-1.5 text-sm !text-white shadow-sm hover:bg-black/90 dark:bg-white/80 dark:!text-gray-900 dark:hover:bg-white/90"
name="Activate model"
onclick={(e) => {
e.stopPropagation();
settings.instantSet({
activeModel: page.params.model,
});
goto(`${base}/`);
}}
>
<CarbonChat class="mr-1.5 text-sm" />
New chat
</button>
{#if model.modelUrl}
<a
href={model.modelUrl || "https://huggingface.co/" + model.name}
target="_blank"
rel="noreferrer"
class="inline-flex items-center rounded-full border border-gray-200 px-2.5 py-1 text-sm hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700/60"
>
<CarbonArrowUpRight class="mr-1.5 shrink-0 text-xs " />
Model page
</a>
{/if}
{#if model.datasetName || model.datasetUrl}
<a
href={model.datasetUrl || "https://huggingface.co/datasets/" + model.datasetName}
target="_blank"
rel="noreferrer"
class="inline-flex items-center rounded-full border border-gray-200 px-2.5 py-1 text-sm hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700/60"
>
<CarbonArrowUpRight class="mr-1.5 shrink-0 text-xs " />
Dataset page
</a>
{/if}
{#if model.websiteUrl}
<a
href={model.websiteUrl}
target="_blank"
class="inline-flex items-center rounded-full border border-gray-200 px-2.5 py-1 text-sm hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700/60"
rel="noreferrer"
>
<CarbonArrowUpRight class="mr-1.5 shrink-0 text-xs " />
Model website
</a>
{/if}
{#if publicConfig.isHuggingChat}
{#if !model?.isRouter}
<a
href={"https://huggingface.co/playground?modelId=" + model.name}
target="_blank"
rel="noreferrer"
class="inline-flex items-center rounded-full border border-gray-200 px-2.5 py-1 text-sm hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700/60"
>
<CarbonCode class="mr-1.5 shrink-0 text-xs" />
API Playground
</a>
<a
href={"https://huggingface.co/" + model.name}
target="_blank"
rel="noreferrer"
class="inline-flex items-center rounded-full border border-gray-200 px-2.5 py-1 text-sm hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700/60"
>
<CarbonArrowUpRight class="mr-1.5 shrink-0 text-xs" />
View model card
</a>
{/if}
<CopyToClipBoardBtn
value="{publicConfig.PUBLIC_ORIGIN || page.url.origin}{base}/models/{model.id}"
classNames="inline-flex items-center rounded-full border border-gray-200 px-2.5 py-1 text-sm hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700/60"
>
<div class="flex items-center gap-1.5">
<CarbonCopy class="shrink-0 text-xs" />Copy new chat link
</div>
</CopyToClipBoardBtn>
{/if}
</div>
<div class="relative flex w-full flex-col gap-2">
{#if model?.isRouter}
<p class="mb-3 mt-2 rounded-lg bg-gray-100 px-3 py-2 text-sm dark:bg-white/5">
<IconOmni classNames="-translate-y-px" /> Omni routes your messages to the best underlying model
depending on your request.
</p>
{/if}
<!-- Capabilities -->
<div
class="mt-3 rounded-xl border border-gray-200 bg-white px-3 shadow-sm dark:border-gray-700 dark:bg-gray-800"
>
<div class="divide-y divide-gray-200 dark:divide-gray-700">
<div class="flex items-start justify-between py-3">
<div>
<div class="text-[13px] font-medium text-gray-800 dark:text-gray-200">
Supports image inputs (multimodal)
</div>
<p class="text-[12px] text-gray-500 dark:text-gray-400">
Enable image uploads and send images to this model.
</p>
</div>
<Switch
name="forceMultimodal"
bind:checked={$settings.multimodalOverrides[page.params.model ?? ""]}
/>
</div>
{#if model?.isRouter}
<div class="flex items-start justify-between py-3">
<div>
<div class="text-[13px] font-medium text-gray-800 dark:text-gray-200">
Hide prompt examples
</div>
<p class="text-[12px] text-gray-500 dark:text-gray-400">
Hide the prompt suggestions above the chat input.
</p>
</div>
<Switch
name="hidePromptExamples"
bind:checked={$settings.hidePromptExamples[page.params.model ?? ""]}
/>
</div>
{/if}
</div>
</div>
{#if model.providers?.length}
<div
class="mt-3 flex flex-col gap-2.5 rounded-xl border border-gray-200 bg-white px-3 py-2 shadow-sm dark:border-gray-700 dark:bg-gray-800"
>
<div>
<div class="text-[13px] font-medium text-gray-800 dark:text-gray-200">
Providers serving this model
</div>
<p class="text-[12px] text-gray-500 dark:text-gray-400">
Your requests for this model will be routed through one of the available providers.
</p>
</div>
<ul class="mb-0.5 flex flex-wrap gap-2">
{#each providerList as prov, i (prov.provider || i)}
<li>
<span
class="flex items-center gap-1 rounded-md bg-gray-100 py-0.5 pl-1.5 pr-2 text-xs text-gray-700 dark:bg-gray-700/60 dark:text-gray-200"
>
{#if prov.provider}
<img
class="h-[14px] w-auto"
src={`${base}/huggingchat/providers/${prov.provider}.svg`}
alt="{prov.provider} logo"
/>
{/if}
{prov.provider}
</span>
</li>
{/each}
</ul>
</div>
{/if}
<!-- Tokenizer-based token counting disabled in this build -->
</div>
</div>