From 97508f8d7bdc0abcc6e36f72a5e626ec2b7d41f4 Mon Sep 17 00:00:00 2001 From: yyh Date: Wed, 4 Mar 2026 17:26:24 +0800 Subject: [PATCH 1/2] fix(web): invalidate default model cache after saving system model settings After saving system models, only the model list cache was invalidated but not the default model cache, causing stale config status in the UI. Add useInvalidateDefaultModel hook and call it for all 5 model types after a successful save. --- .../model-provider-page/hooks.ts | 8 ++++++++ .../system-model-selector/index.spec.tsx | 3 +++ .../system-model-selector/index.tsx | 17 +++++------------ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/web/app/components/header/account-setting/model-provider-page/hooks.ts b/web/app/components/header/account-setting/model-provider-page/hooks.ts index 8db964cc27..7e80183e0c 100644 --- a/web/app/components/header/account-setting/model-provider-page/hooks.ts +++ b/web/app/components/header/account-setting/model-provider-page/hooks.ts @@ -222,6 +222,14 @@ export const useUpdateModelList = () => { return updateModelList } +export const useInvalidateDefaultModel = () => { + const queryClient = useQueryClient() + + return useCallback((type: ModelTypeEnum) => { + queryClient.invalidateQueries({ queryKey: commonQueryKeys.defaultModel(type) }) + }, [queryClient]) +} + export const useAnthropicBuyQuota = () => { const [loading, setLoading] = useState(false) diff --git a/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.spec.tsx index 819bb71164..ca28698b64 100644 --- a/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.spec.tsx @@ -26,6 +26,7 @@ vi.mock('react-i18next', async () => { const mockNotify = vi.hoisted(() => vi.fn()) const mockUpdateModelList = vi.hoisted(() => vi.fn()) +const mockInvalidateDefaultModel = vi.hoisted(() => vi.fn()) const mockUpdateDefaultModel = vi.hoisted(() => vi.fn(() => Promise.resolve({ result: 'success' }))) let mockIsCurrentWorkspaceManager = true @@ -57,6 +58,7 @@ vi.mock('../hooks', () => ({ vi.fn(), ], useUpdateModelList: () => mockUpdateModelList, + useInvalidateDefaultModel: () => mockInvalidateDefaultModel, })) vi.mock('@/service/common', () => ({ @@ -144,6 +146,7 @@ describe('SystemModel', () => { type: 'success', message: 'Modified successfully', }) + expect(mockInvalidateDefaultModel).toHaveBeenCalledTimes(5) expect(mockUpdateModelList).toHaveBeenCalledTimes(5) }) }) diff --git a/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx b/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx index 29c71e04fc..1476a2f03d 100644 --- a/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx @@ -19,6 +19,7 @@ import { useProviderContext } from '@/context/provider-context' import { updateDefaultModel } from '@/service/common' import { ModelTypeEnum } from '../declarations' import { + useInvalidateDefaultModel, useModelList, useSystemDefaultModelAndModelList, useUpdateModelList, @@ -48,6 +49,7 @@ const SystemModel: FC = ({ const { isCurrentWorkspaceManager } = useAppContext() const { textGenerationModelList } = useProviderContext() const updateModelList = useUpdateModelList() + const invalidateDefaultModel = useInvalidateDefaultModel() const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding) const { data: rerankModelList } = useModelList(ModelTypeEnum.rerank) const { data: speech2textModelList } = useModelList(ModelTypeEnum.speech2text) @@ -106,18 +108,9 @@ const SystemModel: FC = ({ notify({ type: 'success', message: t('actionMsg.modifiedSuccessfully', { ns: 'common' }) }) setOpen(false) - changedModelTypes.forEach((modelType) => { - if (modelType === ModelTypeEnum.textGeneration) - updateModelList(modelType) - else if (modelType === ModelTypeEnum.textEmbedding) - updateModelList(modelType) - else if (modelType === ModelTypeEnum.rerank) - updateModelList(modelType) - else if (modelType === ModelTypeEnum.speech2text) - updateModelList(modelType) - else if (modelType === ModelTypeEnum.tts) - updateModelList(modelType) - }) + const allModelTypes = [ModelTypeEnum.textGeneration, ModelTypeEnum.textEmbedding, ModelTypeEnum.rerank, ModelTypeEnum.speech2text, ModelTypeEnum.tts] + allModelTypes.forEach(type => invalidateDefaultModel(type)) + changedModelTypes.forEach(type => updateModelList(type)) } } From a92fb3244bda449a547b4e858bd81cf8e284a523 Mon Sep 17 00:00:00 2001 From: yyh Date: Wed, 4 Mar 2026 17:39:49 +0800 Subject: [PATCH 2/2] fix(web): skip top warning for no-provider state and remove unused i18n key The empty state card below already prompts users to install a provider, so the top warning bar is redundant for the no-provider case. Remove the unused noProviderInstalled i18n key and replace the lookup map with a ternary to preserve i18n literal types without assertions. --- .../model-provider-page/index.spec.tsx | 5 +++-- .../model-provider-page/index.tsx | 17 +++++++---------- web/i18n/en-US/common.json | 1 - web/i18n/zh-Hans/common.json | 1 - 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/web/app/components/header/account-setting/model-provider-page/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/index.spec.tsx index 50e86b6fd8..7f1ab81290 100644 --- a/web/app/components/header/account-setting/model-provider-page/index.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/index.spec.tsx @@ -161,7 +161,7 @@ describe('ModelProviderPage', () => { }) describe('system model config status', () => { - it('should show no-provider warning when no configured providers exist', () => { + it('should not show top warning when no configured providers exist (empty state card handles it)', () => { mockProviders.splice(0, mockProviders.length, { provider: 'anthropic', label: { en_US: 'Anthropic' }, @@ -174,7 +174,8 @@ describe('ModelProviderPage', () => { }) render() - expect(screen.getByText('common.modelProvider.noProviderInstalled')).toBeInTheDocument() + expect(screen.queryByText('common.modelProvider.noProviderInstalled')).not.toBeInTheDocument() + expect(screen.getByText('common.modelProvider.emptyProviderTitle')).toBeInTheDocument() }) it('should show none-configured warning when providers exist but no default models set', () => { diff --git a/web/app/components/header/account-setting/model-provider-page/index.tsx b/web/app/components/header/account-setting/model-provider-page/index.tsx index ec95dde24f..5f5e05b51a 100644 --- a/web/app/components/header/account-setting/model-provider-page/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/index.tsx @@ -23,12 +23,6 @@ import SystemModelSelector from './system-model-selector' type SystemModelConfigStatus = 'no-provider' | 'none-configured' | 'partially-configured' | 'fully-configured' -const WARNING_TEXT_KEYS = { - 'no-provider': 'modelProvider.noProviderInstalled', - 'none-configured': 'modelProvider.noneConfigured', - 'partially-configured': 'modelProvider.notConfigured', -} as const - type Props = { searchText: string } @@ -94,10 +88,13 @@ const ModelProviderPage = ({ searchText }: Props) => { return 'partially-configured' return 'fully-configured' }, [configuredProviders, textGenerationDefaultModel, embeddingsDefaultModel, rerankDefaultModel, speech2textDefaultModel, ttsDefaultModel]) - const showWarning = !isDefaultModelLoading && systemModelConfigStatus !== 'fully-configured' - const warningTextKey = systemModelConfigStatus !== 'fully-configured' - ? WARNING_TEXT_KEYS[systemModelConfigStatus] - : undefined + const warningTextKey + = systemModelConfigStatus === 'none-configured' + ? 'modelProvider.noneConfigured' + : systemModelConfigStatus === 'partially-configured' + ? 'modelProvider.notConfigured' + : null + const showWarning = !isDefaultModelLoading && !!warningTextKey const [filteredConfiguredProviders, filteredNotConfiguredProviders] = useMemo(() => { const filteredConfiguredProviders = configuredProviders.filter( diff --git a/web/i18n/en-US/common.json b/web/i18n/en-US/common.json index ad3b3c7e97..20a3ea7cc0 100644 --- a/web/i18n/en-US/common.json +++ b/web/i18n/en-US/common.json @@ -390,7 +390,6 @@ "modelProvider.models": "Models", "modelProvider.modelsNum": "{{num}} Models", "modelProvider.noModelFound": "No model found for {{model}}", - "modelProvider.noProviderInstalled": "No model provider installed. Install one to configure system models.", "modelProvider.noneConfigured": "Configure a default system model to run applications", "modelProvider.notConfigured": "The system model has not yet been fully configured", "modelProvider.parameters": "PARAMETERS", diff --git a/web/i18n/zh-Hans/common.json b/web/i18n/zh-Hans/common.json index 5e59133595..150cb2ef91 100644 --- a/web/i18n/zh-Hans/common.json +++ b/web/i18n/zh-Hans/common.json @@ -390,7 +390,6 @@ "modelProvider.models": "模型列表", "modelProvider.modelsNum": "{{num}} 个模型", "modelProvider.noModelFound": "找不到模型 {{model}}", - "modelProvider.noProviderInstalled": "尚未安装模型供应商,请先安装以配置系统模型。", "modelProvider.noneConfigured": "配置默认系统模型以运行应用", "modelProvider.notConfigured": "系统模型尚未完全配置", "modelProvider.parameters": "参数",