diff --git a/packages/model-registry/package.json b/packages/model-registry/package.json index e67b2547bb7..9b52da573de 100644 --- a/packages/model-registry/package.json +++ b/packages/model-registry/package.json @@ -23,7 +23,7 @@ "branch": "main", "src": "clients/ui", "target": "upstream", - "commit": "3e8c430880aac2fbaf0b26e74d596609a5d95163" + "commit": "acdf0135544891be7e245017356b6731e5e01e56" }, "module-federation": { "name": "modelRegistry", diff --git a/packages/model-registry/src/modelCatalog/ValidatedModelsBanner.tsx b/packages/model-registry/src/modelCatalog/ValidatedModelsBanner.tsx index 09815ce70fd..b19d51c9c65 100644 --- a/packages/model-registry/src/modelCatalog/ValidatedModelsBanner.tsx +++ b/packages/model-registry/src/modelCatalog/ValidatedModelsBanner.tsx @@ -76,7 +76,7 @@ const ValidatedModelsBanner: React.FC = () => { onClick={handleExploreClick} data-testid="explore-validated-models-button" > - Explore Red Hat AI validated models + Explore Red Hat AI - validated models diff --git a/packages/model-registry/upstream/bff/go.mod b/packages/model-registry/upstream/bff/go.mod index 3f025ea4821..96cfafc71b4 100644 --- a/packages/model-registry/upstream/bff/go.mod +++ b/packages/model-registry/upstream/bff/go.mod @@ -7,8 +7,8 @@ require ( github.com/google/uuid v1.6.0 github.com/julienschmidt/httprouter v1.3.0 github.com/kubeflow/hub/pkg/openapi v0.3.9 - github.com/onsi/ginkgo/v2 v2.29.0 - github.com/onsi/gomega v1.40.0 + github.com/onsi/ginkgo/v2 v2.31.0 + github.com/onsi/gomega v1.42.0 github.com/rs/cors v1.11.1 github.com/stretchr/testify v1.11.1 gopkg.in/yaml.v3 v3.0.1 diff --git a/packages/model-registry/upstream/bff/go.sum b/packages/model-registry/upstream/bff/go.sum index c3a2df2914f..1ef36bf01d5 100644 --- a/packages/model-registry/upstream/bff/go.sum +++ b/packages/model-registry/upstream/bff/go.sum @@ -75,10 +75,10 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.29.0 h1:rfh+ZFjgJhYWRoIqVf3Uwx/W20yLrcrE2h2GmYVRaag= -github.com/onsi/ginkgo/v2 v2.29.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= -github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc= -github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= +github.com/onsi/ginkgo/v2 v2.31.0 h1:GtuJos5DFUV9EerYJo8RhYxosYNGvOdDE5haKq6Grfs= +github.com/onsi/ginkgo/v2 v2.31.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.42.0 h1:CJby8u36xb7v34W78F8WKvqTQP7PCMIPB78IVDB73l4= +github.com/onsi/gomega v1.42.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/packages/model-registry/upstream/frontend/package-lock.json b/packages/model-registry/upstream/frontend/package-lock.json index 0d1401b59da..afa77863926 100644 --- a/packages/model-registry/upstream/frontend/package-lock.json +++ b/packages/model-registry/upstream/frontend/package-lock.json @@ -20,7 +20,7 @@ "@patternfly/react-table": "^6.4.3", "@patternfly/react-templates": "^6.4.0", "classnames": "^2.2.6", - "dompurify": "^3.4.9", + "dompurify": "^3.4.10", "js-yaml": "^4.1.1", "lodash-es": "^4.18.1", "mod-arch-core": "~1.18.0", @@ -11130,9 +11130,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.9.tgz", - "integrity": "sha512-4dPSRMRDqHvs0V4YDFCsaIZo4if5u0xM+llyxiM2fwuZFdKArUBAF3VtI2+n8NKg9P870WMdYk0UhqQNoWXbfQ==", + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.10.tgz", + "integrity": "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -13323,17 +13323,17 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "devOptional": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -13995,9 +13995,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -18190,14 +18190,14 @@ } }, "node_modules/launch-editor": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz", - "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", + "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", "dev": true, "license": "MIT", "dependencies": { "picocolors": "^1.1.1", - "shell-quote": "^1.8.3" + "shell-quote": "^1.8.4" } }, "node_modules/lazy-ass": { diff --git a/packages/model-registry/upstream/frontend/package.json b/packages/model-registry/upstream/frontend/package.json index ee77718dc4e..98fd1f5a010 100644 --- a/packages/model-registry/upstream/frontend/package.json +++ b/packages/model-registry/upstream/frontend/package.json @@ -103,7 +103,7 @@ "@patternfly/react-table": "^6.4.3", "@patternfly/react-templates": "^6.4.0", "classnames": "^2.2.6", - "dompurify": "^3.4.9", + "dompurify": "^3.4.10", "js-yaml": "^4.1.1", "lodash-es": "^4.18.1", "mod-arch-core": "~1.18.0", diff --git a/packages/model-registry/upstream/frontend/src/__mocks__/mockCatalogLabelList.ts b/packages/model-registry/upstream/frontend/src/__mocks__/mockCatalogLabelList.ts index 505aeb1e8c2..43c79fc9469 100644 --- a/packages/model-registry/upstream/frontend/src/__mocks__/mockCatalogLabelList.ts +++ b/packages/model-registry/upstream/frontend/src/__mocks__/mockCatalogLabelList.ts @@ -17,9 +17,9 @@ export const mockCatalogLabelList = (partial?: Partial): Catal }), mockCatalogLabel({ name: 'Red Hat AI Validated', - displayName: 'Red Hat AI Validated models', + displayName: 'Red Hat AI - validated models', description: - 'Validated models are benchmarked for performance and quality using leading open source evaluation datasets. Some of these include tested runtime arguments for enabling additional capabilities.', + 'Third-party models verified by Red Hat to ensure reliable performance and compatibility. Some of these include validated runtime arguments for enabling additional capabilities.', }), mockCatalogLabel({ name: 'Sample category 1', diff --git a/packages/model-registry/upstream/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalogSettings/modelCatalogPerformanceFiltersApi.cy.ts b/packages/model-registry/upstream/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalogSettings/modelCatalogPerformanceFiltersApi.cy.ts index 9db30b0b7fc..ee0cd4eaebd 100644 --- a/packages/model-registry/upstream/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalogSettings/modelCatalogPerformanceFiltersApi.cy.ts +++ b/packages/model-registry/upstream/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalogSettings/modelCatalogPerformanceFiltersApi.cy.ts @@ -273,16 +273,23 @@ describe('Model Catalog Performance Filters API Behavior', () => { cy.findByTestId(PERFORMANCE_FILTER_TEST_IDS.hardwareTable).should('exist'); - // Change a filter to ensure something is set + // Change workload type filter changeWorkloadTypeFilter(); - // Click Clear all filters button in the toolbar (PatternFly's native button) + // Apply cold start filter (applies with default max value) + modelCatalog.openColdStartLatencyFilter(); + modelCatalog.applyColdStartLatencyFilter(); + + // Click Reset all defaults button in the toolbar cy.findByRole('button', { name: 'Reset all defaults' }).click(); - // Verify filters are reset to defaults - workload type should NOT show Code Fixing + // Verify workload type is reset - should NOT show Code Fixing cy.findByTestId(PERFORMANCE_FILTER_TEST_IDS.workloadType) .should('be.visible') .and('not.contain.text', 'Code Fixing'); + + // Verify cold start filter is still visible and reset to default + cy.findByTestId(PERFORMANCE_FILTER_TEST_IDS.coldStartLoadTime).should('be.visible'); }); it('should reset latency filter when Reset all filters is clicked', () => { @@ -322,25 +329,6 @@ describe('Model Catalog Performance Filters API Behavior', () => { }); }); - it('should NOT include cold_start_time_to_load_seconds after toggle is turned OFF', () => { - visitWithPerformanceToggle(true); - - modelCatalog.openColdStartLatencyFilter(); - modelCatalog.applyColdStartLatencyFilter(); - - modelCatalog.togglePerformanceView(); - modelCatalog.findLoadingState().should('not.exist'); - - cy.intercept('GET', '**/model_catalog/models*').as('getModelsWithoutColdStart'); - - triggerFilterRefresh(); - - cy.wait('@getModelsWithoutColdStart').then((interception) => { - const decodedUrl = decodeURIComponent(interception.request.url); - expect(decodedUrl).to.not.include('cold_start_time_to_load_seconds'); - }); - }); - it('should pass cold_start_time_to_load_seconds as orderBy when cold start sort is selected', () => { visitWithPerformanceToggle(true); @@ -393,7 +381,7 @@ describe('Model Catalog Performance Filters API Behavior', () => { }); }); - it('should NOT include min_vram_gb after toggle is turned OFF', () => { + it('should still include min_vram_gb after toggle is turned OFF (basic filter)', () => { visitWithPerformanceToggle(true); cy.findByTestId('minimum-vram-filter').scrollIntoView(); @@ -403,17 +391,17 @@ describe('Model Catalog Performance Filters API Behavior', () => { modelCatalog.togglePerformanceView(); modelCatalog.findLoadingState().should('not.exist'); - cy.intercept('GET', '**/model_catalog/models*').as('getModelsWithoutVram'); + cy.intercept('GET', '**/model_catalog/models*').as('getModelsWithVram'); triggerFilterRefresh(); - cy.wait('@getModelsWithoutVram').then((interception) => { + cy.wait('@getModelsWithVram').then((interception) => { const decodedUrl = decodeURIComponent(interception.request.url); - expect(decodedUrl).to.not.include('min_vram_gb'); + expect(decodedUrl).to.include('min_vram_gb'); }); }); - it('should NOT include modelcar_image_size after toggle is turned OFF', () => { + it('should still include modelcar_image_size after toggle is turned OFF (basic filter)', () => { visitWithPerformanceToggle(true); cy.findByTestId('container-size-filter').scrollIntoView(); @@ -423,13 +411,13 @@ describe('Model Catalog Performance Filters API Behavior', () => { modelCatalog.togglePerformanceView(); modelCatalog.findLoadingState().should('not.exist'); - cy.intercept('GET', '**/model_catalog/models*').as('getModelsWithoutContainerSize'); + cy.intercept('GET', '**/model_catalog/models*').as('getModelsWithContainerSize'); triggerFilterRefresh(); - cy.wait('@getModelsWithoutContainerSize').then((interception) => { + cy.wait('@getModelsWithContainerSize').then((interception) => { const decodedUrl = decodeURIComponent(interception.request.url); - expect(decodedUrl).to.not.include('modelcar_image_size'); + expect(decodedUrl).to.include('modelcar_image_size'); }); }); }); diff --git a/packages/model-registry/upstream/frontend/src/app/api/modelCatalog/service.ts b/packages/model-registry/upstream/frontend/src/app/api/modelCatalog/service.ts index fc02a625f88..865a13b5c31 100644 --- a/packages/model-registry/upstream/frontend/src/app/api/modelCatalog/service.ts +++ b/packages/model-registry/upstream/frontend/src/app/api/modelCatalog/service.ts @@ -38,7 +38,9 @@ export const getCatalogModelsBySource = ): Promise => { const computedFilterQuery = filterQuery ?? - (filterData && filterOptions ? filtersToFilterQuery(filterData, filterOptions) : ''); + (filterData && filterOptions + ? filtersToFilterQuery(filterData, filterOptions, 'models', !!performanceParams) + : ''); const allParams = { source: sourceId, diff --git a/packages/model-registry/upstream/frontend/src/app/context/modelCatalog/ModelCatalogContext.tsx b/packages/model-registry/upstream/frontend/src/app/context/modelCatalog/ModelCatalogContext.tsx index 01f7e5cfdd0..1f9530a6030 100644 --- a/packages/model-registry/upstream/frontend/src/app/context/modelCatalog/ModelCatalogContext.tsx +++ b/packages/model-registry/upstream/frontend/src/app/context/modelCatalog/ModelCatalogContext.tsx @@ -134,8 +134,8 @@ function useModelCatalogSetup(providerState: CatalogProviderState) { baseSetFilterData(latencyKey, undefined); }); baseSetFilterData(ModelCatalogStringFilterKey.HARDWARE_CONFIGURATION, []); - baseSetFilterData(ModelCatalogNumberFilterKey.MIN_VRAM, undefined); - baseSetFilterData(ModelCatalogNumberFilterKey.IMAGE_SIZE, undefined); + baseSetFilterData(ModelCatalogNumberFilterKey.MAX_RPS, undefined); + baseSetFilterData(ModelCatalogNumberFilterKey.COLD_START_LOAD_TIME, undefined); // Then apply all defaults from namedQueries const defaultQuery = filterOptions?.namedQueries?.[DEFAULT_PERFORMANCE_FILTERS_QUERY_NAME]; @@ -159,6 +159,8 @@ function useModelCatalogSetup(providerState: CatalogProviderState) { baseSetFilterData(ModelCatalogStringFilterKey.LANGUAGE, []); baseSetFilterData(ModelCatalogStringFilterKey.TENSOR_TYPE, []); baseSetFilterData(ModelCatalogStringFilterKey.VALIDATED_CONFIGURATION, []); + baseSetFilterData(ModelCatalogNumberFilterKey.MIN_VRAM, undefined); + baseSetFilterData(ModelCatalogNumberFilterKey.IMAGE_SIZE, undefined); }, [baseSetFilterData]); /** diff --git a/packages/model-registry/upstream/frontend/src/app/modelCatalogTypes.ts b/packages/model-registry/upstream/frontend/src/app/modelCatalogTypes.ts index b412e77a5a6..44552bb321e 100644 --- a/packages/model-registry/upstream/frontend/src/app/modelCatalogTypes.ts +++ b/packages/model-registry/upstream/frontend/src/app/modelCatalogTypes.ts @@ -164,6 +164,12 @@ export type PerformanceMetricsCustomProperties = { // Computed properties when targetRPS is provided replicas?: ModelRegistryCustomPropertyInt; total_requests_per_second?: ModelRegistryCustomPropertyDouble; + // Cold-start sub-type fields (returned by API with metricsType "performance-metrics") + performance_sub_type?: ModelRegistryCustomPropertyString; + gpu_type?: ModelRegistryCustomPropertyString; + gpu_count?: ModelRegistryCustomPropertyInt; + cold_start_time_to_load_seconds?: ModelRegistryCustomPropertyDouble; + runtime_command?: ModelRegistryCustomPropertyString; } & Partial>; export type AccuracyMetricsCustomProperties = { diff --git a/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/HardwareConfigurationTable.tsx b/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/HardwareConfigurationTable.tsx index db014e03e09..182c29d8f29 100644 --- a/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/HardwareConfigurationTable.tsx +++ b/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/HardwareConfigurationTable.tsx @@ -2,10 +2,13 @@ import * as React from 'react'; import { DashboardEmptyTableView, Table, ManageColumnsModal } from 'mod-arch-shared'; import { Button, Spinner } from '@patternfly/react-core'; import { ColumnsIcon } from '@patternfly/react-icons'; -import { OuterScrollContainer } from '@patternfly/react-table'; +import { InnerScrollContainer } from '@patternfly/react-table'; import { CatalogPerformanceMetricsArtifact, HardwareConfiguration } from '~/app/modelCatalogTypes'; import { ModelCatalogContext } from '~/app/context/modelCatalog/ModelCatalogContext'; -import { getActiveLatencyFieldName } from '~/app/pages/modelCatalog/utils/modelCatalogUtils'; +import { + getActiveLatencyFieldName, + findMatchingHardwareConfig, +} from '~/app/pages/modelCatalog/utils/modelCatalogUtils'; import { getStringValue } from '~/app/utils'; import { SortOrder, PerformancePropertyKey } from '~/concepts/modelCatalog/const'; import { @@ -106,15 +109,13 @@ const HardwareConfigurationTable: React.FC = ({ return ( <> - + {toolbarContent} + = ({ artifact.customProperties, PerformancePropertyKey.HARDWARE_TYPE, ); - const matched = hardwareConfigurations?.find( - (c) => hwConfig.startsWith(c.gpu_type) || c.gpu_type === hwType, - ); + const hwCount = artifact.customProperties?.hardware_count?.int_value + ? parseInt(artifact.customProperties.hardware_count.int_value, 10) + : 1; return ( ); }} /> - + { - const configs = getHardwareConfigurationsFromCustomProperties(customProperties); - const match = configs.find( - (c: HardwareConfiguration) => hwConfig.startsWith(c.gpu_type) || c.gpu_type === hwType, - ); - return match?.cold_start_time_to_load_seconds; -}; - type ModelCatalogCardBodyProps = { model: CatalogModel; isValidated: boolean; @@ -108,8 +98,16 @@ const ModelCatalogCardBody: React.FC = ({ isValidated, // Only fetch if validated ); - // Performance artifacts are already filtered by the server endpoint - const performanceMetrics = performanceArtifactsList.items; + // Separate cold-start artifacts from regular performance artifacts + const performanceMetrics = React.useMemo( + () => filterRegularPerformanceArtifacts(performanceArtifactsList.items), + [performanceArtifactsList.items], + ); + + const hardwareConfigurations = React.useMemo( + () => resolveHardwareConfigurations(performanceArtifactsList.items, model.customProperties), + [performanceArtifactsList.items, model.customProperties], + ); // NOTE: Accuracy metrics are not currently returned by the /performance_artifacts endpoint. // This is kept as a placeholder for when accuracy metrics support is restored. @@ -211,11 +209,13 @@ const ModelCatalogCardBody: React.FC = ({ ? parseLatencyFilterKey(activeLatencyField).metric : LatencyMetric.TTFT; - const matchedColdStart = findMatchedColdStart( - model.customProperties, + const matchedConfig = findMatchingHardwareConfig( + hardwareConfigurations, metrics.hardwareConfiguration, metrics.hardwareType, + parseInt(metrics.hardwareCount, 10) || 1, ); + const matchedColdStart = matchedConfig?.cold_start_time_to_load_seconds; return ( diff --git a/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/ModelCatalogFilters.tsx b/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/ModelCatalogFilters.tsx index a430bdab4b5..9e661c9e715 100644 --- a/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/ModelCatalogFilters.tsx +++ b/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/ModelCatalogFilters.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Content, ContentVariants, Flex } from '@patternfly/react-core'; +import { Content, ContentVariants, Divider, Flex } from '@patternfly/react-core'; import { ModelCatalogContext } from '~/app/context/modelCatalog/ModelCatalogContext'; import { ModelCatalogNumberFilterKey, @@ -141,6 +141,7 @@ const ModelCatalogFilters: React.FC = () => { fallbackMin={4} fallbackMax={480} /> + = ({ provider, labels = [], numLabels, -}) => ( - - {tasks.map((task) => { - const isValidatedTask = validatedTasks?.includes(task); - return ( - + ); +}; export default ModelCatalogLabels; diff --git a/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/SidebarSliderFilter.tsx b/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/SidebarSliderFilter.tsx index 0a713c24b7d..507ea67ab5d 100644 --- a/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/SidebarSliderFilter.tsx +++ b/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/SidebarSliderFilter.tsx @@ -37,7 +37,7 @@ const SidebarSliderFilter: React.FC = ({ if (option && option.range) { const { min, max } = option.range; if (min != null && max != null) { - return { min, max }; + return { min: Math.floor(min), max: Math.ceil(max) }; } } return { min: fallbackMin, max: fallbackMax }; @@ -116,6 +116,7 @@ const SidebarSliderFilter: React.FC = ({ suffix={suffix} ariaLabel={`${label} filter value`} showBoundaries + shouldRound /> diff --git a/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/globalFilters/SliderWithInput.tsx b/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/globalFilters/SliderWithInput.tsx index 576d4d4def2..3900990a7dd 100644 --- a/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/globalFilters/SliderWithInput.tsx +++ b/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/components/globalFilters/SliderWithInput.tsx @@ -27,7 +27,7 @@ const SliderWithInput: React.FC = ({ hasTooltipOverThumb = false, }) => { const roundValue = React.useCallback( - (val: number) => (shouldRound ? Math.ceil(val * 100) / 100 : val), + (val: number) => (shouldRound ? Math.round(val) : val), [shouldRound], ); diff --git a/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/screens/PerformanceInsightsView.tsx b/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/screens/PerformanceInsightsView.tsx index 4274e4992af..b7bf4670a7f 100644 --- a/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/screens/PerformanceInsightsView.tsx +++ b/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/screens/PerformanceInsightsView.tsx @@ -29,7 +29,8 @@ import { import { decodeParams, getActiveLatencyFieldName, - getHardwareConfigurationsFromCustomProperties, + filterRegularPerformanceArtifacts, + resolveHardwareConfigurations, } from '~/app/pages/modelCatalog/utils/modelCatalogUtils'; import { getDefaultFiltersFromNamedQuery } from '~/app/pages/modelCatalog/utils/performanceFilterUtils'; import TensorTypeComparisonCard from './TensorTypeComparisonCard'; @@ -130,9 +131,14 @@ const PerformanceInsightsView: React.FC = ({ model model.name, ]); + const regularPerformanceArtifacts = React.useMemo( + () => filterRegularPerformanceArtifacts(performanceArtifacts.items), + [performanceArtifacts.items], + ); + const hardwareConfigurations = React.useMemo( - () => getHardwareConfigurationsFromCustomProperties(model.customProperties), - [model.customProperties], + () => resolveHardwareConfigurations(performanceArtifacts.items, model.customProperties), + [performanceArtifacts.items, model.customProperties], ); if (performanceArtifactsError) { @@ -168,7 +174,7 @@ const PerformanceInsightsView: React.FC = ({ model { }); it('handles a single array of a single data point', () => { + const orSuffix = " OR artifacts.performance_sub_type.string_value='cold-start'"; expect( filtersToFilterQuery( mockFormData({ tasks: [ModelCatalogTask.TEXT_TO_TEXT] }), mockFilterOptions, ), - ).toBe("tasks='text-to-text'"); + ).toBe(`tasks='text-to-text'${orSuffix}`); expect( filtersToFilterQuery( mockFormData({ provider: [ModelCatalogProvider.GOOGLE] }), mockFilterOptions, ), - ).toBe("provider='Google'"); + ).toBe(`provider='Google'${orSuffix}`); expect( filtersToFilterQuery(mockFormData({ license: ['Apache 2.0'] }), mockFilterOptions), - ).toBe("license='Apache 2.0'"); + ).toBe(`license='Apache 2.0'${orSuffix}`); expect( filtersToFilterQuery(mockFormData({ language: [AllLanguageCode.CA] }), mockFilterOptions), - ).toBe("language='ca'"); + ).toBe(`language='ca'${orSuffix}`); expect( filtersToFilterQuery( mockFormData({ use_case: [UseCaseOptionValue.CHATBOT] }), mockFilterOptions, ), - ).toBe("artifacts.use_case.string_value='chatbot'"); + ).toBe(`artifacts.use_case.string_value='chatbot'${orSuffix}`); expect( filtersToFilterQuery( mockFormData({ tensor_type: [ModelCatalogTensorType.FP16] }), mockFilterOptions, ), - ).toBe("tensor_type.string_value='FP16'"); + ).toBe(`tensor_type.string_value='FP16'${orSuffix}`); }); it('handles multiple arrays of a single data point', () => { + const orSuffix = " OR artifacts.performance_sub_type.string_value='cold-start'"; expect( filtersToFilterQuery( mockFormData({ @@ -296,47 +298,48 @@ describe('filtersToFilterQuery', () => { }), mockFilterOptions, ), - ).toBe("tasks='text-to-text' AND license='Apache 2.0'"); + ).toBe(`tasks='text-to-text' AND license='Apache 2.0'${orSuffix}`); expect( filtersToFilterQuery( mockFormData({ provider: [ModelCatalogProvider.GOOGLE], language: [AllLanguageCode.CA] }), mockFilterOptions, ), - ).toBe("provider='Google' AND language='ca'"); + ).toBe(`provider='Google' AND language='ca'${orSuffix}`); }); it('handles a single array with multiple data points', () => { + const orSuffix = " OR artifacts.performance_sub_type.string_value='cold-start'"; expect( filtersToFilterQuery( mockFormData({ tasks: [ModelCatalogTask.TEXT_TO_TEXT, ModelCatalogTask.IMAGE_TO_TEXT] }), mockFilterOptions, ), - ).toBe("tasks IN ('text-to-text','image-to-text')"); + ).toBe(`tasks IN ('text-to-text','image-to-text')${orSuffix}`); expect( filtersToFilterQuery( mockFormData({ provider: [ModelCatalogProvider.GOOGLE, ModelCatalogProvider.DEEPSEEK] }), mockFilterOptions, ), - ).toBe("provider IN ('Google','DeepSeek')"); + ).toBe(`provider IN ('Google','DeepSeek')${orSuffix}`); expect( filtersToFilterQuery(mockFormData({ license: ['Apache 2.0', 'MIT'] }), mockFilterOptions), - ).toBe("license IN ('Apache 2.0','MIT')"); + ).toBe(`license IN ('Apache 2.0','MIT')${orSuffix}`); expect( filtersToFilterQuery( mockFormData({ language: [AllLanguageCode.CA, AllLanguageCode.PT] }), mockFilterOptions, ), - ).toBe("language IN ('ca','pt')"); + ).toBe(`language IN ('ca','pt')${orSuffix}`); expect( filtersToFilterQuery( mockFormData({ tensor_type: [ModelCatalogTensorType.FP16, ModelCatalogTensorType.FP8] }), mockFilterOptions, ), - ).toBe("tensor_type.string_value IN ('FP16','FP8')"); - // Note: use_case is now single-select, so multi-select test is not applicable + ).toBe(`tensor_type.string_value IN ('FP16','FP8')${orSuffix}`); }); it('handles all tensor type enum values', () => { + const orSuffix = " OR artifacts.performance_sub_type.string_value='cold-start'"; expect( filtersToFilterQuery( mockFormData({ @@ -350,10 +353,11 @@ describe('filtersToFilterQuery', () => { }), mockFilterOptions, ), - ).toBe("tensor_type.string_value IN ('FP16','FP8','INT4','INT8','MXFP4')"); + ).toBe(`tensor_type.string_value IN ('FP16','FP8','INT4','INT8','MXFP4')${orSuffix}`); }); it('handles multiple arrays with mixed count of data points', () => { + const orSuffix = " OR artifacts.performance_sub_type.string_value='cold-start'"; expect( filtersToFilterQuery( mockFormData({ @@ -370,11 +374,12 @@ describe('filtersToFilterQuery', () => { mockFilterOptions, ), ).toBe( - "tasks IN ('text-to-text','image-to-text') AND provider='Google' AND license='MIT' AND language IN ('ca','pt','vi','zsm')", + `tasks IN ('text-to-text','image-to-text') AND provider='Google' AND license='MIT' AND language IN ('ca','pt','vi','zsm')${orSuffix}`, ); }); it('handles tensor type combined with other basic filters', () => { + const orSuffix = " OR artifacts.performance_sub_type.string_value='cold-start'"; expect( filtersToFilterQuery( mockFormData({ @@ -385,28 +390,31 @@ describe('filtersToFilterQuery', () => { mockFilterOptions, ), ).toBe( - "tasks='text-to-text' AND provider='Google' AND tensor_type.string_value IN ('FP16','INT8')", + `tasks='text-to-text' AND provider='Google' AND tensor_type.string_value IN ('FP16','INT8')${orSuffix}`, ); }); }); describe('match-all (AND logic) filters', () => { it('handles a single validated configuration value', () => { + const orSuffix = " OR artifacts.performance_sub_type.string_value='cold-start'"; expect( filtersToFilterQuery(mockFormData({ validatedTasks: ['tool-calling'] }), mockFilterOptions), - ).toBe("validated_tasks='tool-calling'"); + ).toBe(`validated_tasks='tool-calling'${orSuffix}`); }); it('handles multiple validated configuration values with AND logic instead of IN', () => { + const orSuffix = " OR artifacts.performance_sub_type.string_value='cold-start'"; expect( filtersToFilterQuery( mockFormData({ validatedTasks: ['tool-calling', 'text-generation'] }), mockFilterOptions, ), - ).toBe("validated_tasks='tool-calling' AND validated_tasks='text-generation'"); + ).toBe(`validated_tasks='tool-calling' AND validated_tasks='text-generation'${orSuffix}`); }); it('handles validated configuration combined with other OR-logic filters', () => { + const orSuffix = " OR artifacts.performance_sub_type.string_value='cold-start'"; expect( filtersToFilterQuery( mockFormData({ @@ -417,7 +425,7 @@ describe('filtersToFilterQuery', () => { mockFilterOptions, ), ).toBe( - "tasks IN ('text-to-text','image-to-text') AND provider='Google' AND validated_tasks='tool-calling' AND validated_tasks='text-generation'", + `tasks IN ('text-to-text','image-to-text') AND provider='Google' AND validated_tasks='tool-calling' AND validated_tasks='text-generation'${orSuffix}`, ); }); }); @@ -481,6 +489,44 @@ describe('filtersToFilterQuery', () => { expect(query).toContain('modelcar_image_size.double_value <= 10'); }); }); + + describe('includeColdStartClause parameter', () => { + it('does not append cold-start OR clause when includeColdStartClause is false', () => { + const query = filtersToFilterQuery( + mockFormData({ tasks: [ModelCatalogTask.TEXT_TO_TEXT] }), + mockFilterOptions, + 'models', + false, + ); + expect(query).toBe("tasks='text-to-text'"); + expect(query).not.toContain('performance_sub_type'); + expect(query).not.toContain('cold-start'); + }); + + it('appends cold-start OR clause when includeColdStartClause is true (default)', () => { + const query = filtersToFilterQuery( + mockFormData({ tasks: [ModelCatalogTask.TEXT_TO_TEXT] }), + mockFilterOptions, + 'models', + true, + ); + expect(query).toContain("OR artifacts.performance_sub_type.string_value='cold-start'"); + }); + + it('does not append cold-start OR clause with multiple basic filters when performance is off', () => { + const query = filtersToFilterQuery( + mockFormData({ + tasks: [ModelCatalogTask.TEXT_TO_TEXT], + provider: [ModelCatalogProvider.GOOGLE], + }), + mockFilterOptions, + 'models', + false, + ); + expect(query).toBe("tasks='text-to-text' AND provider='Google'"); + expect(query).not.toContain('performance_sub_type'); + }); + }); }); describe('catalog source filtering utilities', () => { diff --git a/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/utils/modelCatalogUtils.ts b/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/utils/modelCatalogUtils.ts index b6b83ca03ea..00aa83afcf6 100644 --- a/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/utils/modelCatalogUtils.ts +++ b/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/utils/modelCatalogUtils.ts @@ -9,6 +9,7 @@ import { CatalogModel, CatalogModelArtifact, CatalogModelDetailsParams, + CatalogPerformanceMetricsArtifact, CatalogSource, CatalogSourceList, HardwareConfiguration, @@ -365,6 +366,8 @@ export type FilterQueryTarget = 'models' | 'artifacts'; * Determines if a filter should be included based on the target endpoint. * - For models: Include all filters except RPS (which is passed as a separate param) * - For artifacts: Only include filters that have the artifacts.* prefix + * Cold-start load time is excluded from the main AND clause for both targets; + * it is placed in the OR clause via buildColdStartOrClause. */ const shouldIncludeFilter = (filterId: string, target: FilterQueryTarget): boolean => { // RPS is always passed as a separate param, not in filterQuery @@ -372,14 +375,13 @@ const shouldIncludeFilter = (filterId: string, target: FilterQueryTarget): boole return false; } - // Cold-start filter is excluded from the performance artifacts endpoint - // (performance-metrics artifacts don't have this field — it's on cold-start-metrics artifacts). - if (filterId === ModelCatalogNumberFilterKey.COLD_START_LOAD_TIME && target === 'artifacts') { + // Cold-start filter is excluded from the main AND clause for both targets. + // It is placed in the OR clause via buildColdStartOrClause. + if (filterId === ModelCatalogNumberFilterKey.COLD_START_LOAD_TIME) { return false; } if (target === 'models') { - // For models, include all filters (except RPS and cold-start which are already excluded) return true; } @@ -454,6 +456,8 @@ const serializeFilterEntry = ( * @param target - The target endpoint: * - 'models': Include all filters (except RPS), use filter keys directly * - 'artifacts': Only include artifact-prefixed filters, strip the prefix in output + * @param includeColdStartClause - Whether to append the cold-start OR clause. + * Should be true only when performance view is enabled. * * Note: RPS is NOT included in filterQuery for either target - it's passed as targetRPS param. */ @@ -461,13 +465,55 @@ export const filtersToFilterQuery = ( filterData: ModelCatalogFilterStates, options: CatalogFilterOptionsList, target: FilterQueryTarget = 'models', + includeColdStartClause = true, ): string => { const serializedFilters: string[] = Object.entries(filterData) .filter(([filterId]) => shouldIncludeFilter(filterId, target)) .map(([filterId, data]) => serializeFilterEntry(filterId, data, options, target)); const nonEmptyFilters = serializedFilters.filter((v) => !!v); - return nonEmptyFilters.length === 0 ? '' : nonEmptyFilters.join(' AND '); + if (nonEmptyFilters.length === 0) { + return ''; + } + + const baseQuery = nonEmptyFilters.join(' AND '); + + if (!includeColdStartClause) { + return baseQuery; + } + + const coldStartClause = buildColdStartOrClause(filterData, options, target); + return `${baseQuery} OR ${coldStartClause}`; +}; + +/** + * Builds the OR clause for cold-start artifacts in the filter query. + * Always includes `performance_sub_type.string_value='cold-start'`. + * If a cold start load time filter is active, appends it as an AND condition. + * Uses the appropriate key format based on target (with artifacts.* prefix for models, stripped for artifacts). + */ +const buildColdStartOrClause = ( + filterData: ModelCatalogFilterStates, + options: CatalogFilterOptionsList, + target: FilterQueryTarget, +): string => { + const subTypePrefix = target === 'models' ? 'artifacts.' : ''; + const subTypeFilter = `${subTypePrefix}performance_sub_type.string_value='cold-start'`; + const coldStartValue = filterData[ModelCatalogNumberFilterKey.COLD_START_LOAD_TIME]; + + if (coldStartValue === undefined || typeof coldStartValue !== 'number') { + return subTypeFilter; + } + + const operator = getNumericFilterOperator( + options, + ModelCatalogNumberFilterKey.COLD_START_LOAD_TIME, + ); + const coldStartKey = + target === 'models' + ? ModelCatalogNumberFilterKey.COLD_START_LOAD_TIME + : stripArtifactsPrefix(ModelCatalogNumberFilterKey.COLD_START_LOAD_TIME); + return `${subTypeFilter} AND ${coldStartKey} ${operator} ${coldStartValue}`; }; /** @@ -847,3 +893,72 @@ export const getHardwareConfigurationsFromCustomProperties = ( return []; } }; + +/** + * Identifies whether a performance artifact is a cold-start sub-type. + */ +export const isColdStartArtifact = (artifact: CatalogPerformanceMetricsArtifact): boolean => { + const subType = artifact.customProperties?.performance_sub_type; + return ( + subType?.metadataType === ModelRegistryMetadataType.STRING && + subType.string_value === 'cold-start' + ); +}; + +/** + * Extracts HardwareConfiguration[] from cold-start performance artifacts. + * Cold-start artifacts are identified by `performance_sub_type === "cold-start"` in customProperties + * and contain gpu_type, gpu_count, cold_start_time_to_load_seconds, and runtime_command. + */ +export const getHardwareConfigurationsFromArtifacts = ( + artifacts: CatalogPerformanceMetricsArtifact[], +): HardwareConfiguration[] => + artifacts.filter(isColdStartArtifact).map((artifact) => { + const props = artifact.customProperties; + return { + // eslint-disable-next-line camelcase + gpu_type: props?.gpu_type?.string_value ?? '', + // eslint-disable-next-line camelcase + gpu_count: props?.gpu_count?.int_value ? parseInt(props.gpu_count.int_value, 10) : 0, + // eslint-disable-next-line camelcase + cold_start_time_to_load_seconds: props?.cold_start_time_to_load_seconds?.double_value ?? 0, + // eslint-disable-next-line camelcase + runtime_command: props?.runtime_command?.string_value ?? '', + }; + }); + +/** + * Filters out cold-start artifacts, returning only regular performance artifacts. + */ +export const filterRegularPerformanceArtifacts = ( + artifacts: CatalogPerformanceMetricsArtifact[], +): CatalogPerformanceMetricsArtifact[] => artifacts.filter((a) => !isColdStartArtifact(a)); + +/** + * Finds the matching HardwareConfiguration for a given hardware description. + * Requires an exact match on both gpu_type and gpu_count; returns undefined otherwise. + */ +export const findMatchingHardwareConfig = ( + configs: HardwareConfiguration[], + hwConfig: string, + hwType: string, + hwCount: number, +): HardwareConfiguration | undefined => + configs.find( + (c) => (hwConfig.startsWith(c.gpu_type) || c.gpu_type === hwType) && c.gpu_count === hwCount, + ); + +/** + * Resolves hardware configurations from performance artifacts (cold-start sub-type), + * falling back to the model's customProperties when no artifact-based configs exist. + */ +export const resolveHardwareConfigurations = ( + artifacts: CatalogPerformanceMetricsArtifact[], + customProperties?: ModelRegistryCustomProperties, +): HardwareConfiguration[] => { + const fromArtifacts = getHardwareConfigurationsFromArtifacts(artifacts); + if (fromArtifacts.length > 0) { + return fromArtifacts; + } + return getHardwareConfigurationsFromCustomProperties(customProperties); +}; diff --git a/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/utils/performanceFilterUtils.ts b/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/utils/performanceFilterUtils.ts index fac9295582c..aaf3c31042a 100644 --- a/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/utils/performanceFilterUtils.ts +++ b/packages/model-registry/upstream/frontend/src/app/pages/modelCatalog/utils/performanceFilterUtils.ts @@ -199,8 +199,12 @@ export const getDefaultFiltersFromNamedQuery = ( if (resolvedValue !== undefined) { if (fieldName === ModelCatalogNumberFilterKey.MAX_RPS) { result[ModelCatalogNumberFilterKey.MAX_RPS] = resolvedValue; - } else { + } else if (fieldName === ModelCatalogNumberFilterKey.COLD_START_LOAD_TIME) { result[ModelCatalogNumberFilterKey.COLD_START_LOAD_TIME] = resolvedValue; + } else if (fieldName === ModelCatalogNumberFilterKey.MIN_VRAM) { + result[ModelCatalogNumberFilterKey.MIN_VRAM] = resolvedValue; + } else { + result[ModelCatalogNumberFilterKey.IMAGE_SIZE] = resolvedValue; } } return; diff --git a/packages/model-registry/upstream/frontend/src/concepts/modelCatalog/const.ts b/packages/model-registry/upstream/frontend/src/concepts/modelCatalog/const.ts index 3230945e791..c610936663f 100644 --- a/packages/model-registry/upstream/frontend/src/concepts/modelCatalog/const.ts +++ b/packages/model-registry/upstream/frontend/src/concepts/modelCatalog/const.ts @@ -262,7 +262,7 @@ export enum ModelCatalogTensorType { export const MODEL_CATALOG_POPOVER_MESSAGES = { VALIDATED: - 'Validated models are benchmarked for performance and quality using leading open source evaluation datasets. Some of these include tested runtime arguments for enabling additional capabilities.', + 'Validated models undergo comprehensive benchmarking to ensure reliable performance and compatibility. Some of these include validated runtime arguments for enabling additional capabilities.', RED_HAT: 'Red Hat AI models are provided and supported by Red Hat.', } as const; @@ -489,8 +489,9 @@ export const MATCH_ALL_FILTER_KEYS: ModelCatalogStringFilterKey[] = [ export const DEPLOYMENT_RESOURCE_PREFIXES = ['vllm']; /** - * Performance filter keys that are shown when performance view is enabled. + * Performance filter keys that are shown as chips in the performance toolbar. * These filters should reset to default values (from namedQueries) instead of clearing. + * Note: MIN_VRAM and IMAGE_SIZE are sidebar-only filters (not shown as chips on the details page). * Note: HARDWARE_CONFIGURATION is NOT included here because it should clear normally * like basic filters, not reset to defaults. */ @@ -498,8 +499,6 @@ export const PERFORMANCE_FILTER_KEYS: ModelCatalogFilterKey[] = [ ModelCatalogStringFilterKey.USE_CASE, ModelCatalogNumberFilterKey.MAX_RPS, ModelCatalogNumberFilterKey.COLD_START_LOAD_TIME, - ModelCatalogNumberFilterKey.MIN_VRAM, - ModelCatalogNumberFilterKey.IMAGE_SIZE, ...ALL_LATENCY_FILTER_KEYS, ]; @@ -554,7 +553,6 @@ export const getPerformanceFiltersToShow = ( filterData: Partial>, ): ModelCatalogFilterKey[] => { const activeLatencyKeys = ALL_LATENCY_FILTER_KEYS.filter((key) => filterData[key] !== undefined); - // Use Set to deduplicate since PERFORMANCE_FILTER_KEYS already includes latency fields return [ ...new Set([ ...PERFORMANCE_FILTER_KEYS, @@ -572,12 +570,13 @@ export const getAllFiltersToShow = ( filterData: Partial>, ): ModelCatalogFilterKey[] => { const activeLatencyKeys = ALL_LATENCY_FILTER_KEYS.filter((key) => filterData[key] !== undefined); - // Use Set to deduplicate since PERFORMANCE_FILTER_KEYS already includes latency fields - // Include HARDWARE_CONFIGURATION which shows in performance toolbar but clears normally + // Include MIN_VRAM and IMAGE_SIZE which are sidebar-only filters shown on the landing page return [ ...new Set([ ...BASIC_FILTER_KEYS, ...PERFORMANCE_FILTER_KEYS, + ModelCatalogNumberFilterKey.MIN_VRAM, + ModelCatalogNumberFilterKey.IMAGE_SIZE, ModelCatalogStringFilterKey.HARDWARE_CONFIGURATION, ...activeLatencyKeys, ]), @@ -589,7 +588,6 @@ export const getAllFiltersToShow = ( * Includes all ModelCatalogFilterKeys (ModelCatalogStringFilterKey | ModelCatalogNumberFilterKey | LatencyMetricFieldName). */ export const MODEL_CATALOG_FILTER_CATEGORY_NAMES: Record = { - // String filter keys [ModelCatalogStringFilterKey.PROVIDER]: 'Provider', [ModelCatalogStringFilterKey.LICENSE]: 'License', [ModelCatalogStringFilterKey.TASK]: 'Task', @@ -599,12 +597,10 @@ export const MODEL_CATALOG_FILTER_CATEGORY_NAMES: Record [field, 'Latency'])) as Record< LatencyMetricFieldName,