Skip to content
Open
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
87 changes: 87 additions & 0 deletions packages/app/cypress/e2e/compare-zh.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Chinese (`/zh/compare*`) locale smoke tests. Verifies the Chinese variants of
* the compare master + detail pages render localized copy, keep the interactive
* chart + interpolated table working, and cross-link within the zh locale.
*/
describe('Compare — Chinese (zh) locale', () => {
const SLUG = 'deepseek-r1-gb200-vs-h100';

beforeEach(() => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
});

describe('master index pages', () => {
it('renders the Chinese GPU comparisons index', () => {
cy.visit('/zh/compare');
cy.get('h1').should('contain.text', 'GPU 对比');
// Localized per-dollar CTA links within the zh locale.
cy.get('[data-testid="compare-index-per-dollar-link"]')
.should('contain.text', '对比 GPU 每美元性能')
.and('have.attr', 'href')
.and('match', /^\/zh\/compare-per-dollar/u);
});

it('renders the Chinese performance-per-dollar index', () => {
cy.visit('/zh/compare-per-dollar');
cy.get('h1').should('contain.text', 'GPU 每美元性能');
});
});

describe('full detail page', () => {
before(() => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
cy.visit(`/zh/compare/${SLUG}`);
cy.get('[data-testid="compare-interpolated-table"]').should('exist');
});

it('shows the localized eyebrow and description', () => {
cy.contains('GPU 对比').should('exist');
cy.contains('AI 推理基准对比').should('exist');
});

it('renders the interpolated table with localized metric labels', () => {
cy.get('[data-testid="compare-interpolated-table"]').should('be.visible');
cy.get('[data-testid="compare-interpolated-table"]').should('contain.text', '并发数');
// GPU labels stay in their original (language-neutral) form.
cy.get('[data-testid="compare-interpolated-table"] tbody td').should('contain.text', 'GB200');
});

it('mounts the interactive chart', () => {
cy.get('[data-testid="scatter-graph"]').should('exist');
});

it('cross-links to the zh per-dollar view', () => {
cy.contains('a', '查看每美元性能视图')
.should('have.attr', 'href')
.and('match', new RegExp(`^/zh/compare-per-dollar/${SLUG}$`, 'u'));
});
});

describe('per-dollar detail page', () => {
before(() => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
cy.visit(`/zh/compare-per-dollar/${SLUG}`);
cy.get('[data-testid="compare-interpolated-table"]').should('exist');
});

it('shows the localized heading and cost-row label', () => {
cy.get('h1').should('contain.text', '每美元性能');
cy.get('[data-testid="compare-interpolated-table"]').should(
'contain.text',
'每百万 Token 美元成本',
);
});

it('cross-links to the zh full comparison view', () => {
cy.contains('a', '查看完整的延迟 + 吞吐量对比')
.should('have.attr', 'href')
.and('match', new RegExp(`^/zh/compare/${SLUG}$`, 'u'));
});
});
});
174 changes: 4 additions & 170 deletions packages/app/src/app/compare-per-dollar/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,6 @@
import type { Metadata } from 'next';
import { notFound, permanentRedirect } from 'next/navigation';

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

import { JsonLd } from '@/components/json-ld';
import { pickPairDefaults } from '@/lib/compare-pair-defaults';
import {
canonicalCompareSlug,
compareDisplayLabel,
compareModelDisplayLabel,
parseCompareSlug,
} from '@/lib/compare-slug';
import { getGpuSpecs } from '@/lib/constants';
import {
buildBreadcrumbJsonLd,
buildJsonLd,
compareTableNarrative,
computeCompareTableData,
dateRangeForPair,
getCachedBenchmarks,
KNOWN_MODELS,
KNOWN_PRECISIONS,
KNOWN_SEQUENCES,
pickString,
summarize,
} from '@/lib/compare-ssr';

import ComparePerDollarPageClient from './page-client';
import PerDollarDetailView, { perDollarDetailMetadata } from '@/lib/compare/per-dollar-detail-view';

export const dynamic = 'force-dynamic';

Expand All @@ -37,150 +11,10 @@ interface Props {

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const parsed = parseCompareSlug(slug);
if (!parsed) return {};
const fullLabel = compareModelDisplayLabel(parsed.model, parsed.a, parsed.b);
const gpuLabel = compareDisplayLabel(parsed.a, parsed.b);
const url = `${SITE_URL}/compare-per-dollar/${canonicalCompareSlug(parsed.model.slug, parsed.a, parsed.b)}`;
// Description weaves the user-named SEO terms — "performance per dollar",
// "performance normalized by cost", "dollars per million tokens" — without
// keyword-stuffing.
const description = `${parsed.model.label} cost per million tokens on ${gpuLabel}. Performance normalized by owning-hyperscaler TCO — see which GPU delivers more inference dollars-per-token at every interactivity level.`;
return {
title: `${fullLabel} — Performance per Dollar`,
description,
alternates: { canonical: url },
openGraph: {
title: `${fullLabel} — Performance per Dollar | ${SITE_NAME}`,
description,
url,
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: `${fullLabel} — Performance per Dollar`,
description,
},
};
return perDollarDetailMetadata(slug, 'en');
}

