diff --git a/web/default/src/features/model-list/model-details-dialog.tsx b/web/default/src/features/model-list/model-details-dialog.tsx index fa4a510..84c9574 100644 --- a/web/default/src/features/model-list/model-details-dialog.tsx +++ b/web/default/src/features/model-list/model-details-dialog.tsx @@ -52,11 +52,31 @@ function PriceCard(props: { ) } -function DetailItem(props: { label: string; children: ReactNode }) { +function EndpointList(props: { endpoints: string[]; emptyLabel: string }) { + if (props.endpoints.length === 0) { + return {props.emptyLabel} + } + return ( -
-
{props.label}
-
{props.children}
+
+ {props.endpoints.map((endpoint) => { + const separatorIndex = endpoint.indexOf(' ') + const method = + separatorIndex > 0 ? endpoint.slice(0, separatorIndex) : 'POST' + const path = + separatorIndex > 0 ? endpoint.slice(separatorIndex + 1) : endpoint + + return ( +
+ + {method} + + + {path} + +
+ ) + })}
) } @@ -86,7 +106,7 @@ export function ModelDetailsDialog(props: { -
+
{model.priceUnitKey === 'request' ? ( -
- - {model.name} - - - - +
+
+
+
+ {t('Model')} +
+
+ {model.name} +
+
+
+
+ {t('Provider')} +
+
+ +
+
+
+
+
+ {t('API Endpoints')} +
+
+ +
+
diff --git a/web/default/src/features/model-list/model-list-data.test.ts b/web/default/src/features/model-list/model-list-data.test.ts index 1a3a2be..8308306 100644 --- a/web/default/src/features/model-list/model-list-data.test.ts +++ b/web/default/src/features/model-list/model-list-data.test.ts @@ -115,7 +115,7 @@ test('merges duplicate catalog rows deterministically without changing display s ]) }) -test('formats endpoint metadata as renderable method and model-specific path strings', () => { +test('formats configured endpoint metadata and omits unresolved internal endpoint types', () => { const model = pricingModel(1, 'model-a', ['chat', 'responses', 'unknown']) assert.deepEqual( @@ -123,11 +123,7 @@ test('formats endpoint metadata as renderable method and model-specific path str chat: { method: 'post', path: '/v1/chat/completions' }, responses: { method: 'POST', path: '/v1/models/{model}/responses' }, }), - [ - 'POST /v1/chat/completions', - 'POST /v1/models/model-a/responses', - 'unknown', - ] + ['POST /v1/chat/completions', 'POST /v1/models/model-a/responses'] ) }) diff --git a/web/default/src/features/model-list/model-list-data.ts b/web/default/src/features/model-list/model-list-data.ts index 53f9a98..182f893 100644 --- a/web/default/src/features/model-list/model-list-data.ts +++ b/web/default/src/features/model-list/model-list-data.ts @@ -45,18 +45,20 @@ export function buildCatalogEndpoints( ) { return [ ...new Set( - (model.supported_endpoint_types ?? []).map((type) => { - const endpoint = endpointMap[type] - if (!endpoint) return type - - const path = endpoint.path - ?.replaceAll('{model}', model.model_name) - .trim() - if (!path) return type - - const method = endpoint.method?.trim().toUpperCase() || 'POST' - return `${method} ${path}` - }) + (model.supported_endpoint_types ?? []) + .map((type) => { + const endpoint = endpointMap[type] + if (!endpoint) return null + + const path = endpoint.path + ?.replaceAll('{model}', model.model_name) + .trim() + if (!path) return null + + const method = endpoint.method?.trim().toUpperCase() || 'POST' + return `${method} ${path}` + }) + .filter((endpoint): endpoint is string => endpoint !== null) ), ] }