diff --git a/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/mcpCatalog/mcpCatalog.cy.ts b/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/mcpCatalog/mcpCatalog.cy.ts index 653643fcb5..8269e8c55c 100644 --- a/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/mcpCatalog/mcpCatalog.cy.ts +++ b/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/mcpCatalog/mcpCatalog.cy.ts @@ -1,5 +1,5 @@ import { mockModArchResponse } from 'mod-arch-core'; -import { mockCatalogSourceList } from '~/__mocks__'; +import { mockCatalogLabel, mockCatalogSourceList, mockCatalogSource } from '~/__mocks__'; import { mcpCatalog } from '~/__tests__/cypress/cypress/pages/mcpCatalog'; import { MODEL_CATALOG_API_VERSION } from '~/__tests__/cypress/cypress/support/commands/api'; import { @@ -98,6 +98,91 @@ describe('MCP Catalog Empty State', () => { }); }); +describe('MCP Catalog Empty Category Hiding', () => { + it('should hide categories that have 0 servers from toggle', () => { + const sources = [ + mockCatalogSource({ + id: 'community-mcp-source', + name: 'Community MCP Servers', + labels: ['community_mcp_servers'], + }), + mockCatalogSource({ + id: 'org-mcp-source', + name: 'Organization MCP Servers', + labels: ['organization_mcp_servers'], + }), + ]; + + cy.interceptApi( + `GET /api/:apiVersion/model_catalog/sources`, + { path: { apiVersion: MODEL_CATALOG_API_VERSION }, query: { assetType: 'mcp_servers' } }, + mockCatalogSourceList({ items: sources }), + ); + + cy.intercept( + { + method: 'GET', + url: new RegExp(`/api/${MODEL_CATALOG_API_VERSION}/model_catalog/labels`), + }, + mockModArchResponse({ + items: [ + mockCatalogLabel({ + name: 'community_mcp_servers', + displayName: 'Community MCP Servers', + }), + mockCatalogLabel({ + name: 'organization_mcp_servers', + displayName: 'Organization MCP Servers', + }), + ], + size: 2, + pageSize: 10, + nextPageToken: '', + }), + ); + + cy.interceptApi( + `GET /api/:apiVersion/mcp_catalog/mcp_servers`, + { + path: { apiVersion: MODEL_CATALOG_API_VERSION }, + query: { sourceLabel: 'community_mcp_servers' }, + }, + { items: [], size: 0, pageSize: 10, nextPageToken: '' }, + ); + + cy.interceptApi( + `GET /api/:apiVersion/mcp_catalog/mcp_servers`, + { + path: { apiVersion: MODEL_CATALOG_API_VERSION }, + query: { sourceLabel: 'organization_mcp_servers' }, + }, + { + items: [ + { + id: 'server-1', + name: 'Test Server', + description: 'test', + source_id: 'org-mcp-source', // eslint-disable-line camelcase + }, + ], + size: 1, + pageSize: 10, + nextPageToken: '', + }, + ); + + cy.intercept( + { method: 'GET', pathname: MCP_FILTER_OPTIONS_PATH }, + mockModArchResponse(mockMcpCatalogFilterOptions()), + ); + + mcpCatalog.visit(); + + cy.findByTestId('mcp-category-title-community_mcp_servers').should('not.exist'); + cy.findByTestId('mcp-category-title-organization_mcp_servers').should('be.visible'); + }); +}); + describe('MCP Catalog Error State', () => { it('should show error state when sources fail to load', () => { cy.intercept( diff --git a/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalog/modelCatalogAllModelsView.cy.ts b/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalog/modelCatalogAllModelsView.cy.ts index faa828e7e4..1707091a64 100644 --- a/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalog/modelCatalogAllModelsView.cy.ts +++ b/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalog/modelCatalogAllModelsView.cy.ts @@ -190,14 +190,44 @@ describe('Model Catalog All Models View', () => { }); describe('Empty States', () => { - it('should show empty state when category has no models', () => { + it('should hide empty categories instead of showing empty state', () => { initIntercepts({ isEmpty: true }); modelCatalog.visit(); - modelCatalog.findEmptyState('OpenVINO').scrollIntoView().should('be.visible'); - modelCatalog - .findEmptyState('OpenVINO') - .should('contain.text', 'No result foundAdjust your filters and try again.'); + modelCatalog.findEmptyState('OpenVINO').should('not.exist'); + modelCatalog.findCategoryTitle('OpenVINO').should('not.exist'); + modelCatalog.findCategoryTitle('Hugging Face').should('not.exist'); + modelCatalog.findCategoryTitle('Community').should('not.exist'); + }); + + it('should hide empty categories from toggle', () => { + initIntercepts({ + sources: [ + mockCatalogSource({ + id: 'huggingface', + name: 'Hugging Face', + labels: ['Hugging Face'], + }), + mockCatalogSource({ id: 'openvino', name: 'OpenVINO', labels: ['OpenVINO'] }), + mockCatalogSource({ id: 'community', name: 'Community', labels: ['Community'] }), + ], + includeSourcesWithoutLabels: false, + }); + + cy.interceptApi( + `GET /api/:apiVersion/model_catalog/models`, + { + path: { apiVersion: MODEL_CATALOG_API_VERSION }, + query: { sourceLabel: 'OpenVINO' }, + }, + mockCatalogModelList({ items: [] }), + ); + + modelCatalog.visit(); + + modelCatalog.findCategoryToggle('label-OpenVINO').should('not.exist'); + modelCatalog.findCategoryToggle('label-Hugging Face').should('be.visible'); + modelCatalog.findCategoryToggle('label-Community').should('be.visible'); }); }); }); diff --git a/clients/ui/frontend/src/app/context/mcpCatalog/McpCatalogContext.tsx b/clients/ui/frontend/src/app/context/mcpCatalog/McpCatalogContext.tsx index 6d9660a325..2090dee702 100644 --- a/clients/ui/frontend/src/app/context/mcpCatalog/McpCatalogContext.tsx +++ b/clients/ui/frontend/src/app/context/mcpCatalog/McpCatalogContext.tsx @@ -7,6 +7,7 @@ import useModelCatalogAPIState, { import { useCatalogSources } from '~/app/hooks/modelCatalog/useCatalogSources'; import { useCatalogLabels } from '~/app/hooks/modelCatalog/useCatalogLabels'; import { useMcpServerFilterOptionListWithAPI } from '~/app/hooks/mcpServerCatalog/useMcpServerFilterOptionList'; +import useEmptyCategoryTracking from '~/app/hooks/useEmptyCategoryTracking'; import type { McpCatalogContextType, McpCatalogPaginationState, @@ -55,6 +56,8 @@ export const McpCatalogContext = React.createContext({ filterOptions: null, filterOptionsLoaded: false, filterOptionsLoadError: undefined, + emptyCategoryLabels: new Set(), + reportCategoryEmpty: () => undefined, }); const MODEL_CATALOG_PATH = `${URL_PREFIX}/api/${BFF_API_VERSION}/model_catalog`; @@ -89,6 +92,7 @@ export const McpCatalogContextProvider: React.FC const [selectedSourceLabel, setSelectedSourceLabel] = React.useState( initialState.selectedSourceLabel, ); + const { emptyCategoryLabels, reportCategoryEmpty } = useEmptyCategoryTracking(); React.useEffect(() => { syncToUrl({ searchQuery, filters, selectedSourceLabel }); @@ -137,6 +141,8 @@ export const McpCatalogContextProvider: React.FC filterOptions, filterOptionsLoaded, filterOptionsLoadError, + emptyCategoryLabels, + reportCategoryEmpty, }), [ apiStateMcpCatalog, @@ -158,6 +164,8 @@ export const McpCatalogContextProvider: React.FC setPageSize, setTotalItems, clearAllFilters, + emptyCategoryLabels, + reportCategoryEmpty, ], ); diff --git a/clients/ui/frontend/src/app/context/modelCatalog/ModelCatalogContext.tsx b/clients/ui/frontend/src/app/context/modelCatalog/ModelCatalogContext.tsx index e728f54e83..c512f4e38d 100644 --- a/clients/ui/frontend/src/app/context/modelCatalog/ModelCatalogContext.tsx +++ b/clients/ui/frontend/src/app/context/modelCatalog/ModelCatalogContext.tsx @@ -8,6 +8,7 @@ import { useCatalogSources } from '~/app/hooks/modelCatalog/useCatalogSources'; import useModelCatalogAPIState, { ModelCatalogAPIState, } from '~/app/hooks/modelCatalog/useModelCatalogAPIState'; +import useEmptyCategoryTracking from '~/app/hooks/useEmptyCategoryTracking'; import { CatalogFilterOptionsList, CatalogLabelList, @@ -69,6 +70,8 @@ export type ModelCatalogContextType = { ) => string | number | string[] | undefined; sortBy: ModelCatalogSortOption | null; setSortBy: (sortBy: ModelCatalogSortOption | null) => void; + emptyCategoryLabels: Set; + reportCategoryEmpty: (label: string, isEmpty: boolean) => void; }; type ModelCatalogContextProviderProps = { @@ -116,6 +119,8 @@ export const ModelCatalogContext = React.createContext( getPerformanceFilterDefaultValue: () => undefined, sortBy: null, setSortBy: () => undefined, + emptyCategoryLabels: new Set(), + reportCategoryEmpty: () => undefined, }); export const ModelCatalogContextProvider: React.FC = ({ @@ -150,6 +155,7 @@ export const ModelCatalogContextProvider: React.FC(null); const [sortBy, setSortBy] = React.useState(null); + const { emptyCategoryLabels, reportCategoryEmpty } = useEmptyCategoryTracking(); const location = useLocation(); const isOnDetailsPage = location.pathname.includes(ModelDetailsTab.PERFORMANCE_INSIGHTS); @@ -350,6 +356,8 @@ export const ModelCatalogContextProvider: React.FC, + catalogSourcesLoaded: boolean, + updateSelectedSourceLabel: (label: string | undefined) => void, +): UseEffectiveCategoriesResult => { + const activeCategories = React.useMemo( + () => getActiveSourceLabels(catalogSources, catalogLabels), + [catalogSources, catalogLabels], + ); + + const effectiveActiveCategories = React.useMemo( + () => activeCategories.filter((c) => !emptyCategoryLabels.has(c)), + [activeCategories, emptyCategoryLabels], + ); + + const isSingleCategory = effectiveActiveCategories.length === 1; + const hasNoCategories = effectiveActiveCategories.length === 0; + + React.useEffect(() => { + if (catalogSourcesLoaded && isSingleCategory) { + updateSelectedSourceLabel(effectiveActiveCategories[0]); + } + }, [ + catalogSourcesLoaded, + isSingleCategory, + effectiveActiveCategories, + updateSelectedSourceLabel, + ]); + + return { effectiveActiveCategories, isSingleCategory, hasNoCategories }; +}; + +export default useEffectiveCategories; diff --git a/clients/ui/frontend/src/app/hooks/useEmptyCategoryTracking.ts b/clients/ui/frontend/src/app/hooks/useEmptyCategoryTracking.ts new file mode 100644 index 0000000000..8563114f25 --- /dev/null +++ b/clients/ui/frontend/src/app/hooks/useEmptyCategoryTracking.ts @@ -0,0 +1,33 @@ +import * as React from 'react'; + +type UseEmptyCategoryTrackingResult = { + emptyCategoryLabels: Set; + reportCategoryEmpty: (label: string, isEmpty: boolean) => void; +}; + +const useEmptyCategoryTracking = (): UseEmptyCategoryTrackingResult => { + const [emptyCategoryLabels, setEmptyCategoryLabels] = React.useState>( + () => new Set(), + ); + + const reportCategoryEmpty = React.useCallback((label: string, isEmpty: boolean) => { + setEmptyCategoryLabels((prev) => { + const hasLabel = prev.has(label); + if (isEmpty && !hasLabel) { + const next = new Set(prev); + next.add(label); + return next; + } + if (!isEmpty && hasLabel) { + const next = new Set(prev); + next.delete(label); + return next; + } + return prev; + }); + }, []); + + return { emptyCategoryLabels, reportCategoryEmpty }; +}; + +export default useEmptyCategoryTracking; diff --git a/clients/ui/frontend/src/app/hooks/useReportCategoryEmpty.ts b/clients/ui/frontend/src/app/hooks/useReportCategoryEmpty.ts new file mode 100644 index 0000000000..86bf58e974 --- /dev/null +++ b/clients/ui/frontend/src/app/hooks/useReportCategoryEmpty.ts @@ -0,0 +1,21 @@ +import * as React from 'react'; + +const useReportCategoryEmpty = ( + reportCategoryEmpty: (label: string, isEmpty: boolean) => void, + label: string, + isLoaded: boolean, + itemCount: number, + searchTerm: string, +): void => { + React.useEffect(() => { + if (!isLoaded || searchTerm) { + return undefined; + } + const timer = setTimeout(() => { + reportCategoryEmpty(label, itemCount === 0); + }, 100); + return () => clearTimeout(timer); + }, [isLoaded, itemCount, label, searchTerm, reportCategoryEmpty]); +}; + +export default useReportCategoryEmpty; diff --git a/clients/ui/frontend/src/app/pages/mcpCatalog/screens/McpCatalog.tsx b/clients/ui/frontend/src/app/pages/mcpCatalog/screens/McpCatalog.tsx index 7dee8bf365..b928228c02 100644 --- a/clients/ui/frontend/src/app/pages/mcpCatalog/screens/McpCatalog.tsx +++ b/clients/ui/frontend/src/app/pages/mcpCatalog/screens/McpCatalog.tsx @@ -7,7 +7,7 @@ import { McpCatalogContext } from '~/app/context/mcpCatalog/McpCatalogContext'; import { hasMcpFiltersApplied } from '~/app/pages/mcpCatalog/utils/mcpCatalogUtils'; import McpCatalogFilters from '~/app/pages/mcpCatalog/components/McpCatalogFilters'; import { MCP_CATALOG_TITLE, MCP_CATALOG_DESCRIPTION } from '~/app/pages/mcpCatalog/const'; -import { getActiveSourceLabels } from '~/app/pages/modelCatalog/utils/modelCatalogUtils'; +import useEffectiveCategories from '~/app/hooks/useEffectiveCategories'; import EmptyModelCatalogState from '~/app/pages/modelCatalog/EmptyModelCatalogState'; import McpCatalogSourceLabelSelector from './McpCatalogSourceLabelSelector'; import McpCatalogAllServersView from './McpCatalogAllServersView'; @@ -24,30 +24,19 @@ const McpCatalog: React.FC = () => { catalogSources, catalogLabels, catalogSourcesLoaded, + emptyCategoryLabels, } = React.useContext(McpCatalogContext); const filtersApplied = hasMcpFiltersApplied(filters, searchQuery); const isAllServersView = selectedSourceLabel === undefined && !filtersApplied; - const activeCategories = React.useMemo( - () => getActiveSourceLabels(catalogSources, catalogLabels), - [catalogSources, catalogLabels], - ); - - const isSingleCategory = activeCategories.length === 1; - const hasNoCategories = activeCategories.length === 0; - - React.useEffect(() => { - if (catalogSourcesLoaded && isSingleCategory && selectedSourceLabel !== activeCategories[0]) { - setSelectedSourceLabel(activeCategories[0]); - } - }, [ + const { effectiveActiveCategories, isSingleCategory, hasNoCategories } = useEffectiveCategories( + catalogSources, + catalogLabels, + emptyCategoryLabels, catalogSourcesLoaded, - isSingleCategory, - activeCategories, - selectedSourceLabel, setSelectedSourceLabel, - ]); + ); const handleSearch = React.useCallback( (term: string) => { @@ -103,7 +92,9 @@ const McpCatalog: React.FC = () => { )} diff --git a/clients/ui/frontend/src/app/pages/mcpCatalog/screens/McpCatalogCategorySection.tsx b/clients/ui/frontend/src/app/pages/mcpCatalog/screens/McpCatalogCategorySection.tsx index c6e71eb8dc..63ea8ffd29 100644 --- a/clients/ui/frontend/src/app/pages/mcpCatalog/screens/McpCatalogCategorySection.tsx +++ b/clients/ui/frontend/src/app/pages/mcpCatalog/screens/McpCatalogCategorySection.tsx @@ -13,6 +13,7 @@ import { } from '@patternfly/react-core'; import { ArrowRightIcon, SearchIcon } from '@patternfly/react-icons'; import { useMcpServersBySourceLabelWithAPI } from '~/app/hooks/mcpServerCatalog/useMcpServersBySourceLabel'; +import useReportCategoryEmpty from '~/app/hooks/useReportCategoryEmpty'; import { getLabelDescription, getLabelDisplayName, @@ -38,7 +39,7 @@ const McpCatalogCategorySection: React.FC = ({ pageSize, onShowMore, }) => { - const { mcpApiState, catalogLabels } = React.useContext(McpCatalogContext); + const { mcpApiState, catalogLabels, reportCategoryEmpty } = React.useContext(McpCatalogContext); const { mcpServers, mcpServersLoaded, mcpServersLoadError } = useMcpServersBySourceLabelWithAPI( mcpApiState, { @@ -58,6 +59,18 @@ const McpCatalogCategorySection: React.FC = ({ ); const description = getLabelDescription(label, catalogLabels); + useReportCategoryEmpty( + reportCategoryEmpty, + label, + mcpServersLoaded, + mcpServers.items.length, + searchTerm, + ); + + if (mcpServersLoaded && mcpServers.items.length === 0 && !searchTerm) { + return null; + } + return ( { - const { catalogSources, catalogLabels, selectedSourceLabel, setSelectedSourceLabel } = - React.useContext(McpCatalogContext); + const { + catalogSources, + catalogLabels, + selectedSourceLabel, + setSelectedSourceLabel, + emptyCategoryLabels, + } = React.useContext(McpCatalogContext); const blocks: SourceLabelBlock[] = React.useMemo(() => { if (!catalogSources) { @@ -31,20 +36,22 @@ const McpCatalogSourceLabelBlocks: React.FC = () => { const allBlock: SourceLabelBlock = { id: 'all', displayName: ALL_SERVERS_LABEL }; - const labelBlocks: SourceLabelBlock[] = orderedLabels.map((label) => ({ - id: `label-${label}`, - label, - displayName: getLabelDisplayName( + const labelBlocks: SourceLabelBlock[] = orderedLabels + .filter((label) => !emptyCategoryLabels.has(label)) + .map((label) => ({ + id: `label-${label}`, label, - catalogLabels, - OTHER_MCP_SERVERS_DISPLAY_NAME, - 'servers', - ), - })); + displayName: getLabelDisplayName( + label, + catalogLabels, + OTHER_MCP_SERVERS_DISPLAY_NAME, + 'servers', + ), + })); const result: SourceLabelBlock[] = [allBlock, ...labelBlocks]; - if (hasNoLabels) { + if (hasNoLabels && !emptyCategoryLabels.has(SourceLabel.other)) { result.push({ id: 'no-labels', label: SourceLabel.other, @@ -58,7 +65,7 @@ const McpCatalogSourceLabelBlocks: React.FC = () => { } return result; - }, [catalogSources, catalogLabels]); + }, [catalogSources, catalogLabels, emptyCategoryLabels]); if (!catalogSources) { return null; diff --git a/clients/ui/frontend/src/app/pages/mcpCatalog/screens/__tests__/McpCatalogGalleryView.spec.tsx b/clients/ui/frontend/src/app/pages/mcpCatalog/screens/__tests__/McpCatalogGalleryView.spec.tsx index cb3d24ae7c..97eebf20a1 100644 --- a/clients/ui/frontend/src/app/pages/mcpCatalog/screens/__tests__/McpCatalogGalleryView.spec.tsx +++ b/clients/ui/frontend/src/app/pages/mcpCatalog/screens/__tests__/McpCatalogGalleryView.spec.tsx @@ -49,6 +49,8 @@ const defaultContext: McpCatalogContextType = { filterOptions: null, filterOptionsLoaded: true, filterOptionsLoadError: undefined, + emptyCategoryLabels: new Set(), + reportCategoryEmpty: jest.fn(), }; const defaultHookResult: McpServersResult = { diff --git a/clients/ui/frontend/src/app/pages/mcpCatalog/types/mcpCatalogContext.ts b/clients/ui/frontend/src/app/pages/mcpCatalog/types/mcpCatalogContext.ts index 56efcb97d4..a8aa44df30 100644 --- a/clients/ui/frontend/src/app/pages/mcpCatalog/types/mcpCatalogContext.ts +++ b/clients/ui/frontend/src/app/pages/mcpCatalog/types/mcpCatalogContext.ts @@ -37,4 +37,6 @@ export type McpCatalogContextType = { filterOptions: McpCatalogFilterOptionsList | null; filterOptionsLoaded: boolean; filterOptionsLoadError: Error | undefined; + emptyCategoryLabels: Set; + reportCategoryEmpty: (label: string, isEmpty: boolean) => void; }; diff --git a/clients/ui/frontend/src/app/pages/modelCatalog/screens/CatalogCategorySection.tsx b/clients/ui/frontend/src/app/pages/modelCatalog/screens/CatalogCategorySection.tsx index 7f0b63969e..dd5f5adfa2 100644 --- a/clients/ui/frontend/src/app/pages/modelCatalog/screens/CatalogCategorySection.tsx +++ b/clients/ui/frontend/src/app/pages/modelCatalog/screens/CatalogCategorySection.tsx @@ -14,6 +14,7 @@ import React from 'react'; import { ArrowRightIcon, SearchIcon } from '@patternfly/react-icons'; import { CatalogSourceList } from '~/app/modelCatalogTypes'; import { useCatalogModelsBySources } from '~/app/hooks/modelCatalog/useCatalogModelsBySource'; +import useReportCategoryEmpty from '~/app/hooks/useReportCategoryEmpty'; import EmptyModelCatalogState from '~/app/pages/modelCatalog/EmptyModelCatalogState'; import { getLabelDescription, @@ -38,7 +39,7 @@ const CatalogCategorySection: React.FC = ({ catalogSources, onShowMore, }) => { - const { catalogLabels } = React.useContext(ModelCatalogContext); + const { catalogLabels, reportCategoryEmpty } = React.useContext(ModelCatalogContext); const { catalogModels, catalogModelsLoaded, catalogModelsLoadError } = useCatalogModelsBySources( undefined, label, @@ -48,10 +49,21 @@ const CatalogCategorySection: React.FC = ({ const itemsToDisplay = catalogModels.items.slice(0, pageSize); - // Get display name and description from labels API const categoryTitle = getLabelDisplayName(label, catalogLabels); const description = getLabelDescription(label, catalogLabels); + useReportCategoryEmpty( + reportCategoryEmpty, + label, + catalogModelsLoaded, + catalogModels.items.length, + searchTerm, + ); + + if (catalogModelsLoaded && catalogModels.items.length === 0 && !searchTerm) { + return null; + } + return ( <> diff --git a/clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelCatalog.tsx b/clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelCatalog.tsx index 14fbc50ac5..44d4df6ba6 100755 --- a/clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelCatalog.tsx +++ b/clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelCatalog.tsx @@ -7,7 +7,7 @@ import ModelCatalogFilters from '~/app/pages/modelCatalog/components/ModelCatalo import { ModelCatalogContext } from '~/app/context/modelCatalog/ModelCatalogContext'; import { CategoryName } from '~/app/modelCatalogTypes'; import { useHasVisibleFiltersApplied } from '~/app/hooks/modelCatalog/useHasVisibleFiltersApplied'; -import { getActiveSourceLabels } from '~/app/pages/modelCatalog/utils/modelCatalogUtils'; +import useEffectiveCategories from '~/app/hooks/useEffectiveCategories'; import EmptyModelCatalogState from '~/app/pages/modelCatalog/EmptyModelCatalogState'; import ModelCatalogSourceLabelSelectorNavigator from './ModelCatalogSourceLabelSelectorNavigator'; import ModelCatalogAllModelsView from './ModelCatalogAllModelsView'; @@ -22,28 +22,17 @@ const ModelCatalog: React.FC = () => { catalogSources, catalogLabels, catalogSourcesLoaded, + emptyCategoryLabels, } = React.useContext(ModelCatalogContext); const filtersApplied = useHasVisibleFiltersApplied(); - const activeCategories = React.useMemo( - () => getActiveSourceLabels(catalogSources, catalogLabels), - [catalogSources, catalogLabels], - ); - - const isSingleCategory = activeCategories.length === 1; - const hasNoCategories = activeCategories.length === 0; - - React.useEffect(() => { - if (catalogSourcesLoaded && isSingleCategory && selectedSourceLabel !== activeCategories[0]) { - updateSelectedSourceLabel(activeCategories[0]); - } - }, [ + const { effectiveActiveCategories, isSingleCategory, hasNoCategories } = useEffectiveCategories( + catalogSources, + catalogLabels, + emptyCategoryLabels, catalogSourcesLoaded, - isSingleCategory, - activeCategories, - selectedSourceLabel, updateSelectedSourceLabel, - ]); + ); const isAllModelsView = selectedSourceLabel === CategoryName.allModels && !searchTerm && !filtersApplied; @@ -99,7 +88,9 @@ const ModelCatalog: React.FC = () => { searchTerm={searchTerm} handleFilterReset={handleFilterReset} isSingleCategory={isSingleCategory} - singleCategoryLabel={isSingleCategory ? activeCategories[0] : undefined} + singleCategoryLabel={ + isSingleCategory ? effectiveActiveCategories[0] : undefined + } /> )} diff --git a/clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelCatalogSourceLabelBlocks.tsx b/clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelCatalogSourceLabelBlocks.tsx index 1c16d6ac4e..ffa3e98bfa 100644 --- a/clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelCatalogSourceLabelBlocks.tsx +++ b/clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelCatalogSourceLabelBlocks.tsx @@ -17,8 +17,13 @@ type SourceLabelBlock = { }; const ModelCatalogSourceLabelBlocks: React.FC = () => { - const { catalogSources, catalogLabels, updateSelectedSourceLabel, selectedSourceLabel } = - React.useContext(ModelCatalogContext); + const { + catalogSources, + catalogLabels, + updateSelectedSourceLabel, + selectedSourceLabel, + emptyCategoryLabels, + } = React.useContext(ModelCatalogContext); const blocks: SourceLabelBlock[] = React.useMemo(() => { if (!catalogSources) { @@ -29,7 +34,6 @@ const ModelCatalogSourceLabelBlocks: React.FC = () => { const uniqueLabels = getUniqueSourceLabels(enabledSources); const hasNoLabels = hasSourcesWithoutLabels(enabledSources); - // Order labels according to catalogLabels priority const orderedLabels = orderLabelsByPriority(uniqueLabels, catalogLabels); const allBlock: SourceLabelBlock = { @@ -38,15 +42,17 @@ const ModelCatalogSourceLabelBlocks: React.FC = () => { displayName: CategoryName.allModels, }; - const labelBlocks: SourceLabelBlock[] = orderedLabels.map((label) => ({ - id: `label-${label}`, - label, - displayName: getLabelDisplayName(label, catalogLabels), - })); + const labelBlocks: SourceLabelBlock[] = orderedLabels + .filter((label) => !emptyCategoryLabels.has(label)) + .map((label) => ({ + id: `label-${label}`, + label, + displayName: getLabelDisplayName(label, catalogLabels), + })); const blocksToReturn: SourceLabelBlock[] = [allBlock, ...labelBlocks]; - if (hasNoLabels) { + if (hasNoLabels && !emptyCategoryLabels.has(SourceLabel.other)) { const noLabelsBlock: SourceLabelBlock = { id: 'no-labels', label: SourceLabel.other, @@ -56,7 +62,7 @@ const ModelCatalogSourceLabelBlocks: React.FC = () => { } return blocksToReturn; - }, [catalogSources, catalogLabels]); + }, [catalogSources, catalogLabels, emptyCategoryLabels]); if (!catalogSources) { return null;