diff --git a/web/src/components/common/__tests__/model-pill.test.tsx b/web/src/components/common/__tests__/model-pill.test.tsx new file mode 100644 index 00000000..3a6ee5b7 --- /dev/null +++ b/web/src/components/common/__tests__/model-pill.test.tsx @@ -0,0 +1,29 @@ +// Behavior test for ModelPill: known models render the brand glyph inside the +// capsule; unknown models still render the name (never an empty pill); blank +// input renders nothing. + +import '@testing-library/jest-dom/vitest' +import { cleanup, render, screen } from '@testing-library/react' +import { afterEach, describe, expect, it } from 'vitest' + +import { ModelPill } from '../model-pill' + +describe('ModelPill', () => { + afterEach(cleanup) + + it('renders the brand glyph for a known model', () => { + const { container } = render() + expect(screen.getByText('gpt-4o-mini')).toBeInTheDocument() + expect(container.querySelector('img')).not.toBeNull() + }) + + it('renders the plain name for an unknown model', () => { + render() + expect(screen.getByText('my-custom-model-v9')).toBeInTheDocument() + }) + + it('renders nothing for a blank model', () => { + const { container } = render() + expect(container).toBeEmptyDOMElement() + }) +}) diff --git a/web/src/components/common/model-pill.tsx b/web/src/components/common/model-pill.tsx new file mode 100644 index 00000000..59afeeba --- /dev/null +++ b/web/src/components/common/model-pill.tsx @@ -0,0 +1,32 @@ +// metapi-go/components/common — model pill: a compact capsule that pairs a +// model name with its brand glyph (BrandIcon) wherever model identifiers +// appear in dense tables (proxy logs first). Token-driven surface only; +// unknown models degrade to the plain truncated name (no empty pill). + +import { InlineBrandIcon } from '@/assets/brand-icons/BrandIcon' +import { cn } from '@/lib/utils' + +export type ModelPillProps = { + /** Resolved model name shown in the pill (usually the actual upstream one). */ + model: string + /** Full label for the tooltip when the cell truncates (e.g. requested→actual). */ + title?: string + className?: string +} + +export function ModelPill({ model, title, className }: ModelPillProps) { + const name = model.trim() + if (!name) return null + return ( + + + {name} + + ) +} diff --git a/web/src/features/proxy-logs/components/proxy-logs-columns.tsx b/web/src/features/proxy-logs/components/proxy-logs-columns.tsx index 21d6309f..9519372d 100644 --- a/web/src/features/proxy-logs/components/proxy-logs-columns.tsx +++ b/web/src/features/proxy-logs/components/proxy-logs-columns.tsx @@ -9,6 +9,7 @@ import { import { useTranslation } from 'react-i18next' import { HttpStatusBadge } from '@/components/common/http-status-badge' +import { ModelPill } from '@/components/common/model-pill' import { DataTableColumnHeader } from '@/components/data-table' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' @@ -136,15 +137,13 @@ export function useProxyLogsColumns( /> ), cell: ({ row }) => ( - - {formatModelCell( + + title={row.original.modelActual || row.original.modelRequested} + /> ), }, {