diff --git a/mateclaw-ui/src/components/chat/ChatInput.vue b/mateclaw-ui/src/components/chat/ChatInput.vue index 6ae1059c..56b35f31 100644 --- a/mateclaw-ui/src/components/chat/ChatInput.vue +++ b/mateclaw-ui/src/components/chat/ChatInput.vue @@ -136,14 +136,16 @@ - + @@ -239,6 +241,11 @@ interface Props { enableTalkMode?: boolean /** 深度思考开关状态 */ thinkingEnabled?: boolean + /** + * RFC-049 PR-1-UI: 当前 runtime 模型是否支持 reasoning_effort。false 时按钮灰掉, + * 不响应点击,tooltip 提示当前模型不支持深度思考。默认 true 以保持向后兼容。 + */ + thinkingSupported?: boolean } const props = withDefaults(defineProps(), { @@ -257,6 +264,7 @@ const props = withDefaults(defineProps(), { queueSize: 0, enableTalkMode: false, thinkingEnabled: false, + thinkingSupported: true, }) const emit = defineEmits<{ @@ -598,6 +606,11 @@ defineExpose({ border-radius: 50%; background: var(--el-color-primary, #409eff); } +/* RFC-049 PR-1-UI: model doesn't support reasoning_effort — stronger grayed state */ +.thinking-btn.unsupported { + opacity: 0.35; + cursor: not-allowed; +} .thinking-btn:hover:not(:disabled) { color: var(--el-color-primary, #409eff); background: var(--el-color-primary-light-9, rgba(64, 158, 255, 0.08)); diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts index a11e20ee..6e4af92b 100644 --- a/mateclaw-ui/src/i18n/locales/en-US.ts +++ b/mateclaw-ui/src/i18n/locales/en-US.ts @@ -165,6 +165,7 @@ export default { messagePlaceholder: 'Type a message... (Enter to send, Shift+Enter for new line)', thinkingOn: 'Deep thinking enabled', thinkingOff: 'Click to enable deep thinking', + thinkingUnsupported: 'Current model does not support deep thinking', subtitle: 'Your intelligent AI assistant powered by Spring AI Alibaba', // Queue related queuedSending: 'Sending queued message...', diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts index e014e338..063db738 100644 --- a/mateclaw-ui/src/i18n/locales/zh-CN.ts +++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts @@ -165,6 +165,7 @@ export default { messagePlaceholder: '输入消息... (Enter 发送, Shift+Enter 换行)', thinkingOn: '深度思考已开启', thinkingOff: '点击开启深度思考', + thinkingUnsupported: '当前模型不支持深度思考', subtitle: '基于 Spring AI Alibaba 的智能 AI 助手', // 排队相关 queuedSending: '正在发送排队消息...', diff --git a/mateclaw-ui/src/types/index.ts b/mateclaw-ui/src/types/index.ts index 94c48aea..6b1e0607 100644 --- a/mateclaw-ui/src/types/index.ts +++ b/mateclaw-ui/src/types/index.ts @@ -586,6 +586,14 @@ export interface ProviderModelInfo { probeOk?: boolean /** 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. + */ + supportsReasoningEffort?: boolean } export interface ProviderInfo { diff --git a/mateclaw-ui/src/views/ChatConsole.vue b/mateclaw-ui/src/views/ChatConsole.vue index 676f192d..548b7494 100644 --- a/mateclaw-ui/src/views/ChatConsole.vue +++ b/mateclaw-ui/src/views/ChatConsole.vue @@ -252,6 +252,7 @@ @deny="handleDeny" :enable-talk-mode="!!selectedAgentId" :thinking-enabled="thinkingEnabled" + :thinking-supported="currentModelSupportsReasoningEffort" @toggle-thinking="thinkingEnabled = !thinkingEnabled" @talk="showTalkMode = true" /> @@ -635,6 +636,26 @@ const currentRuntimeModel = computed(() => { return currentAgent.value?.modelName || 'default' }) +/** + * 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). + * + * Source of truth: ProviderModelInfo.supportsReasoningEffort (set by backend via + * ModelFamily.detect(modelName) in ModelInfoDTO). + */ +const currentModelSupportsReasoningEffort = computed(() => { + const providerId = activeModels.value?.activeLlm?.providerId + const modelName = activeModels.value?.activeLlm?.model + if (!providerId || !modelName) return false + const provider = providers.value.find((p) => p.id === providerId) + 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) +}) + const userInitial = computed(() => (localStorage.getItem('username') || 'U').charAt(0).toUpperCase()) const activeModelValue = computed(() => {