Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 56 additions & 13 deletions web/default/src/features/model-list/model-details-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <span className='text-muted-foreground'>{props.emptyLabel}</span>
}

return (
<div className='grid grid-cols-[4.25rem_minmax(0,1fr)] items-center gap-2 py-2.5 sm:grid-cols-[6rem_minmax(0,1fr)] sm:gap-3'>
<dt className='text-muted-foreground text-xs'>{props.label}</dt>
<dd className='min-w-0 text-sm'>{props.children}</dd>
<div className='flex min-w-0 flex-col gap-1.5'>
{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 (
<div key={endpoint} className='flex min-w-0 items-baseline gap-3'>
<span className='text-muted-foreground w-8 shrink-0 font-mono text-[10px] font-semibold tracking-wide'>
{method}
</span>
<code className='text-foreground min-w-0 text-xs break-all'>
{path}
</code>
</div>
)
})}
</div>
)
}
Expand Down Expand Up @@ -86,7 +106,7 @@ export function ModelDetailsDialog(props: {
<Dialog open={props.open} onOpenChange={props.onOpenChange}>
<DialogContent
data-visual-region='model-details-dialog'
className='h-[min(520px,calc(100dvh-2rem))] w-[672px] max-w-[calc(100%-2rem)] grid-rows-[auto_minmax(0,1fr)] gap-0 overflow-hidden p-0 sm:max-w-2xl'
className='h-[min(580px,calc(100dvh-2rem))] w-[672px] max-w-[calc(100%-2rem)] grid-rows-[auto_minmax(0,1fr)] gap-0 overflow-hidden p-0 sm:max-w-2xl'
>
<CopyButton
value={model.name}
Expand All @@ -112,7 +132,7 @@ export function ModelDetailsDialog(props: {
)}
</DialogHeader>

<div className='grid min-h-0 grid-rows-[auto_auto_1fr] gap-4 overflow-hidden px-5 py-4 sm:px-6'>
<div className='grid min-h-0 grid-rows-[auto_auto_1fr] gap-3 overflow-hidden px-5 py-3 sm:px-6'>
<DialogSection title={t('Price')}>
{model.priceUnitKey === 'request' ? (
<PriceCard
Expand Down Expand Up @@ -148,13 +168,36 @@ export function ModelDetailsDialog(props: {
</DialogSection>

<DialogSection title={t('Model details')}>
<dl className='grid min-h-0 flex-1 grid-rows-2 divide-y'>
<DetailItem label={t('Model')}>
<code className='font-medium break-all'>{model.name}</code>
</DetailItem>
<DetailItem label={t('Provider')}>
<ModelProvider model={model} />
</DetailItem>
<dl className='bg-muted/20 min-h-0 flex-1 rounded-[10px] px-4 py-3'>
<div className='grid grid-cols-2 gap-4 pb-3 sm:gap-8'>
<div className='min-w-0'>
<dt className='text-muted-foreground text-xs'>
{t('Model')}
</dt>
<dd className='mt-1.5 min-w-0 text-sm'>
<code className='font-medium break-all'>{model.name}</code>
</dd>
</div>
<div className='min-w-0'>
<dt className='text-muted-foreground text-xs'>
{t('Provider')}
</dt>
<dd className='mt-1.5 min-w-0 text-sm'>
<ModelProvider model={model} />
</dd>
</div>
</div>
<div className='border-border/60 border-t pt-3'>
<dt className='text-muted-foreground text-xs'>
{t('API Endpoints')}
</dt>
<dd className='mt-2 min-w-0 text-sm'>
<EndpointList
endpoints={model.endpoints}
emptyLabel={t('No data')}
/>
</dd>
</div>
</dl>
</DialogSection>
</div>
Expand Down
8 changes: 2 additions & 6 deletions web/default/src/features/model-list/model-list-data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,19 +115,15 @@ 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(
buildCatalogEndpoints(model, {
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']
)
})

Expand Down
26 changes: 14 additions & 12 deletions web/default/src/features/model-list/model-list-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
),
]
}
Expand Down