export default async function ComparePerDollarPage({ params, searchParams }: Props) {
const { slug } = await params;
const parsed = parseCompareSlug(slug);
if (!parsed) notFound();

const sp = await searchParams;

// Same one-hop 308 normalization as /compare/[slug] — bare-slug fallback,
// alias model resolution, GPU alphabetical order — but redirect target lives
// under /compare-per-dollar/. Query string is preserved across the hop.
const canonical = canonicalCompareSlug(parsed.model.slug, parsed.a, parsed.b);
// canonical is always lowercase; compare against lowercased input so mixed-case
// URLs don't emit a fresh 308 + CDN cache entry every hit.
if (canonical !== slug.toLowerCase()) {
const qs = Object.entries(sp)
.flatMap(([k, v]) => {
if (Array.isArray(v)) return v.map((vv) => [k, vv] as const);
if (v === undefined) return [];
return [[k, v] as const];
})
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&');
permanentRedirect(`/compare-per-dollar/${canonical}${qs ? `?${qs}` : ''}`);
}

const rows = await getCachedBenchmarks(parsed.model.dbKeys);
const summaryA = summarize(rows, parsed.a);
const summaryB = summarize(rows, parsed.b);
const { sequence: pickedSequence, precision: pickedPrecision } = pickPairDefaults(
rows,
parsed.a,
parsed.b,
);

const urlSeq = pickString(sp.i_seq);
const urlPrec = pickString(sp.i_prec);
const urlModel = pickString(sp.g_model);
const effectiveSequence = urlSeq && KNOWN_SEQUENCES.has(urlSeq) ? urlSeq : pickedSequence;
const effectivePrecision = urlPrec && KNOWN_PRECISIONS.has(urlPrec) ? urlPrec : pickedPrecision;
const effectiveModel =
urlModel && KNOWN_MODELS.has(urlModel) ? urlModel : parsed.model.displayName;

const { defaultTargets, ssrRows, interactivityRange } = computeCompareTableData(
rows,
parsed.a,
parsed.b,
effectiveSequence,
effectivePrecision,
);

const url = `${SITE_URL}/compare-per-dollar/${canonical}`;
const imageUrl = `${url}/performance-per-dollar.png`;
const { oldest, newest } = dateRangeForPair(rows, parsed.a, parsed.b);
const jsonLd = buildJsonLd(
'per-dollar',
parsed.model,
parsed.a,
parsed.b,
url,
summaryA,
summaryB,
ssrRows,
imageUrl,
oldest,
newest,
parsed.model.displayName,
);
const breadcrumbJsonLd = buildBreadcrumbJsonLd(
'per-dollar',
compareModelDisplayLabel(parsed.model, parsed.a, parsed.b),
url,
);
const label = compareModelDisplayLabel(parsed.model, parsed.a, parsed.b);
const aMeta = HW_REGISTRY[parsed.a];
const bMeta = HW_REGISTRY[parsed.b];
const aLabel = aMeta?.label ?? parsed.a.toUpperCase();
const bLabel = bMeta?.label ?? parsed.b.toUpperCase();
const narrative = compareTableNarrative(
'per-dollar',
parsed.model.label,
aLabel,
bLabel,
ssrRows,
interactivityRange,
);
// Owning-hyperscaler $/GPU/hr — the same `costh` value the per-dollar math
// upstream uses to derive cost per million tokens. Rendered in the header
// so the reader can audit the underlying pricing inputs without leaving
// the page.
const aCostPerGpuHr = getGpuSpecs(parsed.a).costh;
const bCostPerGpuHr = getGpuSpecs(parsed.b).costh;

return (
<>
<JsonLd data={jsonLd} />
<JsonLd data={breadcrumbJsonLd} />
<ComparePerDollarPageClient
a={parsed.a}
b={parsed.b}
slug={canonical}
label={label}
modelLabel={parsed.model.label}
defaultModel={effectiveModel}
defaultSequence={effectiveSequence}
defaultPrecision={effectivePrecision}
ssrTableData={{ defaultTargets, ssrRows, interactivityRange }}
narrative={narrative}
aLabel={aLabel}
bLabel={bLabel}
aVendor={aMeta?.vendor ?? ''}
bVendor={bMeta?.vendor ?? ''}
aArch={aMeta?.arch ?? ''}
bArch={bMeta?.arch ?? ''}
aCostPerGpuHr={aCostPerGpuHr}
bCostPerGpuHr={bCostPerGpuHr}
heroImageSrc={`/compare-per-dollar/${canonical}/performance-per-dollar.png`}
/>
</>
);
const [{ slug }, sp] = await Promise.all([params, searchParams]);
return <PerDollarDetailView slug={slug} sp={sp} lang="en" />;
}
Loading
Loading