Skip to content
Draft
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
86 changes: 86 additions & 0 deletions packages/app/cypress/e2e/compare-index-matrix.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
describe('Compare index matrix', () => {
beforeEach(() => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
});

it('renders one matrix per model section with linked + ghost cells on /compare', () => {
cy.visit('/compare');
cy.get('[data-testid="compare-pair-matrix"]').should('have.length.greaterThan', 0);

// Every model section renders exactly one matrix.
cy.get('section[id]').then(($sections) => {
const withMatrix = $sections.filter(
(_, s) => s.querySelector('[data-testid="compare-pair-matrix"]') !== null,
);
cy.get('[data-testid="compare-pair-matrix"]').should('have.length', withMatrix.length);
});

// Available pairs are real links under /compare/<model>-<a>-vs-<b>.
cy.get('a[data-testid="compare-matrix-cell"]')
.should('have.length.greaterThan', 0)
.first()
.should('have.attr', 'href')
.and('match', /^\/compare\/[a-z0-9-]+-[a-z0-9]+-vs-[a-z0-9]+$/u);

// Pairs without benchmark data render as non-link ghost cells (the
// fixtures' availability is sparse, so ghosts always exist).
cy.get('[data-testid="compare-matrix-empty-cell"]').should('have.length.greaterThan', 0);

// Anchor text (sr-only) carries the pair name for crawlers.
cy.get('a[data-testid="compare-matrix-cell"]').first().invoke('text').should('match', /vs/u);
});

it('navigates to the pair page when a cell is clicked', () => {
cy.visit('/compare');
cy.get('a[data-testid="compare-matrix-cell"]')
.first()
.then(($a) => {
const href = $a.attr('href')!;
cy.wrap($a).click();
cy.location('pathname').should('eq', href);
});
});

it('shows the focused pair in the readout line (same path as hover)', () => {
cy.visit('/compare');
// React's onMouseEnter is delegated and unreachable via cy.trigger — the
// keyboard focus handler updates the same readout state, so exercise that.
cy.get('a[data-testid="compare-matrix-cell"]').first().focus();
cy.get('a[data-testid="compare-matrix-cell"]')
.first()
.invoke('attr', 'title')
.then((label) => {
cy.get('[data-testid="compare-matrix-readout"]').first().should('contain.text', label);
});
});

it('renders per-dollar matrices with /compare-per-dollar cell hrefs', () => {
cy.visit('/compare-per-dollar');
cy.get('[data-testid="compare-pair-matrix"]').should('have.length.greaterThan', 0);
cy.get('a[data-testid="compare-matrix-cell"]')
.first()
.should('have.attr', 'href')
.and('match', /^\/compare-per-dollar\//u);
});

it('renders the zh matrices with /zh hrefs and Chinese legend strings', () => {
cy.visit('/zh/compare');
cy.get('[data-testid="compare-pair-matrix"]').should('have.length.greaterThan', 0);
cy.get('a[data-testid="compare-matrix-cell"]')
.first()
.should('have.attr', 'href')
.and('match', /^\/zh\/compare\//u);
cy.contains('暂无基准测试数据').should('exist');
cy.contains('点击单元格查看对比').should('exist');
});

it('renders the zh per-dollar matrices with /zh/compare-per-dollar hrefs', () => {
cy.visit('/zh/compare-per-dollar');
cy.get('a[data-testid="compare-matrix-cell"]')
.first()
.should('have.attr', 'href')
.and('match', /^\/zh\/compare-per-dollar\//u);
});
});
80 changes: 10 additions & 70 deletions packages/app/src/app/compare-per-dollar/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import type { Metadata } from 'next';
import Link from 'next/link';

import {
HW_REGISTRY,
SITE_NAME,
SITE_URL,
SUPPORTERS_LINE,
} from '@semianalysisai/inferencex-constants';
import { SITE_NAME, SITE_URL, SUPPORTERS_LINE } from '@semianalysisai/inferencex-constants';

import { enAlternates } from '@/lib/i18n';

import { ComparePairCardLink } from '@/components/compare/compare-pair-card-link';
import { ComparePairMatrix } from '@/components/compare/compare-pair-matrix';
import { JsonLd } from '@/components/json-ld';
import { Card } from '@/components/ui/card';
import { getComparablePairsByModelSlug } from '@/lib/compare-availability';
import { type ComparePair, COMPARE_MODEL_SLUGS, type CompareModelSlug } from '@/lib/compare-slug';
import { bucketComparePairsByVendor, formatModelList } from '@/lib/compare-ssr';
import { buildCompareMatrix } from '@/lib/compare-matrix';
import { COMPARE_MODEL_SLUGS } from '@/lib/compare-slug';
import { formatModelList } from '@/lib/compare-ssr';

export const dynamic = 'force-dynamic';

Expand All @@ -38,42 +34,6 @@ export const metadata: Metadata = {
},
};

interface VendorGroup {
heading: string;
description: string;
pairs: { a: string; b: string; slug: string; label: string }[];
}

function groupPairsByVendorForModel(
model: CompareModelSlug,
comparablePairs: ComparePair[],
): VendorGroup[] {
const { cross, nvidia, amd } = bucketComparePairsByVendor(model.slug, comparablePairs);
const groups: VendorGroup[] = [];
if (cross.length > 0) {
groups.push({
heading: 'NVIDIA vs AMD',
description: 'Cross-vendor cost-per-token comparisons across architecture generations.',
pairs: cross,
});
}
if (nvidia.length > 0) {
groups.push({
heading: 'NVIDIA vs NVIDIA',
description: 'Hopper and Blackwell generation cost-per-token comparisons.',
pairs: nvidia,
});
}
if (amd.length > 0) {
groups.push({
heading: 'AMD vs AMD',
description: 'CDNA 3 and CDNA 4 generation cost-per-token comparisons.',
pairs: amd,
});
}
return groups;
}

const jsonLd = {
'@context': 'https://schema.org',
'@type': 'CollectionPage',
Expand Down Expand Up @@ -145,7 +105,6 @@ export default async function ComparePerDollarIndexPage() {

{modelsWithPairs.map((model) => {
const pairs = comparablePairsByModel.get(model.slug) ?? [];
const groups = groupPairsByVendorForModel(model, pairs);
return (
<section key={model.slug} id={model.slug}>
<Card className="flex flex-col gap-4">
Expand All @@ -156,30 +115,11 @@ export default async function ComparePerDollarIndexPage() {
benchmark data on {model.label}.
</p>
</div>
{groups.map((group) => (
<div key={`${model.slug}__${group.heading}`} className="flex flex-col gap-3">
<div>
<h3 className="text-base font-semibold">{group.heading}</h3>
<p className="text-xs text-muted-foreground mt-1">{group.description}</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
{group.pairs.map(({ slug, label, a, b }) => {
const aMeta = HW_REGISTRY[a];
const bMeta = HW_REGISTRY[b];
const archLine = `${aMeta?.arch ?? '—'} · ${bMeta?.arch ?? '—'}`;
return (
<ComparePairCardLink
key={slug}
href={`/compare-per-dollar/${slug}`}
slug={slug}
label={label}
archLine={archLine}
/>
);
})}
</div>
</div>
))}
<ComparePairMatrix
matrix={buildCompareMatrix(model.slug, pairs)}
hrefPrefix="/compare-per-dollar"
modelLabel={model.label}
/>
</Card>
</section>
);
Expand Down
80 changes: 10 additions & 70 deletions packages/app/src/app/compare/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import type { Metadata } from 'next';
import Link from 'next/link';

import {
HW_REGISTRY,
SITE_NAME,
SITE_URL,
SUPPORTERS_LINE,
} from '@semianalysisai/inferencex-constants';
import { SITE_NAME, SITE_URL, SUPPORTERS_LINE } from '@semianalysisai/inferencex-constants';

import { enAlternates } from '@/lib/i18n';

import { ComparePairCardLink } from '@/components/compare/compare-pair-card-link';
import { ComparePairMatrix } from '@/components/compare/compare-pair-matrix';
import { JsonLd } from '@/components/json-ld';
import { Card } from '@/components/ui/card';
import { getComparablePairsByModelSlug } from '@/lib/compare-availability';
import { type ComparePair, COMPARE_MODEL_SLUGS, type CompareModelSlug } from '@/lib/compare-slug';
import { bucketComparePairsByVendor, formatModelList } from '@/lib/compare-ssr';
import { buildCompareMatrix } from '@/lib/compare-matrix';
import { COMPARE_MODEL_SLUGS } from '@/lib/compare-slug';
import { formatModelList } from '@/lib/compare-ssr';

export const dynamic = 'force-dynamic';

Expand All @@ -38,42 +34,6 @@ export const metadata: Metadata = {
},
};

interface VendorGroup {
heading: string;
description: string;
pairs: { a: string; b: string; slug: string; label: string }[];
}

function groupPairsByVendorForModel(
model: CompareModelSlug,
comparablePairs: ComparePair[],
): VendorGroup[] {
const { cross, nvidia, amd } = bucketComparePairsByVendor(model.slug, comparablePairs);
const groups: VendorGroup[] = [];
if (cross.length > 0) {
groups.push({
heading: 'NVIDIA vs AMD',
description: 'Cross-vendor comparisons across architecture generations.',
pairs: cross,
});
}
if (nvidia.length > 0) {
groups.push({
heading: 'NVIDIA vs NVIDIA',
description: 'Hopper and Blackwell generation comparisons.',
pairs: nvidia,
});
}
if (amd.length > 0) {
groups.push({
heading: 'AMD vs AMD',
description: 'CDNA 3 and CDNA 4 generation comparisons.',
pairs: amd,
});
}
return groups;
}

const jsonLd = {
'@context': 'https://schema.org',
'@type': 'CollectionPage',
Expand Down Expand Up @@ -142,7 +102,6 @@ export default async function CompareIndexPage() {

{modelsWithPairs.map((model) => {
const pairs = comparablePairsByModel.get(model.slug) ?? [];
const groups = groupPairsByVendorForModel(model, pairs);
return (
<section key={model.slug} id={model.slug}>
<Card className="flex flex-col gap-4">
Expand All @@ -153,30 +112,11 @@ export default async function CompareIndexPage() {
{model.label}.
</p>
</div>
{groups.map((group) => (
<div key={`${model.slug}__${group.heading}`} className="flex flex-col gap-3">
<div>
<h3 className="text-base font-semibold">{group.heading}</h3>
<p className="text-xs text-muted-foreground mt-1">{group.description}</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
{group.pairs.map(({ slug, label, a, b }) => {
const aMeta = HW_REGISTRY[a];
const bMeta = HW_REGISTRY[b];
const archLine = `${aMeta?.arch ?? '—'} · ${bMeta?.arch ?? '—'}`;
return (
<ComparePairCardLink
key={slug}
href={`/compare/${slug}`}
slug={slug}
label={label}
archLine={archLine}
/>
);
})}
</div>
</div>
))}
<ComparePairMatrix
matrix={buildCompareMatrix(model.slug, pairs)}
hrefPrefix="/compare"
modelLabel={model.label}
/>
Comment thread
Oseltamivir marked this conversation as resolved.
</Card>
</section>
);
Expand Down
Loading
Loading