fix(web): show full model names in selectors when truncated

Selector dropdowns previously reserved 50% of the row for the right-hand
metadata, squeezing long model names beyond visibility, and triggers
truncated their labels without a tooltip. Restructure the model option
into a two-row layout (name + capabilities/context badge above, model_id
below), and add native title tooltips on every truncated label across
the shared SearchableSelectPopover and the model/TTS/search/memory/
browser-context selectors so the full name is always reachable.
This commit is contained in:
Acbox
2026-04-26 15:39:13 +08:00
parent 6b931d9139
commit 73ab5dfd90
8 changed files with 86 additions and 50 deletions
@@ -15,7 +15,10 @@
:aria-label="ariaLabel || placeholder"
class="w-full justify-between font-normal"
>
<span class="truncate">
<span
class="truncate"
:title="displayLabel || placeholder"
>
{{ displayLabel || placeholder }}
</span>
<Search
@@ -80,33 +83,39 @@
>
<Check
v-if="selected === option.value"
class="size-3.5"
class="size-3.5 shrink-0"
/>
<span
v-else
class="size-3.5"
class="size-3.5 shrink-0"
/>
<slot
name="option-icon"
:option="option"
/>
<slot
name="option-label"
:option="option"
>
<span class="truncate">{{ option.label }}</span>
</slot>
<slot
name="option-suffix"
:option="option"
>
<span
v-if="option.description"
class="ml-auto text-xs text-muted-foreground"
<span class="flex min-w-0 flex-1 items-center gap-2">
<slot
name="option-label"
:option="option"
>
{{ option.description }}
</span>
</slot>
<span
class="truncate flex-1 text-left"
:title="option.label"
>{{ option.label }}</span>
</slot>
<slot
name="option-suffix"
:option="option"
>
<span
v-if="option.description"
class="ml-auto text-xs text-muted-foreground truncate max-w-[50%] text-right"
:title="option.description"
>
{{ option.description }}
</span>
</slot>
</span>
</button>
</div>
</div>
@@ -17,12 +17,15 @@
:aria-label="placeholder || 'Select browser context'"
class="w-full justify-between font-normal"
>
<span class="flex items-center gap-2 truncate">
<span class="flex min-w-0 items-center gap-2 truncate">
<AppWindow
v-if="selected"
class="size-3.5 text-muted-foreground"
class="size-3.5 shrink-0 text-muted-foreground"
/>
<span class="truncate">{{ displayLabel || placeholder }}</span>
<span
class="truncate"
:title="displayLabel || placeholder"
>{{ displayLabel || placeholder }}</span>
</span>
<Search
class="ml-2 size-3.5 shrink-0 text-muted-foreground"
@@ -33,14 +36,15 @@
<template #option-icon="{ option }">
<AppWindow
v-if="option.value"
class="size-3.5 text-muted-foreground"
class="size-3.5 shrink-0 text-muted-foreground"
/>
</template>
<template #option-label="{ option }">
<span
class="truncate"
class="truncate flex-1 text-left"
:class="{ 'text-muted-foreground': !option.value }"
:title="option.label"
>
{{ option.label }}
</span>
@@ -17,12 +17,15 @@
:aria-label="placeholder || 'Select memory provider'"
class="w-full justify-between font-normal"
>
<span class="flex items-center gap-2 truncate">
<span class="flex min-w-0 items-center gap-2 truncate">
<Brain
v-if="selected"
class="size-3.5 text-primary"
class="size-3.5 shrink-0 text-primary"
/>
<span class="truncate">{{ displayLabel || placeholder }}</span>
<span
class="truncate"
:title="displayLabel || placeholder"
>{{ displayLabel || placeholder }}</span>
</span>
<Search
class="ml-2 size-3.5 shrink-0 text-muted-foreground"
@@ -33,14 +36,15 @@
<template #option-icon="{ option }">
<Brain
v-if="option.value"
class="size-3.5 text-primary"
class="size-3.5 shrink-0 text-primary"
/>
</template>
<template #option-label="{ option }">
<span
class="truncate"
class="truncate flex-1 text-left"
:class="{ 'text-muted-foreground': !option.value }"
:title="option.label"
>
{{ option.label }}
</span>
@@ -35,34 +35,41 @@
</div>
<button
v-for="option in group.items"
:key="option.value"
type="button"
role="option"
:aria-selected="modelValue === option.value"
class="relative flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-xs outline-none hover:bg-accent hover:text-accent-foreground [&_+button]:mt-1"
class="relative flex w-full cursor-pointer items-start gap-2 rounded-md px-2 py-1.5 text-xs outline-none hover:bg-accent hover:text-accent-foreground [&_+button]:mt-1"
:class="{ 'bg-accent': modelValue === option.value }"
@click="$emit('update:modelValue', option.value)"
>
<Check
v-if="modelValue === option.value"
class="size-3.5 shrink-0"
class="size-3.5 shrink-0 mt-0.5"
/>
<span
v-else
class="size-3.5 shrink-0"
/>
<span class="truncate ">{{ option.label }}</span>
<span class="ml-auto min-w-[50%] flex items-center gap-1.5 text-right">
<ModelCapabilities
v-if="option.compatibilities?.length"
:compatibilities="option.compatibilities"
/>
<ContextWindowBadge :context-window="option.contextWindow" />
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
<span class="flex min-w-0 items-center gap-2">
<span
class="truncate flex-1 text-left"
:title="option.label"
>{{ option.label }}</span>
<span class="flex items-center gap-1.5 shrink-0">
<ModelCapabilities
v-if="option.compatibilities?.length"
:compatibilities="option.compatibilities"
/>
<ContextWindowBadge :context-window="option.contextWindow" />
</span>
</span>
<span
v-if="option.description"
class="text-xs text-muted-foreground truncate "
v-if="option.description && option.description !== option.label"
class="text-xs text-muted-foreground truncate text-left"
:title="option.description"
>
{{ option.description }}
</span>
@@ -8,7 +8,10 @@
:aria-label="placeholder || 'Select model'"
class="w-full justify-between font-normal"
>
<span class="truncate">
<span
class="truncate"
:title="displayLabel || placeholder"
>
{{ displayLabel || placeholder }}
</span>
<Search
@@ -17,13 +17,16 @@
:aria-label="placeholder || 'Select search provider'"
class="w-full justify-between font-normal"
>
<span class="flex items-center gap-2 truncate">
<span class="flex min-w-0 items-center gap-2 truncate">
<SearchProviderLogo
v-if="selectedProvider"
:provider="selectedProvider.provider || ''"
size="xs"
/>
<span class="truncate">{{ displayLabel || placeholder }}</span>
<span
class="truncate"
:title="displayLabel || placeholder"
>{{ displayLabel || placeholder }}</span>
</span>
<Search
class="ml-2 size-3.5 shrink-0 text-muted-foreground"
@@ -41,8 +44,9 @@
<template #option-label="{ option }">
<span
class="truncate"
class="truncate flex-1 text-left"
:class="{ 'text-muted-foreground': !option.value }"
:title="option.label"
>
{{ option.label }}
</span>
@@ -16,12 +16,15 @@
:aria-label="placeholder || 'Select TTS model'"
class="w-full justify-between font-normal"
>
<span class="flex items-center gap-2 truncate">
<span class="flex min-w-0 items-center gap-2 truncate">
<Volume2
v-if="selected"
class="size-3.5 text-muted-foreground"
class="size-3.5 shrink-0 text-muted-foreground"
/>
<span class="truncate">{{ displayLabel || placeholder }}</span>
<span
class="truncate"
:title="displayLabel || placeholder"
>{{ displayLabel || placeholder }}</span>
</span>
<Search
class="ml-2 size-3.5 shrink-0 text-muted-foreground"
@@ -32,14 +35,15 @@
<template #option-icon="{ option }">
<Volume2
v-if="option.value"
class="size-3.5 text-muted-foreground"
class="size-3.5 shrink-0 text-muted-foreground"
/>
</template>
<template #option-label="{ option }">
<span
class="truncate"
class="truncate flex-1 text-left"
:class="{ 'text-muted-foreground': !option.value }"
:title="option.label"
>
{{ option.label }}
</span>
@@ -164,6 +164,7 @@
variant="ghost"
:disabled="!currentBotId || activeChatReadOnly"
class="gap-0.5 text-muted-foreground max-w-40"
:title="selectedModelLabel"
>
<span class="truncate text-[11px]">{{ selectedModelLabel }}</span>
<ChevronDown class="size-3 shrink-0 opacity-50" />