From 217ab4d2c355bc4b0b983d28baf5d24be6d213b5 Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:55:10 +0800 Subject: [PATCH] fix(web): filter model selector by model name (#35624) --- .../model-selector/__tests__/popup.spec.tsx | 159 +++++++++++++++++- .../model-selector/popup.tsx | 30 +++- 2 files changed, 179 insertions(+), 10 deletions(-) diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup.spec.tsx index a440313b3c..42232a71c0 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup.spec.tsx @@ -55,7 +55,14 @@ vi.mock('../../hooks', async () => { }) vi.mock('../popup-item', () => ({ - default: ({ model }: { model: Model }) =>
{model.provider}
, + default: ({ model }: { model: Model }) => ( +
+ {model.provider} + {model.models.map(modelItem => ( + {modelItem.model} + ))} +
+ ), })) vi.mock('@/context/provider-context', () => ({ @@ -207,6 +214,156 @@ describe('Popup', () => { expect((input as HTMLInputElement).value).toBe('') }) + it('should show matching models when searching by model name', () => { + renderPopup( + , + ) + + fireEvent.change( + screen.getByPlaceholderText('datasetSettings.form.searchModel'), + { target: { value: 'claude' } }, + ) + + expect(screen.queryByText('openai')).not.toBeInTheDocument() + expect(screen.getByText('anthropic')).toBeInTheDocument() + expect(screen.getByText('claude-3')).toBeInTheDocument() + expect(screen.queryByText('gpt-4')).not.toBeInTheDocument() + expect(screen.queryByText('No model found for \u201Cclaude\u201D')).not.toBeInTheDocument() + }) + + it('should show empty search placeholder when no provider or model name matches', () => { + renderPopup( + , + ) + + fireEvent.change( + screen.getByPlaceholderText('datasetSettings.form.searchModel'), + { target: { value: 'mistral' } }, + ) + + expect(screen.getByText('No model found for \u201Cmistral\u201D'))!.toBeInTheDocument() + expect(screen.queryByText('openai')).not.toBeInTheDocument() + expect(screen.queryByText('gpt-4')).not.toBeInTheDocument() + }) + + it('should show all models of a provider when searching by provider label', () => { + renderPopup( + , + ) + + fireEvent.change( + screen.getByPlaceholderText('datasetSettings.form.searchModel'), + { target: { value: 'openai' } }, + ) + + expect(screen.getByText('openai'))!.toBeInTheDocument() + expect(screen.getByText('gpt-4'))!.toBeInTheDocument() + expect(screen.getByText('gpt-4o'))!.toBeInTheDocument() + expect(screen.queryByText('anthropic')).not.toBeInTheDocument() + expect(screen.queryByText('claude-3')).not.toBeInTheDocument() + }) + + it('should match by model provider key when model label does not contain the search text', () => { + renderPopup( + , + ) + + fireEvent.change( + screen.getByPlaceholderText('datasetSettings.form.searchModel'), + { target: { value: 'openai' } }, + ) + + expect(screen.getByText('azure_openai'))!.toBeInTheDocument() + expect(screen.getByText('gpt-4'))!.toBeInTheDocument() + }) + + it('should still apply scope features when matching by provider label', () => { + mockSupportFunctionCall.mockReturnValue(false) + + renderPopup( + , + ) + + fireEvent.change( + screen.getByPlaceholderText('datasetSettings.form.searchModel'), + { target: { value: 'openai' } }, + ) + + expect(screen.getByText('No model found for \u201Copenai\u201D'))!.toBeInTheDocument() + expect(screen.queryByText('gpt-4')).not.toBeInTheDocument() + expect(screen.queryByText('gpt-4-tool')).not.toBeInTheDocument() + }) + it('should not show compatible-only helper text when no scope features are applied', () => { renderPopup( = ({ }, [aiCreditVisibleProviders, installedProviderMap, modelList]) const filteredModelList = useMemo(() => { + const normalizedSearch = searchText.toLowerCase() + const matchesLabel = (label: Record) => { + if (label[language] !== undefined) + return label[language].toLowerCase().includes(normalizedSearch) + return Object.values(label).some(value => + value.toLowerCase().includes(normalizedSearch), + ) + } + const filtered = installedModelList.map((model) => { - const matchesProviderSearch = !searchText - || model.provider.toLowerCase().includes(searchText.toLowerCase()) - || Object.values(model.label).some(label => label.toLowerCase().includes(searchText.toLowerCase())) + const providerMatched = !!searchText && ( + matchesLabel(model.label) + || model.provider.toLowerCase().includes(normalizedSearch) + ) const filteredModels = model.models .filter((modelItem) => { - if (modelItem.label[language] !== undefined) - return modelItem.label[language].toLowerCase().includes(searchText.toLowerCase()) - return Object.values(modelItem.label).some(label => - label.toLowerCase().includes(searchText.toLowerCase()), - ) + if (!searchText || providerMatched) + return true + return matchesLabel(modelItem.label) }) .filter((modelItem) => { if (scopeFeatures.length === 0) @@ -165,8 +173,12 @@ const Popup: FC = ({ return modelItem.features?.includes(feature) ?? false }) }) - if (!matchesProviderSearch || (filteredModels.length === 0 && !aiCreditVisibleProviders.has(model.provider))) + if ( + (searchText && filteredModels.length === 0) + || (!searchText && filteredModels.length === 0 && !aiCreditVisibleProviders.has(model.provider)) + ) { return null + } return { ...model, models: filteredModels } }).filter((model): model is Model => model !== null)