diff --git a/src/renderer/src/config/models/__tests__/utils.test.ts b/src/renderer/src/config/models/__tests__/utils.test.ts index 49e1e9ff55..f3f4d402af 100644 --- a/src/renderer/src/config/models/__tests__/utils.test.ts +++ b/src/renderer/src/config/models/__tests__/utils.test.ts @@ -24,9 +24,9 @@ import { isGemmaModel, isGenerateImageModels, isMaxTemperatureOneModel, - isNotSupportedTextDelta, isNotSupportSystemMessageModel, isNotSupportTemperatureAndTopP, + isNotSupportTextDeltaModel, isSupportedFlexServiceTier, isSupportedModel, isSupportFlexServiceTierModel, @@ -215,12 +215,51 @@ describe('model utils', () => { it('aggregates boolean helpers based on regex rules', () => { expect(isAnthropicModel(createModel({ id: 'claude-3.5' }))).toBe(true) - expect(isQwenMTModel(createModel({ id: 'qwen-mt-large' }))).toBe(true) - expect(isNotSupportedTextDelta(createModel({ id: 'qwen-mt-large' }))).toBe(true) + expect(isQwenMTModel(createModel({ id: 'qwen-mt-plus' }))).toBe(true) expect(isNotSupportSystemMessageModel(createModel({ id: 'gemma-moe' }))).toBe(true) expect(isOpenAIOpenWeightModel(createModel({ id: 'gpt-oss-free' }))).toBe(true) }) + describe('isNotSupportedTextDelta', () => { + it('returns true for qwen-mt-turbo and qwen-mt-plus models', () => { + // qwen-mt series that don't support text delta + expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-mt-turbo' }))).toBe(true) + expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-mt-plus' }))).toBe(true) + expect(isNotSupportTextDeltaModel(createModel({ id: 'Qwen-MT-Turbo' }))).toBe(true) + expect(isNotSupportTextDeltaModel(createModel({ id: 'QWEN-MT-PLUS' }))).toBe(true) + }) + + it('returns false for qwen-mt-flash and other models', () => { + // qwen-mt-flash supports text delta + expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-mt-flash' }))).toBe(false) + expect(isNotSupportTextDeltaModel(createModel({ id: 'Qwen-MT-Flash' }))).toBe(false) + + // Legacy qwen models without mt prefix (support text delta) + expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-turbo' }))).toBe(false) + expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-plus' }))).toBe(false) + + // Other qwen models + expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-max' }))).toBe(false) + expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen2.5-72b' }))).toBe(false) + expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-vl-plus' }))).toBe(false) + + // Non-qwen models + expect(isNotSupportTextDeltaModel(createModel({ id: 'gpt-4o' }))).toBe(false) + expect(isNotSupportTextDeltaModel(createModel({ id: 'claude-3.5' }))).toBe(false) + expect(isNotSupportTextDeltaModel(createModel({ id: 'glm-4-plus' }))).toBe(false) + }) + + it('handles models with version suffixes', () => { + // qwen-mt models with version suffixes + expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-mt-turbo-1201' }))).toBe(true) + expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-mt-plus-0828' }))).toBe(true) + + // Legacy qwen models with version suffixes (support text delta) + expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-turbo-0828' }))).toBe(false) + expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-plus-latest' }))).toBe(false) + }) + }) + it('evaluates GPT-5 family helpers', () => { expect(isGPT5SeriesModel(createModel({ id: 'gpt-5-preview' }))).toBe(true) expect(isGPT5SeriesModel(createModel({ id: 'gpt-5.1-preview' }))).toBe(false) diff --git a/src/renderer/src/config/models/utils.ts b/src/renderer/src/config/models/utils.ts index c3cd2a2cc2..dabe9ff409 100644 --- a/src/renderer/src/config/models/utils.ts +++ b/src/renderer/src/config/models/utils.ts @@ -111,8 +111,11 @@ export const isAnthropicModel = (model?: Model): boolean => { return modelId.startsWith('claude') } -export const isNotSupportedTextDelta = (model: Model): boolean => { - return isQwenMTModel(model) +const NOT_SUPPORT_TEXT_DELTA_MODEL_REGEX = new RegExp('qwen-mt-(?:turbo|plus)') + +export const isNotSupportTextDeltaModel = (model: Model): boolean => { + const modelId = getLowerBaseModelName(model.id) + return NOT_SUPPORT_TEXT_DELTA_MODEL_REGEX.test(modelId) } export const isNotSupportSystemMessageModel = (model: Model): boolean => { diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/AddModelPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/AddModelPopup.tsx index 6fc79df479..5ed71b8957 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/AddModelPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/AddModelPopup.tsx @@ -1,5 +1,5 @@ import { TopView } from '@renderer/components/TopView' -import { isNotSupportedTextDelta } from '@renderer/config/models' +import { isNotSupportTextDeltaModel } from '@renderer/config/models' import { useProvider } from '@renderer/hooks/useProvider' import type { Model, Provider } from '@renderer/types' import { getDefaultGroupName } from '@renderer/utils' @@ -58,7 +58,7 @@ const PopupContainer: React.FC = ({ title, provider, resolve }) => { group: values.group ?? getDefaultGroupName(id) } - addModel({ ...model, supported_text_delta: !isNotSupportedTextDelta(model) }) + addModel({ ...model, supported_text_delta: !isNotSupportTextDeltaModel(model) }) return true } diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx index 96b802806a..4e4307b8fb 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx @@ -6,7 +6,7 @@ import { groupQwenModels, isEmbeddingModel, isFunctionCallingModel, - isNotSupportedTextDelta, + isNotSupportTextDeltaModel, isReasoningModel, isRerankModel, isVisionModel, @@ -136,13 +136,13 @@ const PopupContainer: React.FC = ({ providerId, resolve }) => { addModel({ ...model, endpoint_type: endpointTypes.includes('image-generation') ? 'image-generation' : endpointTypes[0], - supported_text_delta: !isNotSupportedTextDelta(model) + supported_text_delta: !isNotSupportTextDeltaModel(model) }) } else { NewApiAddModelPopup.show({ title: t('settings.models.add.add_model'), provider, model }) } } else { - addModel({ ...model, supported_text_delta: !isNotSupportedTextDelta(model) }) + addModel({ ...model, supported_text_delta: !isNotSupportTextDeltaModel(model) }) } } }, diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup.tsx index f7d29c772b..d643e1958c 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup.tsx @@ -1,6 +1,6 @@ import { TopView } from '@renderer/components/TopView' import { endpointTypeOptions } from '@renderer/config/endpointTypes' -import { isNotSupportedTextDelta } from '@renderer/config/models' +import { isNotSupportTextDeltaModel } from '@renderer/config/models' import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth' import { useProvider } from '@renderer/hooks/useProvider' import type { EndpointType, Model, Provider } from '@renderer/types' @@ -65,7 +65,7 @@ const PopupContainer: React.FC = ({ title, provider, resolve, model, endp endpoint_type: isNewApiProvider(provider) ? values.endpointType : undefined } - addModel({ ...model, supported_text_delta: !isNotSupportedTextDelta(model) }) + addModel({ ...model, supported_text_delta: !isNotSupportTextDeltaModel(model) }) return true } diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup.tsx index 73905197f6..5a4583738c 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup.tsx @@ -1,6 +1,6 @@ import { TopView } from '@renderer/components/TopView' import { endpointTypeOptions } from '@renderer/config/endpointTypes' -import { isNotSupportedTextDelta } from '@renderer/config/models' +import { isNotSupportTextDeltaModel } from '@renderer/config/models' import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth' import { useProvider } from '@renderer/hooks/useProvider' import type { EndpointType, Model, Provider } from '@renderer/types' @@ -48,7 +48,7 @@ const PopupContainer: React.FC = ({ title, provider, resolve, batchModels addModel({ ...model, endpoint_type: values.endpointType, - supported_text_delta: !isNotSupportedTextDelta(model) + supported_text_delta: !isNotSupportTextDeltaModel(model) }) }) return true diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 228be2e37d..b392091be6 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -5,7 +5,7 @@ import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' import { glm45FlashModel, isFunctionCallingModel, - isNotSupportedTextDelta, + isNotSupportTextDeltaModel, SYSTEM_MODELS } from '@renderer/config/models' import { BUILTIN_OCR_PROVIDERS, BUILTIN_OCR_PROVIDERS_MAP, DEFAULT_OCR_PROVIDER } from '@renderer/config/ocr' @@ -1988,7 +1988,7 @@ const migrateConfig = { const updateModelTextDelta = (model?: Model) => { if (model) { model.supported_text_delta = true - if (isNotSupportedTextDelta(model)) { + if (isNotSupportTextDeltaModel(model)) { model.supported_text_delta = false } }