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
79 changes: 12 additions & 67 deletions packages/app/src/app/compare-per-dollar/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { Metadata } from 'next';

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

import { ComparePairCardLink } from '@/components/compare/compare-pair-card-link';
import { CompareMatrixLegend, 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 { COMPARE_MODEL_SLUGS } from '@/lib/compare-slug';
import { bucketComparePairsByVendor, formatModelList } from '@/lib/compare-ssr';

export const dynamic = 'force-dynamic';
Expand All @@ -31,42 +31,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 All @@ -78,9 +42,9 @@ const jsonLd = {
export default async function ComparePerDollarIndexPage() {
// Server-side filter (Neon availability): only show (model, pair) combos
// where both GPUs have benchmark data for that model. Matches the /compare
// index's behavior — no empty-state cards in navigation. The page-level
// handler at /compare-per-dollar/[slug] still renders the empty-state for
// direct URL hits.
// index's behavior — no empty cells in navigation. The page-level handler at
// /compare-per-dollar/[slug] still renders the empty-state for direct URL
// hits.
const comparablePairsByModel = await getComparablePairsByModelSlug();
const totalUrls = [...comparablePairsByModel.values()].reduce((s, p) => s + p.length, 0);
const modelsWithPairs = COMPARE_MODEL_SLUGS.filter(
Expand All @@ -101,12 +65,16 @@ export default async function ComparePerDollarIndexPage() {
each page renders the cost-per-token chart and an interpolated dollars-per-million
comparison table so you can pick the cheaper SKU at any target interactivity level.
</p>
<div className="mt-5">
<CompareMatrixLegend />
</div>
</Card>
</section>

{modelsWithPairs.map((model) => {
const pairs = comparablePairsByModel.get(model.slug) ?? [];
const groups = groupPairsByVendorForModel(model, pairs);
const buckets = bucketComparePairsByVendor(model.slug, pairs);
const entries = [...buckets.nvidia, ...buckets.amd, ...buckets.cross];
return (
<section key={model.slug} id={model.slug}>
<Card className="flex flex-col gap-4">
Expand All @@ -117,30 +85,7 @@ 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 pairs={entries} hrefPrefix="/compare-per-dollar" />
</Card>
</section>
);
Expand Down
75 changes: 10 additions & 65 deletions packages/app/src/app/compare/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Metadata } from 'next';
import Link from 'next/link';

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

import { ComparePairCardLink } from '@/components/compare/compare-pair-card-link';
import { CompareMatrixLegend, 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 { COMPARE_MODEL_SLUGS } from '@/lib/compare-slug';
import { bucketComparePairsByVendor, formatModelList } from '@/lib/compare-ssr';

export const dynamic = 'force-dynamic';
Expand All @@ -32,42 +32,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 All @@ -78,7 +42,7 @@ const jsonLd = {

export default async function CompareIndexPage() {
// Server-side filter: only show (model, pair) combinations where both GPUs
// have benchmark data for that model. Avoids cards that would link to an
// have benchmark data for that model. Avoids cells that would link to an
// empty-state page. The page-level handler at /compare/[slug] still renders
// the empty-state for direct URL hits, so this is purely a navigation
// hygiene concern.
Expand All @@ -99,6 +63,9 @@ export default async function CompareIndexPage() {
{formatModelList(modelsWithPairs)}. Each page includes interactive charts for latency,
throughput, and cost metrics, plus an interpolated comparison table.
</p>
<div className="mt-5">
<CompareMatrixLegend />
</div>
<div className="mt-6">
<Link
data-testid="compare-index-per-dollar-link"
Expand All @@ -116,7 +83,8 @@ export default async function CompareIndexPage() {

{modelsWithPairs.map((model) => {
const pairs = comparablePairsByModel.get(model.slug) ?? [];
const groups = groupPairsByVendorForModel(model, pairs);
const buckets = bucketComparePairsByVendor(model.slug, pairs);
const entries = [...buckets.nvidia, ...buckets.amd, ...buckets.cross];
return (
<section key={model.slug} id={model.slug}>
<Card className="flex flex-col gap-4">
Expand All @@ -127,30 +95,7 @@ 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 pairs={entries} hrefPrefix="/compare" />
</Card>
</section>
);
Expand Down
38 changes: 0 additions & 38 deletions packages/app/src/components/compare/compare-pair-card-link.tsx

This file was deleted.

Loading
Loading