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
+70
View File
@@ -4,6 +4,10 @@ import { ServerModelStatus, ModelModality } from '$lib/enums';
import { ModelsService } from '$lib/services/models.service';
import { PropsService } from '$lib/services/props.service';
import { serverStore, isRouterMode } from '$lib/stores/server.svelte';
import {
detectThinkingSupport,
detectThinkingSupportWithReason
} from '$lib/utils/chat-template-thinking-detector';
import { TTLCache } from '$lib/utils';
import {
MODEL_PROPS_CACHE_TTL_MS,
@@ -215,6 +219,67 @@ class ModelsStore {
return usage !== undefined && usage.size > 0;
}
//
// Thinking Support Detection
//
/**
* Whether the selected model's chat template supports thinking/reasoning.
* Uses heuristic detection on the model's chat_template from /props.
*
* - MODEL mode: uses serverStore.props.chat_template (single loaded model)
* - ROUTER mode: fetches /props?model=<id> for the selected model (cached)
*
* Triggers an async fetch of model props if not yet cached in ROUTER mode.
*/
get supportsThinking(): boolean {
const modelId = this.selectedModelName;
if (!modelId) {
if (!isRouterMode()) {
return detectThinkingSupport(serverStore.props?.chat_template ?? '');
}
return false;
}
if (isRouterMode() && !this.modelPropsCache.get(modelId)) {
this.fetchModelProps(modelId);
}
const props = this.getModelProps(modelId);
return detectThinkingSupport(props?.chat_template ?? '');
}
/**
* Check if a specific model supports thinking.
* Fetches model props if not cached (in router mode).
*/
checkModelSupportsThinking(modelId: string): boolean {
if (!modelId) return false;
if (isRouterMode() && !this.modelPropsCache.get(modelId)) {
this.fetchModelProps(modelId);
}
const props = this.getModelProps(modelId);
return detectThinkingSupport(props?.chat_template ?? '');
}
/**
* Detailed thinking support detection result with reason for debugging/UI.
*/
get thinkingSupportDetails(): { supported: boolean; reason: string } {
const modelId = this.selectedModelName;
if (!modelId) {
if (!isRouterMode()) {
return detectThinkingSupportWithReason(serverStore.props?.chat_template ?? '');
}
return { supported: false, reason: 'No model selected' };
}
if (isRouterMode() && !this.modelPropsCache.get(modelId)) {
this.fetchModelProps(modelId);
}
const props = this.getModelProps(modelId);
return detectThinkingSupportWithReason(props?.chat_template ?? '');
}
/**
*
@@ -362,6 +427,7 @@ class ModelsStore {
try {
const props = await PropsService.fetchForModel(modelId);
this.modelPropsCache.set(modelId, props);
this.propsCacheVersion++;
return props;
} catch (error) {
console.warn(`Failed to fetch props for model ${modelId}:`, error);
@@ -755,3 +821,7 @@ export const propsCacheVersion = () => modelsStore.propsCacheVersion;
export const singleModelName = () => modelsStore.singleModelName;
export const selectedModelContextSize = () => modelsStore.selectedModelContextSize;
export const favoriteModelIds = () => modelsStore.favoriteModelIds;
export const supportsThinking = () => modelsStore.supportsThinking;
export const checkModelSupportsThinking = (modelId: string) =>
modelsStore.checkModelSupportsThinking(modelId);
export const thinkingSupportDetails = () => modelsStore.thinkingSupportDetails;