Skip to content

Commit 6d7b314

Browse files
Initial Model Catalog skeleton (kubeflow#1373)
* initial Model Catalog skeleton Signed-off-by: Yulia Krimerman <juliapiterova@hotmail.com> * signed Signed-off-by: Yulia Krimerman <juliapiterova@hotmail.com> * Hide MC as stanalone Signed-off-by: Yulia Krimerman <juliapiterova@hotmail.com> * lint Signed-off-by: Yulia Krimerman <juliapiterova@hotmail.com> * final version Signed-off-by: Yulia Krimerman <juliapiterova@hotmail.com> * added tests Signed-off-by: Yulia Krimerman <juliapiterova@hotmail.com> * removed unit tests, added todo and util Signed-off-by: Yulia Krimerman <juliapiterova@hotmail.com> * updated the tests Signed-off-by: Yulia Krimerman <juliapiterova@hotmail.com> * added removed TODO Signed-off-by: Yulia Krimerman <juliapiterova@hotmail.com> * fix new imports Signed-off-by: Yulia Krimerman <juliapiterova@hotmail.com> --------- Signed-off-by: Yulia Krimerman <juliapiterova@hotmail.com>
1 parent 0666a7c commit 6d7b314

12 files changed

Lines changed: 634 additions & 12 deletions

File tree

clients/ui/frontend/src/__mocks__/mockModelArtifactList.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/* eslint-disable camelcase */
22
import { ModelArtifactList } from '~/app/types';
3+
import { mockModelArtifact } from './mockModelArtifact';
34

45
export const mockModelArtifactList = ({
5-
items = [],
6+
items = [mockModelArtifact()],
67
}: Partial<ModelArtifactList>): ModelArtifactList => ({
78
items,
89
nextPageToken: '',
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
class ModelCatalog {
2+
visit() {
3+
cy.visit('/model-catalog');
4+
}
5+
6+
findModelCatalogCards() {
7+
return cy.findAllByTestId('model-catalog-card');
8+
}
9+
10+
findFirstModelCatalogCard() {
11+
return this.findModelCatalogCards().first().should('be.visible');
12+
}
13+
14+
findModelCatalogDetailLink() {
15+
return cy.findByTestId('model-catalog-detail-link');
16+
}
17+
18+
findModelCatalogDescription() {
19+
return cy.findByTestId('model-catalog-card-description');
20+
}
21+
22+
findSourceLabel() {
23+
return cy.get('.pf-v6-c-label');
24+
}
25+
26+
findModelLogo() {
27+
return cy.get('img[alt="model logo"]');
28+
}
29+
30+
findVersionIcon() {
31+
return cy.get('.pf-v6-c-icon');
32+
}
33+
34+
findFrameworkLabel() {
35+
return cy.contains('PyTorch');
36+
}
37+
38+
findTaskLabel() {
39+
return cy.contains('text-generation');
40+
}
41+
42+
findLicenseLabel() {
43+
return cy.contains('apache-2.0');
44+
}
45+
46+
findLabBaseLabel() {
47+
return cy.contains('lab-base');
48+
}
49+
50+
findLoadingState() {
51+
return cy.contains('Loading model catalog...');
52+
}
53+
54+
findPageTitle() {
55+
return cy.contains('Model Catalog');
56+
}
57+
58+
findPageDescription() {
59+
return cy.contains('Discover models that are available for your organization');
60+
}
61+
}
62+
63+
export const modelCatalog = new ModelCatalog();
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { modelCatalog } from '~/__tests__/cypress/cypress/pages/modelCatalog';
2+
3+
describe('Model Catalog Page', () => {
4+
beforeEach(() => {
5+
cy.visit('/model-catalog');
6+
});
7+
8+
it('should display loading state initially', () => {
9+
modelCatalog.findLoadingState().should('be.visible');
10+
});
11+
12+
it('should display model catalog content when data is loaded', () => {
13+
modelCatalog.findLoadingState().should('not.exist');
14+
modelCatalog.findPageTitle().should('be.visible');
15+
modelCatalog.findPageDescription().should('be.visible');
16+
modelCatalog.findModelCatalogCards().should('have.length.at.least', 1);
17+
});
18+
});
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { modelCatalog } from '~/__tests__/cypress/cypress/pages/modelCatalog';
2+
3+
describe('ModelCatalogCard Component', () => {
4+
beforeEach(() => {
5+
cy.visit('/model-catalog');
6+
modelCatalog.findLoadingState().should('not.exist');
7+
modelCatalog.findModelCatalogCards().should('have.length.at.least', 1);
8+
});
9+
10+
describe('Card Layout and Content', () => {
11+
it('should render all cards from the mock data', () => {
12+
modelCatalog.findModelCatalogCards().should('have.length.at.least', 1);
13+
});
14+
15+
it('should display correct source labels', () => {
16+
modelCatalog.findFirstModelCatalogCard().within(() => {
17+
modelCatalog.findSourceLabel().should('contain.text', 'Red Hat');
18+
});
19+
});
20+
21+
it('should handle cards with logos', () => {
22+
modelCatalog.findFirstModelCatalogCard().within(() => {
23+
modelCatalog
24+
.findModelLogo()
25+
.should('exist')
26+
.and('have.attr', 'src')
27+
.and('include', 'data:image/svg+xml;base64');
28+
});
29+
});
30+
});
31+
32+
describe('Version Tag Display', () => {
33+
it('should extract and display version tags correctly', () => {
34+
modelCatalog.findFirstModelCatalogCard().within(() => {
35+
modelCatalog.findVersionIcon().should('exist');
36+
cy.contains('1.4.0').should('exist');
37+
});
38+
});
39+
});
40+
41+
describe('Description Handling', () => {
42+
it('should display model descriptions', () => {
43+
modelCatalog.findFirstModelCatalogCard().within(() => {
44+
modelCatalog
45+
.findModelCatalogDescription()
46+
.should('contain.text', 'Base model for customizing and fine-tuning');
47+
});
48+
});
49+
});
50+
51+
describe('Navigation and Interaction', () => {
52+
it('should show all model metadata correctly', () => {
53+
modelCatalog.findFirstModelCatalogCard().within(() => {
54+
modelCatalog.findModelCatalogDetailLink().should('contain.text', 'granite-7b-starter');
55+
56+
modelCatalog.findFrameworkLabel().should('exist');
57+
modelCatalog.findTaskLabel().should('exist');
58+
modelCatalog.findLicenseLabel().should('exist');
59+
modelCatalog.findLabBaseLabel().should('exist');
60+
});
61+
});
62+
});
63+
});

clients/ui/frontend/src/app/AppRoutes.tsx

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import * as React from 'react';
22
import { Navigate, Route, Routes } from 'react-router-dom';
33
import { NotFound } from 'mod-arch-shared';
4+
import { useModularArchContext, DeploymentMode } from 'mod-arch-core';
45
import { NavDataItem } from '~/app/standalone/types';
5-
import ModelRegistrySettingsRoutes from '~/app/pages/settings/ModelRegistrySettingsRoutes';
6-
import ModelRegistryRoutes from '~/app/pages/modelRegistry/ModelRegistryRoutes';
7-
import useUser from '~/app/hooks/useUser';
6+
import ModelRegistrySettingsRoutes from './pages/settings/ModelRegistrySettingsRoutes';
7+
import ModelRegistryRoutes from './pages/modelRegistry/ModelRegistryRoutes';
8+
import ModelCatalogPage from './pages/modelCatalog/screens/ModelCatalogPage';
9+
import { ModelCatalogContextProvider } from './context/modelCatalog/ModelCatalogContext';
10+
import useUser from './hooks/useUser';
811

912
export const useAdminSettings = (): NavDataItem[] => {
1013
const { clusterAdmin } = useUser();
@@ -21,23 +24,53 @@ export const useAdminSettings = (): NavDataItem[] => {
2124
];
2225
};
2326

24-
export const useNavData = (): NavDataItem[] => [
25-
{
26-
label: 'Model Registry',
27-
path: '/model-registry',
28-
},
29-
...useAdminSettings(),
30-
];
27+
export const useNavData = (): NavDataItem[] => {
28+
const { config } = useModularArchContext();
29+
const { deploymentMode } = config;
30+
const isStandalone = deploymentMode === DeploymentMode.Standalone;
31+
const isFederated = deploymentMode === DeploymentMode.Federated;
32+
33+
const baseNavItems = [
34+
{
35+
label: 'Model Registry',
36+
path: '/model-registry',
37+
},
38+
];
39+
40+
// Only show Model Catalog in Standalone or Federated mode
41+
if (isStandalone || isFederated) {
42+
baseNavItems.push({
43+
label: 'Model Catalog',
44+
path: '/model-catalog',
45+
});
46+
}
47+
48+
return [...baseNavItems, ...useAdminSettings()];
49+
};
3150

3251
const AppRoutes: React.FC = () => {
3352
const { clusterAdmin } = useUser();
53+
const { config } = useModularArchContext();
54+
const { deploymentMode } = config;
55+
const isStandalone = deploymentMode === DeploymentMode.Standalone;
56+
const isFederated = deploymentMode === DeploymentMode.Federated;
3457

3558
return (
3659
<Routes>
3760
<Route path="/" element={<Navigate to="/model-registry" replace />} />
3861
<Route path="/model-registry/*" element={<ModelRegistryRoutes />} />
62+
{(isStandalone || isFederated) && (
63+
<Route
64+
path="/model-catalog"
65+
element={
66+
<ModelCatalogContextProvider>
67+
<ModelCatalogPage />
68+
</ModelCatalogContextProvider>
69+
}
70+
/>
71+
)}
3972
<Route path="*" element={<NotFound />} />
40-
{/* TODO: [Conditional render] Follow up add testing and conditional rendering when in standalone mode*/}
73+
{/* TODO: [Conditional render] Follow up add testing and conditional rendering when in standalone mode */}
4174
{clusterAdmin && (
4275
<Route path="/model-registry-settings/*" element={<ModelRegistrySettingsRoutes />} />
4376
)}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import * as React from 'react';
2+
import { ModelCatalogContextType, ModelCatalogSource } from '~/app/modelCatalogTypes';
3+
4+
// Mock data for initial development
5+
const mockModelCatalogSources: ModelCatalogSource[] = [
6+
{
7+
name: 'redhat',
8+
displayName: 'Red Hat',
9+
description: 'Models from Red Hat',
10+
provider: 'Red Hat',
11+
url: 'https://registry.redhat.io',
12+
models: [
13+
{
14+
id: 'granite-7b-starter',
15+
name: 'granite-7b-starter',
16+
displayName: 'Granite 7B Starter',
17+
description: 'Base model for customizing and fine-tuning',
18+
provider: 'Red Hat',
19+
url: 'oci://registry.redhat.io/rhelai1/modelcar-granite-7b-starter:1.4.0',
20+
logo: 'data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTIgMTQ1Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2UwMDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPlJlZEhhdC1Mb2dvLUhhdC1Db2xvcjwvdGl0bGU+PHBhdGggZD0iTTE1Ny43Nyw2Mi42MWExNCwxNCwwLDAsMSwuMzEsMy40MmMwLDE0Ljg4LTE4LjEsMTcuNDYtMzAuNjEsMTcuNDZDNzguODMsODMuNDksNDIuNTMsNTMuMjYsNDIuNTMsNDRhNi40Myw2LjQzLDAsMCwxLC4yMi0xLjk0bC0zLjY2LDkuMDZhMTguNDUsMTguNDUsMCwwLDAtMS41MSw3LjMzYzAsMTguMTEsNDEsNDUuNDgsODcuNzQsNDUuNDgsMjAuNjksMCwzNi40My03Ljc2LDM2LjQzLTIxLjc3LDAtMS4wOCwwLTEuOTQtMS43My0xMC4xM1oiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xMjcuNDcsODMuNDljMTIuNTEsMCwzMC42MS0yLjU4LDMwLjYxLTE3LjQ2YTE0LDE0LDAsMCwwLS4zMS0zLjQybC03LjQ1LTMyLjM2Yy0xLjcyLTcuMTItMy4yMy0xMC4zNS0xNS43My0xNi42QzEyNC44OSw4LjY5LDEwMy43Ni41LDk3LjUxLjUsOTEuNjkuNSw5MCw4LDgzLjA2LDhjLTYuNjgsMC0xMS42NC01LjYtMTcuODktNS42LTYsMC05LjkxLDQuMDktMTIuOTMsMTIuNSwwLDAtOC40MSwyMy43Mi05LjQ5LDI3LjE2QTYuNDMsNi40MywwLDAsMCw0Mi41Myw0NGMwLDkuMjIsMzYuMywzOS40NSw4NC45NCwzOS40NU0xNjAsNzIuMDdjMS43Myw4LjE5LDEuNzMsOS4wNSwxLjczLDEwLjEzLDAsMTQtMTUuNzQsMjEuNzctMzYuNDMsMjEuNzdDNzguNTQsMTA0LDM3LjU4LDc2LjYsMzcuNTgsNTguNDlhMTguNDUsMTguNDUsMCwwLDEsMS41MS03LjMzQzIyLjI3LDUyLC41LDU1LC41LDc0LjIyYzAsMzEuNDgsNzQuNTksNzAuMjgsMTMzLjY1LDcwLjI4LDQ1LjI4LDAsNTYuNy0yMC40OCw1Ni43LTM2LjY1LDAtMTIuNzItMTEtMjcuMTYtMzAuODMtMzUuNzgiLz48L3N2Zz4=',
21+
tags: ['1.4.0', 'lab-base'],
22+
framework: 'PyTorch',
23+
task: 'text-generation',
24+
license: 'apache-2.0',
25+
metrics: {
26+
parameters: '7B',
27+
},
28+
createdAt: '2025-04-14T20:12:31Z',
29+
updatedAt: '2025-04-14T20:12:31Z',
30+
},
31+
{
32+
id: 'granite-7b-redhat-lab',
33+
name: 'granite-7b-redhat-lab',
34+
displayName: 'Granite 7B Red Hat LAB',
35+
description: 'Granite model for inference serving',
36+
provider: 'Red Hat',
37+
url: 'oci://registry.redhat.io/rhelai1/modelcar-granite-7b-redhat-lab:1.4.0',
38+
logo: 'data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTIgMTQ1Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2UwMDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPlJlZEhhdC1Mb2dvLUhhdC1Db2xvcjwvdGl0bGU+PHBhdGggZD0iTTE1Ny43Nyw2Mi42MWExNCwxNCwwLDAsMSwuMzEsMy40MmMwLDE0Ljg4LTE4LjEsMTcuNDYtMzAuNjEsMTcuNDZDNzguODMsODMuNDksNDIuNTMsNTMuMjYsNDIuNTMsNDRhNi40Myw2LjQzLDAsMCwxLC4yMi0xLjk0bC0zLjY2LDkuMDZhMTguNDUsMTguNDUsMCwwLDAtMS41MSw3LjMzYzAsMTguMTEsNDEsNDUuNDgsODcuNzQsNDUuNDgsMjAuNjksMCwzNi40My03Ljc2LDM2LjQzLTIxLjc3LDAtMS4wOCwwLTEuOTQtMS43My0xMC4xM1oiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xMjcuNDcsODMuNDljMTIuNTEsMCwzMC42MS0yLjU4LDMwLjYxLTE3LjQ2YTE0LDE0LDAsMCwwLS4zMS0zLjQybC03LjQ1LTMyLjM2Yy0xLjcyLTcuMTItMy4yMy0xMC4zNS0xNS43My0xNi42QzEyNC44OSw4LjY5LDEwMy43Ni41LDk3LjUxLjUsOTEuNjkuNSw5MCw4LDgzLjA2LDhjLTYuNjgsMC0xMS42NC01LjYtMTcuODktNS42LTYsMC05LjkxLDQuMDktMTIuOTMsMTIuNSwwLDAtOC40MSwyMy43Mi05LjQ5LDI3LjE2QTYuNDMsNi40MywwLDAsMCw0Mi41Myw0NGMwLDkuMjIsMzYuMywzOS40NSw4NC45NCwzOS40NU0xNjAsNzIuMDdjMS43Myw4LjE5LDEuNzMsOS4wNSwxLjczLDEwLjEzLDAsMTQtMTUuNzQsMjEuNzctMzYuNDMsMjEuNzdDNzguNTQsMTA0LDM3LjU4LDc2LjYsMzcuNTgsNTguNDlhMTguNDUsMTguNDUsMCwwLDEsMS41MS03LjMzQzIyLjI3LDUyLC41LDU1LC41LDc0LjIyYzAsMzEuNDgsNzQuNTksNzAuMjgsMTMzLjY1LDcwLjI4LDQ1LjI4LDAsNTYuNy0yMC40OCw1Ni43LTM2LjY1LDAtMTIuNzItMTEtMjcuMTYtMzAuODMtMzUuNzgiLz48L3N2Zz4=',
39+
tags: ['1.4.0', 'text-generation'],
40+
framework: 'PyTorch',
41+
task: 'text-generation',
42+
license: 'apache-2.0',
43+
metrics: {
44+
parameters: '7B',
45+
},
46+
createdAt: '2025-04-14T20:12:31Z',
47+
updatedAt: '2025-04-14T20:12:31Z',
48+
},
49+
{
50+
id: 'granite-8b-starter-v1',
51+
name: 'granite-8b-starter-v1',
52+
displayName: 'Granite 8B Starter V1',
53+
description: 'Base model for customizing and fine-tuning',
54+
provider: 'Red Hat',
55+
url: 'oci://registry.redhat.io/rhelai1/modelcar-granite-8b-starter-v1:1.4.0',
56+
logo: 'data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTIgMTQ1Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2UwMDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPlJlZEhhdC1Mb2dvLUhhdC1Db2xvcjwvdGl0bGU+PHBhdGggZD0iTTE1Ny43Nyw2Mi42MWExNCwxNCwwLDAsMSwuMzEsMy40MmMwLDE0Ljg4LTE4LjEsMTcuNDYtMzAuNjEsMTcuNDZDNzguODMsODMuNDksNDIuNTMsNTMuMjYsNDIuNTMsNDRhNi40Myw2LjQzLDAsMCwxLC4yMi0xLjk0bC0zLjY2LDkuMDZhMTguNDUsMTguNDUsMCwwLDAtMS41MSw3LjMzYzAsMTguMTEsNDEsNDUuNDgsODcuNzQsNDUuNDgsMjAuNjksMCwzNi40My03Ljc2LDM2LjQzLTIxLjc3LDAtMS4wOCwwLTEuOTQtMS43My0xMC4xM1oiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xMjcuNDcsODMuNDljMTIuNTEsMCwzMC42MS0yLjU4LDMwLjYxLTE3LjQ2YTE0LDE0LDAsMCwwLS4zMS0zLjQybC03LjQ1LTMyLjM2Yy0xLjcyLTcuMTItMy4yMy0xMC4zNS0xNS43My0xNi42QzEyNC44OSw4LjY5LDEwMy43Ni41LDk3LjUxLjUsOTEuNjkuNSw5MCw4LDgzLjA2LDhjLTYuNjgsMC0xMS42NC01LjYtMTcuODktNS42LTYsMC05LjkxLDQuMDktMTIuOTMsMTIuNSwwLDAtOC40MSwyMy43Mi05LjQ5LDI3LjE2QTYuNDMsNi40MywwLDAsMCw0Mi41Myw0NGMwLDkuMjIsMzYuMywzOS40NSw4NC45NCwzOS40NU0xNjAsNzIuMDdjMS43Myw4LjE5LDEuNzMsOS4wNSwxLjczLDEwLjEzLDAsMTQtMTUuNzQsMjEuNzctMzYuNDMsMjEuNzdDNzguNTQsMTA0LDM3LjU4LDc2LjYsMzcuNTgsNTguNDlhMTguNDUsMTguNDUsMCwwLDEsMS41MS03LjMzQzIyLjI3LDUyLC41LDU1LC41LDc0LjIyYzAsMzEuNDgsNzQuNTksNzAuMjgsMTMzLjY1LDcwLjI4LDQ1LjI4LDAsNTYuNy0yMC40OCw1Ni43LTM2LjY1LDAtMTIuNzItMTEtMjcuMTYtMzAuODMtMzUuNzgiLz48L3N2Zz4=',
57+
tags: ['1.4.0', 'lab-base'],
58+
framework: 'PyTorch',
59+
task: 'text-generation',
60+
license: 'apache-2.0',
61+
metrics: {
62+
parameters: '8B',
63+
},
64+
createdAt: '2025-04-14T20:12:31Z',
65+
updatedAt: '2025-04-14T20:12:31Z',
66+
},
67+
],
68+
},
69+
];
70+
71+
export const ModelCatalogContext = React.createContext<ModelCatalogContextType>({
72+
sources: [],
73+
loading: false,
74+
// eslint-disable-next-line @typescript-eslint/no-empty-function
75+
refreshSources: async () => {},
76+
});
77+
export const ModelCatalogContextProvider: React.FC<{ children: React.ReactNode }> = ({
78+
children,
79+
}) => {
80+
const [sources, setSources] = React.useState(mockModelCatalogSources);
81+
const [loading, setLoading] = React.useState(false);
82+
const [error, setError] = React.useState<Error>();
83+
84+
const refreshSources = React.useCallback(async () => {
85+
setLoading(true);
86+
try {
87+
// TODO: Replace with actual API call
88+
// eslint-disable-next-line no-promise-executor-return
89+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate API delay
90+
setSources(mockModelCatalogSources);
91+
setError(undefined);
92+
} catch (e) {
93+
setError(e instanceof Error ? e : new Error('Failed to fetch catalog sources'));
94+
} finally {
95+
setLoading(false);
96+
}
97+
}, []);
98+
99+
React.useEffect(() => {
100+
refreshSources();
101+
}, [refreshSources]);
102+
103+
const value = React.useMemo(
104+
() => ({
105+
sources,
106+
loading,
107+
error,
108+
refreshSources,
109+
}),
110+
[sources, loading, error, refreshSources],
111+
);
112+
113+
return <ModelCatalogContext.Provider value={value}>{children}</ModelCatalogContext.Provider>;
114+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as React from 'react';
2+
import { ModelCatalogContext } from '~/app/context/modelCatalog/ModelCatalogContext';
3+
import { ModelCatalogContextType } from '~/app/modelCatalogTypes';
4+
// TODO: This is a placeholder for the model catalog sources hook once API are implemented and BFF endpoints are there
5+
export const useModelCatalogSources = (): ModelCatalogContextType => {
6+
const context = React.useContext(ModelCatalogContext);
7+
8+
return context;
9+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export type ModelCatalogSource = {
2+
name: string;
3+
displayName: string;
4+
description?: string;
5+
provider?: string;
6+
url?: string;
7+
models?: ModelCatalogItem[];
8+
};
9+
10+
export type ModelCatalogItem = {
11+
id: string;
12+
name: string;
13+
displayName: string;
14+
description?: string;
15+
provider?: string;
16+
url?: string;
17+
logo?: string;
18+
tags?: string[];
19+
framework?: string;
20+
task?: string;
21+
license?: string;
22+
metrics?: {
23+
[key: string]: string | number;
24+
};
25+
createdAt?: string;
26+
updatedAt?: string;
27+
};
28+
29+
export type ModelCatalogContextType = {
30+
sources: ModelCatalogSource[];
31+
loading: boolean;
32+
error?: Error;
33+
refreshSources: () => Promise<void>;
34+
};

0 commit comments

Comments
 (0)