ui : show reasoning and modality icons on model options and search by modality

This commit is contained in:
Aleksander Grygier
2026-08-15 22:53:18 +02:00
committed by GitHub
parent 06f8d7ae96
commit a43c1c0cde
4 changed files with 111 additions and 28 deletions
@@ -1,5 +1,5 @@
<script lang="ts">
import { Eye, Mic, Video } from '@lucide/svelte';
import { Image, Mic, Video } from '@lucide/svelte';
import { ModelModality } from '$lib/enums';
interface Props {
@@ -19,7 +19,7 @@
]}
>
{#if modality === ModelModality.VISION}
<Eye class="h-3 w-3" />
<Image class="h-3 w-3" />
Vision (Image)
{:else if modality === ModelModality.VIDEO}
@@ -1,7 +1,10 @@
<script lang="ts">
import { TruncatedText } from '$lib/components/app';
import { Image, Lightbulb, Mic, Video } from '@lucide/svelte';
import * as Tooltip from '$lib/components/ui/tooltip';
import { ModelsService } from '$lib/services/models.service';
import { settingsStore } from '$lib/stores';
import type { ModelModalities } from '$lib/types/models';
interface Props {
modelId: string;
@@ -11,6 +14,8 @@
hideTags?: boolean;
aliases?: string[];
tags?: string[];
modalities?: ModelModalities;
supportsThinking?: boolean;
class?: string;
}
@@ -20,8 +25,10 @@
hideOrgName = false,
hideQuantization,
hideTags,
modalities,
modelId,
showRaw = undefined,
supportsThinking = false,
tags,
...rest
}: Props = $props();
@@ -42,6 +49,9 @@
let uniqueAliases = $derived([...new Set(aliases ?? [])]);
let uniqueTags = $derived([...new Set([...(parsed.tags ?? []), ...(tags ?? [])])]);
let hasModalityIcons = $derived(
supportsThinking || modalities?.vision || modalities?.video || modalities?.audio
);
let primaryAlias = $derived(uniqueAliases.length === 1 ? uniqueAliases[0] : null);
let displayName = $derived(primaryAlias ?? parsed.modelName ?? modelId);
@@ -50,37 +60,87 @@
{#if resolvedShowRaw}
<TruncatedText class="font-medium {className}" showTooltip={false} text={modelId} {...rest} />
{:else}
<span class="flex min-w-0 flex-wrap items-center gap-1 {className}" {...rest}>
<span class="flex min-w-0 items-center gap-1.5 {className}" {...rest}>
<span class="min-w-0 truncate font-medium">
{#if !hideOrgName && parsed.orgName}{parsed.orgName}/{/if}{displayName}
</span>
{#if parsed.params}
<span class={badgeClass}>
{parsed.params}{parsed.activatedParams ? `-${parsed.activatedParams}` : ''}
</span>
{/if}
{#if parsed.quantization && !resolvedHideQuantization}
<span class={badgeClass}>
{parsed.quantization}
</span>
{/if}
{#if primaryAlias}
{#if primaryAlias !== parsed.modelName}
<span class={badgeClass}>{parsed.modelName ?? modelId}</span>
<span class="inline-flex items-center gap-1">
{#if parsed.params}
<span class={badgeClass}>
{parsed.params}{parsed.activatedParams ? `-${parsed.activatedParams}` : ''}
</span>
{/if}
{:else if uniqueAliases.length > 1}
{#each uniqueAliases as alias (alias)}
<span class={badgeClass}>{alias}</span>
{/each}
{/if}
{#if uniqueTags.length > 0 && !resolvedHideTags}
{#each uniqueTags as tag (tag)}
<span class={tagBadgeClass}>{tag}</span>
{/each}
{#if parsed.quantization && !resolvedHideQuantization}
<span class={badgeClass}>
{parsed.quantization}
</span>
{/if}
{#if primaryAlias}
{#if primaryAlias !== parsed.modelName}
<span class={badgeClass}>{parsed.modelName ?? modelId}</span>
{/if}
{:else if uniqueAliases.length > 1}
{#each uniqueAliases as alias (alias)}
<span class={badgeClass}>{alias}</span>
{/each}
{/if}
{#if uniqueTags.length > 0 && !resolvedHideTags}
{#each uniqueTags as tag (tag)}
<span class={tagBadgeClass}>{tag}</span>
{/each}
{/if}
</span>
{#if hasModalityIcons}
<span class="inline-flex items-center gap-1.25 text-muted-foreground">
{#if supportsThinking}
<Tooltip.Root>
<Tooltip.Trigger>
<Lightbulb class="h-3 w-3 text-muted-foreground" />
</Tooltip.Trigger>
<Tooltip.Content>
<p>Reasoning</p>
</Tooltip.Content>
</Tooltip.Root>
{/if}
{#if modalities?.vision}
<Tooltip.Root>
<Tooltip.Trigger>
<Image class="h-3 w-3 text-muted-foreground" />
</Tooltip.Trigger>
<Tooltip.Content>
<p>Vision</p>
</Tooltip.Content>
</Tooltip.Root>
{/if}
{#if modalities?.video}
<Tooltip.Root>
<Tooltip.Trigger>
<Video class="h-3 w-3 text-muted-foreground" />
</Tooltip.Trigger>
<Tooltip.Content>
<p>Video</p>
</Tooltip.Content>
</Tooltip.Root>
{/if}
{#if modalities?.audio}
<Tooltip.Root>
<Tooltip.Trigger>
<Mic class="h-3 w-3 text-muted-foreground" />
</Tooltip.Trigger>
<Tooltip.Content>
<p>Audio</p>
</Tooltip.Content>
</Tooltip.Root>
{/if}
</span>
{/if}
</span>
{/if}
@@ -58,6 +58,8 @@
let loadProgress = $derived(isLoading ? modelsStore.status.getLoadProgress(option.model) : null);
let loadPercent = $derived(Math.round(modelLoadFraction(loadProgress) * 100));
let loadTitle = $derived(modelLoadProgressText(loadProgress));
let modalities = $derived(option.modalities);
let supportsThinking = $derived(modelsStore.props.checkModelSupportsThinking(option.model));
</script>
<div
@@ -82,6 +84,8 @@
{hideOrgName}
aliases={option.aliases}
tags={option.tags}
modalities={modalities}
{supportsThinking}
class="flex-1"
/>
@@ -1,4 +1,5 @@
import type { ModelOption } from '$lib/types/models';
import { ModelModality } from '$lib/enums';
import { SvelteMap } from 'svelte/reactivity';
export interface ModelItem {
@@ -17,6 +18,23 @@ export interface GroupedModelOptions {
available: OrgGroup[];
}
function matchesModality(option: ModelOption, term: string): boolean {
const modalities = option.modalities;
if (!modalities) return false;
switch (term) {
case ModelModality.VISION.toLowerCase():
return modalities.vision;
case ModelModality.AUDIO.toLowerCase():
return modalities.audio;
case ModelModality.VIDEO.toLowerCase():
return modalities.video;
default:
return false;
}
}
export function filterModelOptions(options: ModelOption[], searchTerm: string): ModelOption[] {
const term = searchTerm.trim().toLowerCase();
@@ -27,7 +45,8 @@ export function filterModelOptions(options: ModelOption[], searchTerm: string):
option.model.toLowerCase().includes(term) ||
option.name?.toLowerCase().includes(term) ||
option.aliases?.some((alias: string) => alias.toLowerCase().includes(term)) ||
option.tags?.some((tag: string) => tag.toLowerCase().includes(term))
option.tags?.some((tag: string) => tag.toLowerCase().includes(term)) ||
matchesModality(option, term)
);
}