ui: Add Thinking mode toggle with reasoning effort levels + improvements for Chat Form Add Action UI (#23434)

* feat: Add "Thinking" toggle and status icon + redesign Chat Form Actions Add panel

* test: Update test reference

* fix: Icon

* fix: E2E test command

* fix: wait for greeting h1 to be visible in e2e test

* fix: remove duplicate PDF option in attachment dropdown

* fix: use label-based group toggle to avoid stale references

* refactor: inline MCP server and tool toggles in mobile sheet

* fix: serve correct build directory in e2e playwright config

* feat: add reasoning effort levels selector in model dropdown

* feat: Reasoning effort

* refactor: Make server origin configurable via environment variable

* feat: Add chat template thinking detector utility

* feat: Add thinking support detection to models store

* refactor: Update model selector components with thinking detection and message-specific indicators

* feat: Update chat form components for model selection and thinking support

* feat: Improve Reasoning controls UI

* refactor: Apply suggestions from code review

Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>

* fix: Model tags

* refactor: Cleanup

* refactor: Remove unneeded components

* refactor: Cleanup
This commit is contained in:
Aleksander Grygier
2026-06-02 10:23:19 +02:00
committed by GitHub
parent 2365315955
commit f8e67fc583
40 changed files with 1085 additions and 263 deletions
@@ -8,6 +8,7 @@
hideOrgName?: boolean;
showRaw?: boolean;
hideQuantization?: boolean;
hideTags?: boolean;
aliases?: string[];
tags?: string[];
class?: string;
@@ -17,7 +18,8 @@
modelId,
hideOrgName = false,
showRaw = undefined,
hideQuantization = false,
hideQuantization,
hideTags,
aliases,
tags,
class: className = '',
@@ -31,6 +33,8 @@
let parsed = $derived(ModelsService.parseModelId(modelId));
let resolvedShowRaw = $derived(showRaw ?? (config().showRawModelNames as boolean) ?? false);
let resolvedHideQuantization = $derived(hideQuantization ?? !config().showModelQuantization);
let resolvedHideTags = $derived(hideTags ?? !config().showModelTags);
let uniqueAliases = $derived([...new Set(aliases ?? [])]);
let uniqueTags = $derived([...new Set([...(parsed.tags ?? []), ...(tags ?? [])])]);
@@ -53,7 +57,7 @@
</span>
{/if}
{#if parsed.quantization && !hideQuantization}
{#if parsed.quantization && !resolvedHideQuantization}
<span class={badgeClass}>
{parsed.quantization}
</span>
@@ -69,7 +73,7 @@
{/each}
{/if}
{#if uniqueTags.length > 0}
{#if uniqueTags.length > 0 && !resolvedHideTags}
{#each uniqueTags as tag (tag)}
<span class={tagBadgeClass}>{tag}</span>
{/each}
@@ -106,9 +106,7 @@
]}
style="max-width: min(calc(100cqw - 10rem), 20rem)"
>
<Package class="h-3.5 w-3.5" />
<ModelId modelId={currentModel} class="min-w-0" hideQuantization />
<Package class="h-3.5 w-3.5 shrink-0" />
</span>
{:else}
<p class="text-xs text-muted-foreground">No models available.</p>
@@ -120,7 +118,7 @@
<DropdownMenu.Root bind:open={isOpen} onOpenChange={ms.handleOpenChange}>
<DropdownMenu.Trigger
class={[
`inline-grid cursor-pointer grid-cols-[1fr_auto_1fr] items-center gap-1.5 rounded-sm bg-background px-1.5 py-1 text-xs shadow-sm transition hover:bg-muted-foreground/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 dark:bg-muted-foreground/15 dark:text-secondary-foreground`,
`inline-flex cursor-pointer items-center gap-1.5 rounded-sm bg-background px-1.5 py-1 text-xs shadow-sm transition hover:bg-muted-foreground/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 dark:bg-muted-foreground/15 dark:text-secondary-foreground`,
!ms.isCurrentModelInCache
? 'bg-red-400/10 !text-red-400 hover:bg-red-400/20 hover:text-red-400'
: forceForegroundText
@@ -133,7 +131,7 @@
]}
disabled={disabled || ms.updating}
>
<Package class="h-3.5 w-3.5" />
<Package class="h-3.5 w-3.5 shrink-0" />
{#if selectedOption}
<Tooltip.Root>
@@ -144,6 +142,7 @@
modelId={selectedOption.model}
class="min-w-0 overflow-hidden"
hideOrgName={false}
hideQuantization
{...props}
/>
{/snippet}
@@ -158,9 +157,9 @@
{/if}
{#if ms.updating || ms.isLoadingModel}
<Loader2 class="h-3 w-3.5 animate-spin" />
<Loader2 class="h-3 w-3.5 shrink-0 animate-spin" />
{:else}
<ChevronDown class="h-3 w-3.5" />
<ChevronDown class="h-3 w-3.5 shrink-0" />
{/if}
</DropdownMenu.Trigger>
@@ -251,7 +250,7 @@
onclick={() => ms.handleOpenChange(true)}
disabled={disabled || ms.updating}
>
<Package class="h-3.5 w-3.5" />
<Package class="h-3.5 w-3.5 shrink-0" />
{#if selectedOption}
<Tooltip.Root>
@@ -262,6 +261,7 @@
modelId={selectedOption.model}
class="min-w-0 overflow-hidden"
hideOrgName={false}
hideQuantization
{...props}
/>
{/snippet}
@@ -274,7 +274,7 @@
{/if}
{#if ms.updating}
<Loader2 class="h-3 w-3.5 animate-spin" />
<Loader2 class="h-3 w-3.5 shrink-0 animate-spin" />
{/if}
</button>
{/if}
@@ -6,8 +6,7 @@
DialogModelInformation,
ModelId,
ModelsSelectorList,
SearchInput,
TruncatedText
SearchInput
} from '$lib/components/app';
interface Props {
@@ -67,7 +66,7 @@
<button
type="button"
class={[
`inline-grid cursor-pointer grid-cols-[1fr_auto_1fr] items-center gap-1.5 rounded-sm bg-background px-1.5 py-1 text-xs shadow-sm transition hover:bg-muted-foreground/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 dark:bg-muted-foreground/15 dark:text-secondary-foreground`,
`inline-flex cursor-pointer items-center gap-1.5 rounded-sm bg-background px-1.5 py-1 text-xs shadow-sm transition hover:bg-muted-foreground/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 dark:bg-muted-foreground/15 dark:text-secondary-foreground`,
!ms.isCurrentModelInCache
? 'bg-red-400/10 !text-red-400 hover:bg-red-400/20 hover:text-red-400'
: forceForegroundText
@@ -81,7 +80,7 @@
disabled={disabled || ms.updating}
onclick={() => ms.handleOpenChange(true)}
>
<Package class="h-3.5 w-3.5" />
<Package class="h-3.5 w-3.5 shrink-0" />
{#if !selectedOption}
<span class="min-w-0 font-medium">Select model</span>
@@ -90,14 +89,15 @@
class="text-xs"
modelId={selectedOption?.model || ''}
hideQuantization
hideTags
hideOrgName
/>
{/if}
{#if ms.updating || ms.isLoadingModel}
<Loader2 class="h-3 w-3.5 animate-spin" />
<Loader2 class="h-3 w-3.5 shrink-0 animate-spin" />
{:else}
<ChevronDown class="h-3 w-3.5" />
<ChevronDown class="h-3 w-3.5 shrink-0" />
{/if}
</button>
@@ -168,12 +168,12 @@
onclick={() => ms.handleOpenChange(true)}
disabled={disabled || ms.updating}
>
<Package class="h-3.5 w-3.5" />
<Package class="h-3.5 w-3.5 shrink-0" />
<TruncatedText text={selectedOption?.model || ''} class="font-medium" />
<ModelId modelId={selectedOption?.model || ''} class="font-medium" hideQuantization />
{#if ms.updating}
<Loader2 class="h-3 w-3.5 animate-spin" />
<Loader2 class="h-3 w-3.5 shrink-0 animate-spin" />
{/if}
</button>
{/if}
@@ -74,8 +74,7 @@ export { default as ModelsSelectorOption } from './ModelsSelectorOption.svelte';
*/
export { default as ModelsSelectorSheet } from './ModelsSelectorSheet.svelte';
/**
* **ModelBadge** - Model name display badge
/** * **ModelBadge** - Model name display badge
*
* Compact badge showing current model name with package icon.
* Only visible in single model mode. Supports tooltip and copy functionality.