From a3289d2780eb86a5d1b487c31a5d1e48059dbaab Mon Sep 17 00:00:00 2001 From: matevip Date: Fri, 24 Apr 2026 18:16:28 +0800 Subject: [PATCH] fix(ui): gate thinking toggle on supportsThinking (broad), not supportsReasoningEffort --- .../java/vip/mate/llm/model/ModelInfoDTO.java | 39 ++++++++++++++----- mateclaw-ui/src/types/index.ts | 14 ++++--- mateclaw-ui/src/views/ChatConsole.vue | 20 +++++----- 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/mateclaw-server/src/main/java/vip/mate/llm/model/ModelInfoDTO.java b/mateclaw-server/src/main/java/vip/mate/llm/model/ModelInfoDTO.java index e840068c..18013199 100644 --- a/mateclaw-server/src/main/java/vip/mate/llm/model/ModelInfoDTO.java +++ b/mateclaw-server/src/main/java/vip/mate/llm/model/ModelInfoDTO.java @@ -23,21 +23,42 @@ public class ModelInfoDTO { private String probeError; /** - * RFC-049 PR-1-UI: whether this model's {@code ModelFamily} accepts the - * {@code reasoning_effort} parameter. Derived from the model name on the - * server side so the frontend can gray out the "thinking depth" selector - * for chat-type models that don't support thinking — a product contract, - * not a runtime toggle. - * - *

{@code true} only for the OpenAI reasoning family (gpt-5*, o1*, o3*, o4*). + * RFC-049 PR-1-UI (narrow): whether this model's {@code ModelFamily} accepts the + * OpenAI {@code reasoning_effort} parameter specifically. True only for + * the OpenAI reasoning family (gpt-5, o1, o3, o4 variants). Retained for callers + * that need to know parameter-level compatibility; the UI "deep thinking" toggle + * should use {@link #supportsThinking} instead. */ private boolean supportsReasoningEffort; + /** + * RFC-049 PR-1-UI (broad): whether this model supports any form of + * deep thinking — either OpenAI-style via {@code reasoning_effort}, provider- + * native (Kimi K2.x, DeepSeek-Reasoner, qwen-thinking), or Anthropic extended + * thinking (Claude family). This is what the UI "deep thinking" toggle reads. + * + *

Derived from the model name so every construction site stays consistent. + */ + private boolean supportsThinking; + public ModelInfoDTO(String id, String name) { this.id = id; this.name = name; - // RFC-049 PR-1-UI: derived from id (the model name) so every construction - // site populates the capability consistently. this.supportsReasoningEffort = ModelFamily.detect(id).supportsReasoningEffort(); + this.supportsThinking = computeSupportsThinking(id); + } + + /** + * Thinking capability at the product level (what the UI toggle should reflect): + * any model family that has a thinking mode, regardless of how it is triggered + * (parameter vs. model-native vs. Anthropic extended thinking). + */ + private static boolean computeSupportsThinking(String modelName) { + if (modelName == null || modelName.isBlank()) return false; + ModelFamily family = ModelFamily.detect(modelName); + if (family.isThinking()) return true; // OPENAI_REASONING / KIMI_THINKING / DEEPSEEK_REASONER / GENERIC_THINKING + // Anthropic Claude supports extended thinking via AnthropicChatOptions.thinking; + // ModelFamily doesn't model non-OpenAI-compatible providers so match by name. + return modelName.toLowerCase().contains("claude"); } } diff --git a/mateclaw-ui/src/types/index.ts b/mateclaw-ui/src/types/index.ts index 6b1e0607..0e0896b7 100644 --- a/mateclaw-ui/src/types/index.ts +++ b/mateclaw-ui/src/types/index.ts @@ -587,13 +587,17 @@ export interface ProviderModelInfo { /** Short error message when probeOk=false */ probeError?: string /** - * RFC-049 PR-1-UI: whether the model's `ModelFamily` accepts the - * `reasoning_effort` parameter. Derived server-side from the model name; - * true only for the OpenAI reasoning family (gpt-5, o1, o3, o4 variants). - * Used by the thinking-depth selector to gray itself out on non-reasoning - * models. + * RFC-049 PR-1-UI (narrow): whether the model accepts the OpenAI + * `reasoning_effort` parameter. True only for OpenAI reasoning family. */ supportsReasoningEffort?: boolean + /** + * RFC-049 PR-1-UI (broad): whether the model supports any form of deep + * thinking (OpenAI reasoning_effort, Kimi/DeepSeek native thinking, + * Anthropic extended thinking). This is the field the UI "thinking depth" + * toggle should gate on. + */ + supportsThinking?: boolean } export interface ProviderInfo { diff --git a/mateclaw-ui/src/views/ChatConsole.vue b/mateclaw-ui/src/views/ChatConsole.vue index 548b7494..7120e658 100644 --- a/mateclaw-ui/src/views/ChatConsole.vue +++ b/mateclaw-ui/src/views/ChatConsole.vue @@ -252,7 +252,7 @@ @deny="handleDeny" :enable-talk-mode="!!selectedAgentId" :thinking-enabled="thinkingEnabled" - :thinking-supported="currentModelSupportsReasoningEffort" + :thinking-supported="currentModelSupportsThinking" @toggle-thinking="thinkingEnabled = !thinkingEnabled" @talk="showTalkMode = true" /> @@ -637,15 +637,17 @@ const currentRuntimeModel = computed(() => { }) /** - * RFC-049 PR-1-UI: whether the active runtime model supports `reasoning_effort`. - * Drives the enable/disable state of the thinking-depth toggle in ChatInput: - * chat-type models that don't support thinking must not honor the "deep thinking" - * selection (product contract — UI reflects the backend gate). + * RFC-049 PR-1-UI: whether the active runtime model supports any form + * of deep thinking (OpenAI reasoning_effort / Kimi native / DeepSeek-Reasoner + * native / Anthropic extended thinking). Drives the enable/disable state of + * the thinking-depth toggle in ChatInput. * - * Source of truth: ProviderModelInfo.supportsReasoningEffort (set by backend via - * ModelFamily.detect(modelName) in ModelInfoDTO). + * Reads the broad capability (`supportsThinking`) from ProviderModelInfo, + * populated server-side in ModelInfoDTO. The narrow `supportsReasoningEffort` + * only covers OpenAI gpt-5/o1/o3/o4 and would wrongly gray out Kimi K2.x, + * DeepSeek-Reasoner, and Claude — all of which legitimately support thinking. */ -const currentModelSupportsReasoningEffort = computed(() => { +const currentModelSupportsThinking = computed(() => { const providerId = activeModels.value?.activeLlm?.providerId const modelName = activeModels.value?.activeLlm?.model if (!providerId || !modelName) return false @@ -653,7 +655,7 @@ const currentModelSupportsReasoningEffort = computed(() => { if (!provider) return false const all = [...(provider.models || []), ...(provider.extraModels || [])] const hit = all.find((m) => m.id === modelName || m.name === modelName) - return Boolean(hit?.supportsReasoningEffort) + return Boolean(hit?.supportsThinking) }) const userInitial = computed(() => (localStorage.getItem('username') || 'U').charAt(0).toUpperCase())