From 6cb0cbcee02a796621436dfb753c6310c1ad807e Mon Sep 17 00:00:00 2001 From: fretuks Date: Mon, 21 Sep 2026 20:35:38 +0200 Subject: [PATCH] improve reasoning toggle tests and refactor model capability checks --- public/app/modelCapabilities.js | 12 +++++++--- public/app/reasoning.js | 2 +- public/chat.html | 2 +- public/script.js | 2 +- test/reasoning.test.js | 41 +++++++++++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/public/app/modelCapabilities.js b/public/app/modelCapabilities.js index 3dc8745..fc09ba3 100644 --- a/public/app/modelCapabilities.js +++ b/public/app/modelCapabilities.js @@ -1,6 +1,12 @@ // Conservative allowlist for models supporting the boolean think API. // Unknown models remain usable for normal chat. -export function supportsReasoning(model) { - const id = String(typeof model === "string" ? model : model?.model || model?.name || "").toLowerCase().split("/").at(-1); - return /^(qwen3|deepseek-r1|deepseek-v3\.1)(:|$)/.test(id); +const reasoningFamilies = new Set(["qwen3", "deepseek-r1", "deepseek-v3.1"]); + +export function getModelFamily(model) { + const id = typeof model === "string" ? model : model?.model || model?.name || model?.id || ""; + return String(id).trim().toLowerCase().split("/").at(-1).split(":")[0]; +} + +export function supportsReasoning(model) { + return reasoningFamilies.has(getModelFamily(model)); } diff --git a/public/app/reasoning.js b/public/app/reasoning.js index 745c07d..c7f4473 100644 --- a/public/app/reasoning.js +++ b/public/app/reasoning.js @@ -1,4 +1,4 @@ -import {supportsReasoning} from "./modelCapabilities.js"; +import {supportsReasoning} from "./modelCapabilities.js?v=20260921-reasoning-capability"; export function createReasoningToggle(button) { let reasoningEnabled = false; diff --git a/public/chat.html b/public/chat.html index 2b94c1a..60f0abf 100644 --- a/public/chat.html +++ b/public/chat.html @@ -358,7 +358,7 @@ - + diff --git a/public/script.js b/public/script.js index 9b8a403..a54e064 100644 --- a/public/script.js +++ b/public/script.js @@ -1,6 +1,6 @@ import {del, get, post, put, stream} from "./app/api.js?v=20260909-account-controls"; import {appendSources, createWebSearchToggle} from "./app/webSearch.js"; -import {createReasoningToggle, finishReasoning, updateReasoning} from "./app/reasoning.js"; +import {createReasoningToggle, finishReasoning, updateReasoning} from "./app/reasoning.js?v=20260921-reasoning-capability"; import {closeChatDrawer, openChatDrawer} from "./app/chatDrawer.js?v=20260909-account-controls"; import {onboardingPrompts, requestedChatId} from "./app/constants.js?v=20260909-account-controls"; import {getModelProfile} from "./app/modelProfiles.js?v=20260909-account-controls"; diff --git a/test/reasoning.test.js b/test/reasoning.test.js index 1dd51ff..442eb96 100644 --- a/test/reasoning.test.js +++ b/test/reasoning.test.js @@ -5,6 +5,7 @@ import {createApp} from "../src/app.js"; import {createModelService} from "../src/services/modelService.js"; import {stream} from "../public/app/api.js"; import {createReasoningToggle, finishReasoning, updateReasoning} from "../public/app/reasoning.js"; +import {getModelFamily, supportsReasoning} from "../public/app/modelCapabilities.js"; test("thinking streams separately and survives reload, retry, branch and import", async (t) => { const requests = []; @@ -169,17 +170,20 @@ test("reasoning toggle defaults off, resets, and rejects unsupported models", () const button = {setAttribute() {}, addEventListener(_event, callback) { this.click = callback; }}; const toggle = createReasoningToggle(button); toggle.setModel("qwen3:14b"); + assert.equal(button.disabled, false); assert.equal(toggle.enabled, false); assert.equal(button.title, "Enable reasoning"); button.click(); assert.equal(toggle.enabled, true); assert.equal(button.title, "Reasoning enabled"); + assert.equal(button.disabled, false); toggle.setBusy(true); button.click(); assert.equal(toggle.enabled, true); toggle.reset(); assert.equal(toggle.enabled, false); toggle.setBusy(false); + assert.equal(button.disabled, false); button.click(); toggle.setModel("mistral:latest"); assert.equal(toggle.enabled, false); @@ -188,3 +192,40 @@ test("reasoning toggle defaults off, resets, and rejects unsupported models", () button.click(); assert.equal(toggle.enabled, false); }); + +test("tagged Qwen3 models enable the control without activating reasoning", () => { + for (const model of [ + "qwen3:14b", "qwen3:8b", "qwen3:30b", "qwen3:latest", + " QWEN3:14b ", "library/qwen3:14b", + {model: "qwen3:14b"}, {name: "qwen3:8b"}, {id: "qwen3:latest"} + ]) { + assert.equal(getModelFamily(model), "qwen3"); + assert.equal(supportsReasoning(model), true); + const attributes = {}; + const button = { + disabled: true, + setAttribute(key, value) { attributes[key] = value; }, + addEventListener(_event, callback) { this.click = callback; } + }; + const toggle = createReasoningToggle(button); + toggle.setModel(""); // Model list has not loaded yet. + assert.equal(button.disabled, true); + toggle.setModel(model); + assert.equal(button.disabled, false); + assert.equal(toggle.enabled, false); + assert.equal(attributes["aria-pressed"], "false"); + assert.equal(button.title, "Enable reasoning"); + button.click(); + assert.equal(toggle.enabled, true); + assert.equal(attributes["aria-pressed"], "true"); + toggle.setModel(model); // A UI refresh must preserve the choice. + assert.equal(toggle.enabled, true); + toggle.reset(); + assert.equal(toggle.enabled, false); + assert.equal(button.disabled, false); + assert.equal(button.title, "Enable reasoning"); + } + for (const model of ["mistral:latest", "qwen2.5:14b", "qwen3-coder:30b", "qwen30:14b", "", null]) { + assert.equal(supportsReasoning(model), false); + } +});