diff --git a/src/features/providers/components/ProviderResourceTable.module.scss b/src/features/providers/components/ProviderResourceTable.module.scss index a056183..02fcc66 100644 --- a/src/features/providers/components/ProviderResourceTable.module.scss +++ b/src/features/providers/components/ProviderResourceTable.module.scss @@ -48,10 +48,48 @@ font-size: 11px; } -.modelsCell { - font-size: 12px; +.metricsCell { + display: flex; + flex-wrap: wrap; + gap: 6px; + align-items: center; +} + +.metric { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: var(--radius-md); + border: 1px solid var(--border-color); + background: var(--muted-bg); + font-size: 11px; + line-height: 1.4; + white-space: nowrap; +} + +.metricLabel { color: var(--muted-foreground); - line-height: 1.5; +} + +.metricValue { + color: var(--text-primary); + font-weight: 600; + font-variant-numeric: tabular-nums; +} + +.flagTag { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: var(--radius-md); + border: 1px solid var(--primary-30); + background: var(--primary-10); + color: var(--primary-color); + font-size: 11px; + font-weight: 500; + line-height: 1.4; + white-space: nowrap; } .statusBadge { @@ -88,18 +126,15 @@ display: inline-flex; align-items: center; justify-content: center; - gap: 4px; - padding: 4px 8px; + width: 28px; height: 28px; + padding: 0; border: 1px solid transparent; border-radius: var(--radius-md); background: transparent; color: var(--text-secondary); cursor: pointer; - font-size: 12px; - font-weight: 500; transition: background-color $transition-fast, color $transition-fast; - white-space: nowrap; &:hover:not(:disabled) { background: var(--bg-tertiary); @@ -125,9 +160,3 @@ color: var(--destructive-color); } } - -@media (max-width: 768px) { - .iconBtn span:not(.icon) { - display: none; - } -} diff --git a/src/features/providers/components/ProviderResourceTable.tsx b/src/features/providers/components/ProviderResourceTable.tsx index 2d29cc2..c260844 100644 --- a/src/features/providers/components/ProviderResourceTable.tsx +++ b/src/features/providers/components/ProviderResourceTable.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import { IconAlertTriangle, @@ -26,7 +27,7 @@ interface ProviderResourceTableProps { onDelete: (resource: ProviderResource) => void; } -const columnWidths = ['20%', '22%', '9%', '14%', '9%', '26%']; +const columnWidths = ['20%', '22%', '8%', '22%', '8%', '20%']; export function ProviderResourceTable({ resources, @@ -38,31 +39,45 @@ export function ProviderResourceTable({ }: ProviderResourceTableProps) { const { t } = useTranslation(); + const renderMetric = (key: string, label: string, value: number) => ( + + {label} + {value} + + ); + + const renderFlagTag = (key: string, label: string) => ( + + {label} + + ); + const renderModelsSummary = (r: ProviderResource) => { + const items: ReactNode[] = []; if (r.brand === 'openaiCompatibility') { - return t('providersPage.table.openaiModelSummary', { - models: r.modelCount, - keys: r.apiKeyEntryCount, - headers: r.headerCount, - }); + items.push( + renderMetric('models', t('providersPage.table.metrics.models'), r.modelCount), + renderMetric('keys', t('providersPage.table.metrics.keys'), r.apiKeyEntryCount), + renderMetric('headers', t('providersPage.table.metrics.headers'), r.headerCount), + ); + } else if (r.brand === 'ampcode') { + items.push( + renderMetric('mappings', t('providersPage.table.metrics.mappings'), r.modelCount), + renderMetric('keys', t('providersPage.table.metrics.keys'), r.apiKeyEntryCount), + ); + } else { + items.push( + renderMetric('models', t('providersPage.table.metrics.models'), r.modelCount), + renderMetric('headers', t('providersPage.table.metrics.headers'), r.headerCount), + ); + if (r.brand === 'codex' && r.flags.websockets) { + items.push(renderFlagTag('ws', t('providersPage.table.websocketsTag'))); + } + if (r.brand === 'claude' && r.flags.cloakEnabled) { + items.push(renderFlagTag('cloak', t('providersPage.table.cloakTag'))); + } } - if (r.brand === 'ampcode') { - return t('providersPage.table.ampcodeSummary', { - mappings: r.modelCount, - keys: r.apiKeyEntryCount, - }); - } - let label = t('providersPage.table.modelSummary', { - models: r.modelCount, - headers: r.headerCount, - }); - if (r.brand === 'codex' && r.flags.websockets) { - label += ` · ${t('providersPage.table.websocketsTag')}`; - } - if (r.brand === 'claude' && r.flags.cloakEnabled) { - label += ` · ${t('providersPage.table.cloakTag')}`; - } - return label; + return