diff --git a/packages/app/cypress/e2e/overview.cy.ts b/packages/app/cypress/e2e/overview.cy.ts
index 14519064..2047c143 100644
--- a/packages/app/cypress/e2e/overview.cy.ts
+++ b/packages/app/cypress/e2e/overview.cy.ts
@@ -158,7 +158,7 @@ describe('Overview page', () => {
cy.location('search', { timeout: 15_000 }).should('eq', '?tier=75&engine=all');
cy.window().its('__overviewNavigationSentinel').should('eq', 'preserved');
- cy.get('[data-overview-comparison="history"]').click();
+ cy.get('[data-overview-comparison="30d"]').click();
cy.wait('@overviewJson');
cy.location('search', { timeout: 15_000 }).should('eq', '?tier=75&engine=all&compare=30d');
cy.window().its('__overviewNavigationSentinel').should('eq', 'preserved');
@@ -290,7 +290,7 @@ describe('Overview page', () => {
cy.get('[data-testid="overview-engine-scope-switcher"]')
.find('[data-overview-engine-scope="all"]')
.should('have.attr', 'href', '/overview?engine=all&ref=b300');
- cy.get('[data-overview-comparison="history"]').should(
+ cy.get('[data-overview-comparison="30d"]').should(
'have.attr',
'href',
'/overview?ref=b300&compare=30d',
@@ -311,14 +311,14 @@ describe('Overview page', () => {
cy.get('[data-testid="overview-methodology"]').should('contain.text', 'GB200 NVL72 baseline');
});
- it('switches between B200 and 30-day comparison without losing page state', () => {
+ it('switches among hardware and development-speed windows without losing page state', () => {
cy.viewport(1280, 900);
cy.visit('/overview');
cy.get('[data-testid="overview-page"]')
.children('[data-testid="overview-comparison-switcher"]')
.should('have.length', 1)
- .and('have.class', 'justify-center');
+ .and('have.class', 'items-center');
cy.get('[data-testid="overview-comparison-switcher"]')
.should('have.attr', 'aria-label', 'Compare')
.within(() => {
@@ -331,15 +331,21 @@ describe('Overview page', () => {
expect(style.borderBottomWidth).to.equal('2px');
expect(style.backgroundColor).to.match(/rgba\(0, 0, 0, 0\)|transparent/);
});
- cy.get('[data-overview-comparison="history"]')
+ cy.get('[data-overview-comparison="7d"]')
+ .should('have.attr', 'href', '/overview?compare=7d')
+ .and('have.text', '1 week');
+ cy.get('[data-overview-comparison="60d"]')
+ .should('have.attr', 'href', '/overview?compare=60d')
+ .and('have.text', '2 months');
+ cy.get('[data-overview-comparison="30d"]')
.should('have.attr', 'href', '/overview?compare=30d')
- .and('have.text', '30-day change')
+ .and('have.text', '30 days')
.click();
});
cy.location('search').should('eq', '?compare=30d');
cy.get('[data-testid="overview-comparison-switcher"]')
- .find('[data-overview-comparison="history"]')
+ .find('[data-overview-comparison="30d"]')
.should('have.attr', 'aria-current', 'true')
.and('match', 'span');
cy.get('[data-testid="overview-desktop-matrix"] thead').should('contain.text', 'B200');
@@ -360,14 +366,54 @@ describe('Overview page', () => {
.should('have.attr', 'aria-label', '对比方式')
.within(() => {
cy.get('[data-overview-comparison="hardware"]').should('have.text', '对比 B200');
- cy.get('[data-overview-comparison="history"]')
+ cy.get('[data-overview-comparison="30d"]')
.should('have.attr', 'aria-current', 'true')
- .and('have.text', '30 天变化');
+ .and('have.text', '30 天');
});
cy.contains('当前成本及其相对 30–60 天前最近一次有效平台结果的变化。').should('exist');
cy.contains('缺少有效 30 天对比的平台仅显示当前成本。').should('exist');
});
+ it('customizes platform columns and opens a focused presentation view', () => {
+ cy.viewport(1280, 900);
+ cy.visit('/overview?compare=60d');
+
+ cy.get('[data-testid="overview-hardware-select"]')
+ .should('have.attr', 'aria-label', 'Visible platform columns')
+ .click();
+ cy.contains('[role="option"]', 'B300').click();
+
+ cy.location('search').should('eq', '?compare=60d&hw=b200%2Cmi355x%2Cgb200%2Cgb300');
+ cy.get('[data-testid="overview-desktop-matrix"] thead th').then(($headers) => {
+ expect([...$headers].map((header) => header.textContent?.trim())).to.deep.equal([
+ 'Model · Scenario',
+ 'B200',
+ 'MI355X',
+ 'GB200 NVL72',
+ 'GB300 NVL72',
+ ]);
+ });
+ cy.get('[data-testid="overview-platform"][data-hardware="b300"]').should('not.exist');
+
+ cy.get('[data-testid="overview-presentation-toggle"]')
+ .should('have.text', 'Presentation view')
+ .click();
+ cy.get('[data-testid="overview-page"]')
+ .should('have.attr', 'data-presentation', 'true')
+ .and('have.class', 'fixed');
+ cy.get('[data-testid="overview-desktop-matrix"]').should('be.visible');
+ cy.get('[data-testid="overview-mobile-list"]').should('not.be.visible');
+ cy.get('body').should('have.css', 'overflow', 'hidden');
+ cy.get('[data-testid="overview-presentation-toggle"]').should(
+ 'have.text',
+ 'Exit presentation view',
+ );
+
+ cy.get('body').type('{esc}');
+ cy.get('[data-testid="overview-page"]').should('have.attr', 'data-presentation', 'false');
+ cy.get('body').should('not.have.css', 'overflow', 'hidden');
+ });
+
it('compares each platform with its own validated result from 30–60 days earlier', () => {
cy.viewport(1280, 900);
cy.visit('/overview?compare=30d');
diff --git a/packages/app/src/app/(dashboard)/overview/page.tsx b/packages/app/src/app/(dashboard)/overview/page.tsx
index a0c04446..44a3401a 100644
--- a/packages/app/src/app/(dashboard)/overview/page.tsx
+++ b/packages/app/src/app/(dashboard)/overview/page.tsx
@@ -7,6 +7,8 @@ import { enAlternates } from '@/lib/i18n';
import {
resolveOverviewComparisonMode,
resolveOverviewEngineScope,
+ resolveOverviewHardware,
+ resolveOverviewHistoryDays,
resolveOverviewModelScope,
resolveOverviewReferenceHardware,
resolveOverviewTier,
@@ -47,6 +49,8 @@ export default async function OverviewPage({ searchParams }: Props) {
resolveOverviewComparisonMode(sp.compare),
resolveOverviewReferenceHardware(sp.ref),
resolveOverviewModelScope(sp.models),
+ resolveOverviewHistoryDays(sp.compare),
+ resolveOverviewHardware(sp.hw),
);
return ;
}
diff --git a/packages/app/src/app/api/v1/overview/route.test.ts b/packages/app/src/app/api/v1/overview/route.test.ts
index f0415cf3..4ceb9174 100644
--- a/packages/app/src/app/api/v1/overview/route.test.ts
+++ b/packages/app/src/app/api/v1/overview/route.test.ts
@@ -37,12 +37,15 @@ describe('GET /api/v1/overview', () => {
mockGetOverviewPageData.mockResolvedValueOnce(data);
const response = await GET(
- request('/api/v1/overview?tier=75&engine=all&compare=30d&ref=b300&models=all'),
+ request('/api/v1/overview?tier=75&engine=all&compare=60d&ref=b300&models=all&hw=b300,gb300'),
);
expect(response.status).toBe(200);
expect(await response.json()).toEqual(data);
- expect(mockGetOverviewPageData).toHaveBeenCalledWith(75, 'all', 'history', 'b300', 'all');
+ expect(mockGetOverviewPageData).toHaveBeenCalledWith(75, 'all', 'history', 'b300', 'all', 60, [
+ 'b300',
+ 'gb300',
+ ]);
expect(mockCachedJson).toHaveBeenCalledWith(data);
});
@@ -59,6 +62,8 @@ describe('GET /api/v1/overview', () => {
'hardware',
'b200',
'default',
+ 30,
+ ['b200', 'mi355x', 'b300', 'gb200', 'gb300'],
);
});
diff --git a/packages/app/src/app/api/v1/overview/route.ts b/packages/app/src/app/api/v1/overview/route.ts
index ba681916..c98b37aa 100644
--- a/packages/app/src/app/api/v1/overview/route.ts
+++ b/packages/app/src/app/api/v1/overview/route.ts
@@ -4,6 +4,8 @@ import { cachedJson } from '@/lib/api-cache';
import {
resolveOverviewComparisonMode,
resolveOverviewEngineScope,
+ resolveOverviewHardware,
+ resolveOverviewHistoryDays,
resolveOverviewModelScope,
resolveOverviewReferenceHardware,
resolveOverviewTier,
@@ -22,6 +24,8 @@ export async function GET(request: NextRequest) {
resolveOverviewComparisonMode(params.get('compare') ?? undefined),
resolveOverviewReferenceHardware(params.get('ref') ?? undefined),
resolveOverviewModelScope(params.get('models') ?? undefined),
+ resolveOverviewHistoryDays(params.get('compare') ?? undefined),
+ resolveOverviewHardware(params.get('hw') ?? undefined),
);
return cachedJson(data);
} catch (error) {
diff --git a/packages/app/src/app/zh/(dashboard)/overview/page.tsx b/packages/app/src/app/zh/(dashboard)/overview/page.tsx
index 524d15db..1bb6bb0d 100644
--- a/packages/app/src/app/zh/(dashboard)/overview/page.tsx
+++ b/packages/app/src/app/zh/(dashboard)/overview/page.tsx
@@ -7,6 +7,8 @@ import { ZH_OG_LOCALE, zhAlternates } from '@/lib/i18n';
import {
resolveOverviewComparisonMode,
resolveOverviewEngineScope,
+ resolveOverviewHardware,
+ resolveOverviewHistoryDays,
resolveOverviewModelScope,
resolveOverviewReferenceHardware,
resolveOverviewTier,
@@ -48,6 +50,8 @@ export default async function ZhOverviewPage({ searchParams }: Props) {
resolveOverviewComparisonMode(sp.compare),
resolveOverviewReferenceHardware(sp.ref),
resolveOverviewModelScope(sp.models),
+ resolveOverviewHistoryDays(sp.compare),
+ resolveOverviewHardware(sp.hw),
);
return ;
}
diff --git a/packages/app/src/components/overview/overview-hardware-select.tsx b/packages/app/src/components/overview/overview-hardware-select.tsx
new file mode 100644
index 00000000..f1c0e0f1
--- /dev/null
+++ b/packages/app/src/components/overview/overview-hardware-select.tsx
@@ -0,0 +1,83 @@
+'use client';
+
+import { MultiSelect } from '@/components/ui/multi-select';
+import { track } from '@/lib/analytics';
+import {
+ OVERVIEW_HARDWARE,
+ overviewHardwareLabel,
+ type OverviewComparisonMode,
+ type OverviewEngineScope,
+ type OverviewHardware,
+ type OverviewHistoryDays,
+ type OverviewModelScope,
+ type OverviewReferenceHardware,
+ type OverviewTier,
+} from '@/lib/overview-data';
+import { overviewHref } from '@/lib/overview-links';
+
+import { useOverviewNavigation } from './overview-navigation';
+import type { OverviewLocale } from './overview-scorecard';
+
+export function OverviewHardwareSelect({
+ locale,
+ tier,
+ engineScope,
+ comparisonMode,
+ referenceHardware,
+ modelScope,
+ historyDays,
+ value,
+ ariaLabel,
+}: {
+ locale: OverviewLocale;
+ tier: OverviewTier;
+ engineScope: OverviewEngineScope;
+ comparisonMode: OverviewComparisonMode;
+ referenceHardware: OverviewReferenceHardware;
+ modelScope: OverviewModelScope;
+ historyDays: OverviewHistoryDays;
+ value: OverviewHardware[];
+ ariaLabel: string;
+}) {
+ const navigation = useOverviewNavigation();
+ const options = OVERVIEW_HARDWARE.map((hardware) => ({
+ value: hardware,
+ label: overviewHardwareLabel(hardware),
+ }));
+
+ return (
+
+ {
+ const selected = OVERVIEW_HARDWARE.filter((hardware) => nextValues.includes(hardware));
+ const href = overviewHref(
+ locale,
+ tier,
+ engineScope,
+ comparisonMode,
+ referenceHardware,
+ modelScope,
+ historyDays,
+ selected,
+ );
+ track('overview_selector_changed', {
+ control: 'hardware',
+ value: selected.join(','),
+ });
+ navigation.push(href, ['hw']);
+ }}
+ />
+
+ );
+}
diff --git a/packages/app/src/components/overview/overview-navigation.test.tsx b/packages/app/src/components/overview/overview-navigation.test.tsx
index e4b4e64c..bf31738f 100644
--- a/packages/app/src/components/overview/overview-navigation.test.tsx
+++ b/packages/app/src/components/overview/overview-navigation.test.tsx
@@ -30,6 +30,8 @@ function pageData(tier: OverviewTier): OverviewPageData {
comparisonMode: 'hardware',
referenceHardware: 'b200',
modelScope: 'default',
+ historyDays: 30,
+ visibleHardware: ['b200', 'mi355x', 'b300', 'gb200', 'gb300'],
historicalWindow: null,
};
}
diff --git a/packages/app/src/components/overview/overview-page.tsx b/packages/app/src/components/overview/overview-page.tsx
index c5764c5d..be0ecbd4 100644
--- a/packages/app/src/components/overview/overview-page.tsx
+++ b/packages/app/src/components/overview/overview-page.tsx
@@ -1,7 +1,12 @@
'use client';
+import { Maximize2, Minimize2 } from 'lucide-react';
+import { useEffect, useState } from 'react';
+
+import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { ExternalLinkIcon } from '@/components/ui/external-link-icon';
+import { track } from '@/lib/analytics';
import type { OverviewPageData } from '@/lib/overview-data';
import { overviewHref } from '@/lib/overview-links';
@@ -18,6 +23,7 @@ import {
type OverviewLocale,
} from './overview-scorecard';
import { OverviewNavigationProvider, useOverviewNavigation } from './overview-navigation';
+import { OverviewHardwareSelect } from './overview-hardware-select';
/** The SemiAnalysis AI Cloud TCO model behind `HW_REGISTRY.costh`. */
const OVERVIEW_SOURCE_HREF = 'https://semianalysis.com/ai-cloud-tco-model/';
@@ -38,6 +44,8 @@ export function OverviewPageContent({ data, locale }: OverviewPageProps) {
data.comparisonMode,
data.referenceHardware,
data.modelScope,
+ data.historyDays,
+ data.visibleHardware,
)}
>
@@ -47,12 +55,35 @@ export function OverviewPageContent({ data, locale }: OverviewPageProps) {
function OverviewPageBody({ locale }: { locale: OverviewLocale }) {
const { data } = useOverviewNavigation();
+ const [presentation, setPresentation] = useState(false);
const strings = OVERVIEW_STRINGS[locale];
const formatters = overviewFormatters(locale);
+ useEffect(() => {
+ if (!presentation) return;
+ const previousOverflow = document.body.style.overflow;
+ document.body.style.overflow = 'hidden';
+ const onKeyDown = (event: KeyboardEvent) => {
+ if (event.key === 'Escape') setPresentation(false);
+ };
+ document.addEventListener('keydown', onKeyDown);
+ return () => {
+ document.body.style.overflow = previousOverflow;
+ document.removeEventListener('keydown', onKeyDown);
+ };
+ }, [presentation]);
+
return (
-
-
+
+
{/* Two rows at every width: the title, then the metric it is
measured in and where that measure comes from. */}
@@ -105,6 +136,8 @@ function OverviewPageBody({ locale }: { locale: OverviewLocale }) {
comparisonMode={data.comparisonMode}
referenceHardware={data.referenceHardware}
modelScope={data.modelScope}
+ historyDays={data.historyDays}
+ visibleHardware={data.visibleHardware}
locale={locale}
strings={strings}
/>
@@ -114,6 +147,8 @@ function OverviewPageBody({ locale }: { locale: OverviewLocale }) {
comparisonMode={data.comparisonMode}
referenceHardware={data.referenceHardware}
modelScope={data.modelScope}
+ historyDays={data.historyDays}
+ visibleHardware={data.visibleHardware}
locale={locale}
strings={strings}
/>
@@ -127,10 +162,61 @@ function OverviewPageBody({ locale }: { locale: OverviewLocale }) {
tier={data.tier}
referenceHardware={data.referenceHardware}
modelScope={data.modelScope}
+ historyDays={data.historyDays}
+ visibleHardware={data.visibleHardware}
locale={locale}
strings={strings}
+ hidden={presentation}
/>
+
+
+ {presentation ? (
+
+
{strings.title}
+
+ {data.comparisonMode === 'history'
+ ? `${strings.developmentSpeedLabel}: ${strings.comparisonOptions[data.historyDays]}`
+ : strings.hardwareComparisonLabel(
+ data.models[0]?.platforms.find(
+ (platform) => platform.hardware === data.referenceHardware,
+ )?.hardwareLabel ?? data.referenceHardware,
+ )}
+
+
+ ) : null}
+
+ {strings.hardwareColumnsLabel}
+
+
+
+
+
+
{/* Official-only summary; uploaded runs remain in the linked dashboard. */}
{/* Clipped on phones for the rounded corners; visible from xl so the
desktop matrix header can stick to the page as it scrolls. */}
@@ -142,6 +228,9 @@ function OverviewPageBody({ locale }: { locale: OverviewLocale }) {
strings={strings}
comparisonMode={data.comparisonMode}
referenceHardware={data.referenceHardware}
+ historyDays={data.historyDays}
+ visibleHardware={data.visibleHardware}
+ presentation={presentation}
/>
diff --git a/packages/app/src/components/overview/overview-scorecard.tsx b/packages/app/src/components/overview/overview-scorecard.tsx
index a52afc8b..22e29248 100644
--- a/packages/app/src/components/overview/overview-scorecard.tsx
+++ b/packages/app/src/components/overview/overview-scorecard.tsx
@@ -1,10 +1,13 @@
import {
OVERVIEW_DEFAULT_REFERENCE_HARDWARE,
OVERVIEW_HARDWARE,
+ OVERVIEW_HISTORY_WINDOWS,
OVERVIEW_TIERS,
overviewHardwareLabel,
type OverviewComparisonMode,
type OverviewEngineScope,
+ type OverviewHardware,
+ type OverviewHistoryDays,
type OverviewHistoricalComparison,
type OverviewModelScope,
type OverviewModelSummary,
@@ -48,14 +51,21 @@ export const OVERVIEW_STRINGS = {
},
comparisonNavLabel: 'Compare',
comparisonOptions: {
- history: '30-day change',
+ 7: '1 week',
+ 30: '30 days',
+ 60: '2 months',
},
+ developmentSpeedLabel: 'Development speed',
+ hardwareColumnsLabel: 'Platforms',
+ hardwareColumnsAria: 'Visible platform columns',
+ presentationView: 'Presentation view',
+ exitPresentationView: 'Exit presentation view',
hardwareComparisonLabel: (reference: string) => `vs ${reference}`,
referenceSelectorAria: 'Reference hardware',
caption:
'Cost per million total tokens from each platform’s best observed serving envelope for the scenario shown with each model.',
- historyCaption:
- 'Current cost and change versus the latest validated platform result 30–60 days earlier.',
+ historyCaption: (days: number) =>
+ `Current cost and change versus the latest validated platform result ${days}–${days * 2} days earlier.`,
modelHeader: 'Model · Scenario',
scenarioLabels: {
single_turn_8k1k: '8K/1K',
@@ -92,7 +102,8 @@ export const OVERVIEW_STRINGS = {
`${pct} ${cheaper ? 'cheaper' : 'more expensive'} than this platform’s ${baselineDate} result`,
historicalEvenAria: (baselineDate: string) =>
`About the same cost as this platform’s ${baselineDate} result`,
- historyCellStateLegend: 'Platforms without a valid 30-day comparison show current cost only.',
+ historyCellStateLegend: (days: number) =>
+ `Platforms without a valid ${days}-day comparison show current cost only.`,
referenceHeader: 'Reference',
modelScopeNavLabel: 'Inactive models',
modelScopeShow: 'Show deprecated & maintenance-mode models',
@@ -119,12 +130,20 @@ export const OVERVIEW_STRINGS = {
},
comparisonNavLabel: '对比方式',
comparisonOptions: {
- history: '30 天变化',
+ 7: '1 周',
+ 30: '30 天',
+ 60: '2 个月',
},
+ developmentSpeedLabel: '开发速度',
+ hardwareColumnsLabel: '平台',
+ hardwareColumnsAria: '可见平台列',
+ presentationView: '演示视图',
+ exitPresentationView: '退出演示视图',
hardwareComparisonLabel: (reference: string) => `对比 ${reference}`,
referenceSelectorAria: '基准硬件',
caption: '按各模型标注的场景,基于各平台最佳观测服务包络线计算每百万总 token 成本。',
- historyCaption: '当前成本及其相对 30–60 天前最近一次有效平台结果的变化。',
+ historyCaption: (days: number) =>
+ `当前成本及其相对 ${days}–${days * 2} 天前最近一次有效平台结果的变化。`,
modelHeader: '模型 · 场景',
scenarioLabels: {
single_turn_8k1k: '8K/1K',
@@ -159,7 +178,7 @@ export const OVERVIEW_STRINGS = {
historicalDeltaAria: (pct: string, cheaper: boolean, baselineDate: string) =>
`比该平台 ${baselineDate} 的结果${cheaper ? '便宜' : '昂贵'} ${pct}`,
historicalEvenAria: (baselineDate: string) => `与该平台 ${baselineDate} 的结果成本基本持平`,
- historyCellStateLegend: '缺少有效 30 天对比的平台仅显示当前成本。',
+ historyCellStateLegend: (days: number) => `缺少有效 ${days} 天对比的平台仅显示当前成本。`,
referenceHeader: '基准',
modelScopeNavLabel: '非活跃模型',
modelScopeShow: '显示已弃用与维护模式模型',
@@ -635,6 +654,9 @@ interface SurfaceProps {
strings: OverviewStrings;
comparisonMode: OverviewComparisonMode;
referenceHardware: OverviewReferenceHardware;
+ historyDays: OverviewHistoryDays;
+ visibleHardware: OverviewHardware[];
+ presentation?: boolean;
}
export function DesktopOverviewMatrix({
@@ -644,14 +666,20 @@ export function DesktopOverviewMatrix({
strings,
comparisonMode,
referenceHardware,
+ historyDays,
+ visibleHardware,
+ presentation = false,
}: SurfaceProps) {
- const platforms = models[0]?.platforms ?? [];
+ const visible = new Set(visibleHardware);
+ const platforms = (models[0]?.platforms ?? []).filter((platform) =>
+ visible.has(platform.hardware as OverviewHardware),
+ );
const referenceLabel = overviewHardwareLabel(referenceHardware);
return (
-
+
- {comparisonMode === 'history' ? strings.historyCaption : strings.caption}
+ {comparisonMode === 'history' ? strings.historyCaption(historyDays) : strings.caption}
@@ -708,24 +736,26 @@ export function DesktopOverviewMatrix({
)}
- {model.platforms.map((platform) => (
- |
-
- |
- ))}
+ {model.platforms
+ .filter((platform) => visible.has(platform.hardware as OverviewHardware))
+ .map((platform) => (
+
+
+ |
+ ))}
))}
@@ -741,10 +771,16 @@ export function MobileOverviewList({
strings,
comparisonMode,
referenceHardware,
+ visibleHardware,
+ presentation = false,
}: SurfaceProps) {
const referenceLabel = overviewHardwareLabel(referenceHardware);
+ const visible = new Set(visibleHardware);
return (
-
+
{models.map((model) => (
-
- {model.platforms.map((platform) => (
-
-
visible.has(platform.hardware as OverviewHardware))
+ .map((platform) => (
+
- {platform.hardwareLabel}
-
-
-
- ))}
+
+ {platform.hardwareLabel}
+
+
+
+ ))}
{comparisonMode === 'history' ? null : (
({
value: hardware,
label: overviewHardwareLabel(hardware),
- href: overviewHref(locale, tier, engineScope, 'hardware', hardware, modelScope),
+ href: overviewHref(
+ locale,
+ tier,
+ engineScope,
+ 'hardware',
+ hardware,
+ modelScope,
+ historyDays,
+ visibleHardware,
+ ),
}));
const optionClass =
'relative inline-flex min-h-11 min-w-[130px] items-center justify-center whitespace-nowrap border-b-2 border-transparent px-4 py-2 text-sm font-semibold text-muted-foreground transition-colors duration-200 hover:border-muted-foreground/30 focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring sm:min-w-[140px]';
return (
-
+ {strings.developmentSpeedLabel}
+
);
}
@@ -1009,6 +1108,8 @@ export function OverviewModelScopeToggle({
engineScope,
comparisonMode,
referenceHardware,
+ historyDays,
+ visibleHardware,
locale,
strings,
}: {
@@ -1017,6 +1118,8 @@ export function OverviewModelScopeToggle({
engineScope: OverviewEngineScope;
comparisonMode: OverviewComparisonMode;
referenceHardware: OverviewReferenceHardware;
+ historyDays: OverviewHistoryDays;
+ visibleHardware: OverviewHardware[];
locale: OverviewLocale;
strings: OverviewStrings;
}) {
@@ -1029,7 +1132,16 @@ export function OverviewModelScopeToggle({
>
- {comparisonMode === 'history' ? {strings.historyCaption}
: null}
+ {comparisonMode === 'history' ? {strings.historyCaption(historyDays)}
: null}
{comparisonMode === 'history'
- ? strings.historyCellStateLegend
+ ? strings.historyCellStateLegend(historyDays)
: strings.cellStateLegend(referenceLabel)}
{strings.methodologyNote}
diff --git a/packages/app/src/components/ui/multi-select.tsx b/packages/app/src/components/ui/multi-select.tsx
index 767edcab..78682d5f 100644
--- a/packages/app/src/components/ui/multi-select.tsx
+++ b/packages/app/src/components/ui/multi-select.tsx
@@ -43,6 +43,7 @@ interface MultiSelectProps {
clearSearchLabel?: string;
selectedSuffix?: string;
minimumPrefix?: string;
+ ariaLabel?: string;
}
function MultiSelect({
@@ -69,6 +70,7 @@ function MultiSelect({
clearSearchLabel = 'Clear search',
selectedSuffix = ' selected',
minimumPrefix = 'Minimum: ',
+ ariaLabel,
}: MultiSelectProps) {
const [internalIsOpen, setInternalIsOpen] = React.useState(false);
const [search, setSearch] = React.useState('');
@@ -257,6 +259,7 @@ function MultiSelect({
aria-expanded={isOpen}
aria-haspopup="listbox"
aria-controls={listboxId}
+ aria-label={ariaLabel}
onClick={() => !disabled && setIsOpen(!isOpen)}
disabled={disabled}
data-slot="select-trigger"
diff --git a/packages/app/src/lib/overview-data.server.test.ts b/packages/app/src/lib/overview-data.server.test.ts
index ab29b283..0509c397 100644
--- a/packages/app/src/lib/overview-data.server.test.ts
+++ b/packages/app/src/lib/overview-data.server.test.ts
@@ -188,6 +188,33 @@ describe('getOverviewPageData engine scope forwarding', () => {
expect(getCachedBenchmarks).not.toHaveBeenCalled();
expect(getCachedBenchmarksAsOf).not.toHaveBeenCalled();
});
+
+ it('forwards a custom development window and visible platform selection', async () => {
+ const getCachedBenchmarksAsOf = vi.fn((_keys: string[], _date: string) => Promise.resolve([]));
+ vi.doMock('@semianalysisai/inferencex-db/connection', () => ({ FIXTURES_MODE: false }));
+ vi.doMock('@/lib/benchmark-data.server', () => ({
+ getCachedBenchmarks: vi.fn(() => Promise.resolve(rows)),
+ getCachedBenchmarksAsOf,
+ }));
+ vi.doMock('@/lib/test-fixtures', () => ({ loadFixture: vi.fn() }));
+
+ const { getOverviewPageData } = await import('./overview-data.server');
+ const page = await getOverviewPageData(50, 'community', 'history', 'b200', 'default', 7, [
+ 'b200',
+ 'gb300',
+ ]);
+
+ expect(page.historyDays).toBe(7);
+ expect(page.visibleHardware).toEqual(['b200', 'gb300']);
+ expect(page.historicalWindow).toEqual({
+ snapshotDate: '2026-07-20',
+ targetDate: '2026-07-13',
+ earliestDate: '2026-07-06',
+ });
+ expect(getCachedBenchmarksAsOf.mock.calls.every(([, date]) => date === '2026-07-13')).toBe(
+ true,
+ );
+ });
});
describe('getOverviewPageData model scope forwarding', () => {
diff --git a/packages/app/src/lib/overview-data.server.ts b/packages/app/src/lib/overview-data.server.ts
index 4b0518af..d7d9ef92 100644
--- a/packages/app/src/lib/overview-data.server.ts
+++ b/packages/app/src/lib/overview-data.server.ts
@@ -8,6 +8,7 @@ import {
assembleOverviewHistoricalPageData,
assembleOverviewPageData,
OVERVIEW_DEFAULT_COMPARISON_MODE,
+ OVERVIEW_DEFAULT_HISTORY_DAYS,
OVERVIEW_DEFAULT_MODEL_SCOPE,
OVERVIEW_PRIMARY_TIER,
OVERVIEW_DEFAULT_REFERENCE_HARDWARE,
@@ -16,6 +17,8 @@ import {
overviewSnapshotDate,
type OverviewComparisonMode,
type OverviewEngineScope,
+ type OverviewHardware,
+ type OverviewHistoryDays,
type OverviewModelScope,
type OverviewPageData,
type OverviewReferenceHardware,
@@ -43,6 +46,8 @@ export async function getOverviewPageData(
comparisonMode: OverviewComparisonMode = OVERVIEW_DEFAULT_COMPARISON_MODE,
referenceHardware: OverviewReferenceHardware = OVERVIEW_DEFAULT_REFERENCE_HARDWARE,
modelScope: OverviewModelScope = OVERVIEW_DEFAULT_MODEL_SCOPE,
+ historyDays: OverviewHistoryDays = OVERVIEW_DEFAULT_HISTORY_DAYS,
+ visibleHardware?: OverviewHardware[],
): Promise {
const models = overviewModelsForScope(modelScope);
// Synthetic rows go through the same assemblers as the live path, so a
@@ -57,10 +62,11 @@ export async function getOverviewPageData(
engineScope,
referenceHardware,
modelScope,
+ visibleHardware,
);
}
- // Note (wenyao): the 30-day window must not move when inactive models are
+ // Note (wenyao): the selected history window must not move when inactive models are
// revealed — a maintenance model can still post occasional runs, and a newer
// one would silently shift the cost deltas already shown on default rows.
// Anchor the snapshot to the default models in every scope.
@@ -82,12 +88,14 @@ export async function getOverviewPageData(
engineScope,
referenceHardware,
modelScope,
+ visibleHardware,
),
comparisonMode: 'history',
+ historyDays,
};
}
- const window = overviewHistoricalWindow(snapshotDate);
+ const window = overviewHistoricalWindow(snapshotDate, historyDays);
const unboundedBaselineRows = FIXTURES_MODE
? loadFixture>('overview-history-rows')
: await loadRowsByModel(models, (keys) => getCachedBenchmarksAsOf(keys, window.targetDate));
@@ -106,5 +114,7 @@ export async function getOverviewPageData(
engineScope,
referenceHardware,
modelScope,
+ historyDays,
+ visibleHardware,
);
}
diff --git a/packages/app/src/lib/overview-data.test.ts b/packages/app/src/lib/overview-data.test.ts
index ce098a0c..0fed4c7d 100644
--- a/packages/app/src/lib/overview-data.test.ts
+++ b/packages/app/src/lib/overview-data.test.ts
@@ -23,6 +23,8 @@ import {
overviewTierEvidenceDate,
resolveOverviewComparisonMode,
resolveOverviewEngineScope,
+ resolveOverviewHardware,
+ resolveOverviewHistoryDays,
resolveOverviewModelScope,
resolveOverviewReferenceHardware,
resolveOverviewTier,
@@ -157,12 +159,23 @@ describe('overview engine scope and scenario selection', () => {
[undefined, 'hardware'],
['hardware', 'hardware'],
['30d', 'history'],
+ ['7d', 'history'],
+ ['60d', 'history'],
+ ['14d', 'hardware'],
[['30d'], 'history'],
['unknown', 'hardware'],
] as const)('resolves comparison mode %j to %s', (raw, expected) => {
expect(resolveOverviewComparisonMode(raw)).toBe(expected);
});
+ it('resolves development windows and visible hardware in canonical matrix order', () => {
+ expect(resolveOverviewHistoryDays('7d')).toBe(7);
+ expect(resolveOverviewHistoryDays('60d')).toBe(60);
+ expect(resolveOverviewHistoryDays('14d')).toBe(30);
+ expect(resolveOverviewHardware('gb300,b200')).toEqual(['b200', 'gb300']);
+ expect(resolveOverviewHardware('h100')).toEqual(['b200', 'mi355x', 'b300', 'gb200', 'gb300']);
+ });
+
it('assigns each active model to its configured scenario', () => {
expect(overviewScenarioForModel(Model.Kimi_K3)).toBe('agentx');
expect(overviewScenarioForModel(Model.GLM_5_2)).toBe('agentx');
@@ -680,6 +693,19 @@ describe('overview historical window', () => {
});
});
+ it('supports one-week and two-month comparison windows', () => {
+ expect(overviewHistoricalWindow('2026-08-03', 7)).toEqual({
+ snapshotDate: '2026-08-03',
+ targetDate: '2026-07-27',
+ earliestDate: '2026-07-20',
+ });
+ expect(overviewHistoricalWindow('2026-08-03', 60)).toEqual({
+ snapshotDate: '2026-08-03',
+ targetDate: '2026-06-04',
+ earliestDate: '2026-04-05',
+ });
+ });
+
it('returns the latest date across model buckets', () => {
expect(
overviewSnapshotDate({
diff --git a/packages/app/src/lib/overview-data.ts b/packages/app/src/lib/overview-data.ts
index c20d0136..097ec8c6 100644
--- a/packages/app/src/lib/overview-data.ts
+++ b/packages/app/src/lib/overview-data.ts
@@ -30,6 +30,10 @@ export type OverviewTier = (typeof OVERVIEW_TIERS)[number];
export const OVERVIEW_PRIMARY_TIER = 50;
export const OVERVIEW_HARDWARE = ['b200', 'mi355x', 'b300', 'gb200', 'gb300'] as const;
export type OverviewReferenceHardware = (typeof OVERVIEW_HARDWARE)[number];
+export type OverviewHardware = OverviewReferenceHardware;
+export const OVERVIEW_HISTORY_WINDOWS = [7, 30, 60] as const;
+export type OverviewHistoryDays = (typeof OVERVIEW_HISTORY_WINDOWS)[number];
+export const OVERVIEW_DEFAULT_HISTORY_DAYS: OverviewHistoryDays = 30;
export const OVERVIEW_DEFAULT_REFERENCE_HARDWARE: OverviewReferenceHardware = 'b200';
export type OverviewEngineScope = 'all' | 'community';
export type OverviewComparisonMode = 'hardware' | 'history';
@@ -61,7 +65,30 @@ export function resolveOverviewComparisonMode(
raw: string | readonly string[] | undefined,
): OverviewComparisonMode {
const candidate = Array.isArray(raw) ? raw[0] : raw;
- return candidate === '30d' ? 'history' : OVERVIEW_DEFAULT_COMPARISON_MODE;
+ return OVERVIEW_HISTORY_WINDOWS.some((days) => candidate === `${days}d`)
+ ? 'history'
+ : OVERVIEW_DEFAULT_COMPARISON_MODE;
+}
+
+export function resolveOverviewHistoryDays(
+ raw: string | readonly string[] | undefined,
+): OverviewHistoryDays {
+ const candidate = Array.isArray(raw) ? raw[0] : raw;
+ const days = Number(candidate?.replace(/d$/u, ''));
+ return (
+ OVERVIEW_HISTORY_WINDOWS.find((windowDays) => windowDays === days) ??
+ OVERVIEW_DEFAULT_HISTORY_DAYS
+ );
+}
+
+export function resolveOverviewHardware(
+ raw: string | readonly string[] | undefined,
+): OverviewHardware[] {
+ const candidate = Array.isArray(raw) ? raw[0] : raw;
+ if (candidate === undefined) return [...OVERVIEW_HARDWARE];
+ const requested = new Set(candidate.split(','));
+ const hardware = OVERVIEW_HARDWARE.filter((value) => requested.has(value));
+ return hardware.length === 0 ? [...OVERVIEW_HARDWARE] : hardware;
}
export function resolveOverviewModelScope(
@@ -179,6 +206,8 @@ export interface OverviewPageData {
comparisonMode: OverviewComparisonMode;
referenceHardware: OverviewReferenceHardware;
modelScope: OverviewModelScope;
+ historyDays: OverviewHistoryDays;
+ visibleHardware: OverviewHardware[];
historicalWindow: OverviewHistoricalWindow | null;
}
@@ -226,11 +255,14 @@ export function overviewSnapshotDate(
return dates.length === 0 ? null : (dates.toSorted().at(-1) ?? null);
}
-export function overviewHistoricalWindow(snapshotDate: string): OverviewHistoricalWindow {
+export function overviewHistoricalWindow(
+ snapshotDate: string,
+ days: OverviewHistoryDays = OVERVIEW_DEFAULT_HISTORY_DAYS,
+): OverviewHistoricalWindow {
return {
snapshotDate,
- targetDate: subtractUtcDays(snapshotDate, 30),
- earliestDate: subtractUtcDays(snapshotDate, 60),
+ targetDate: subtractUtcDays(snapshotDate, days),
+ earliestDate: subtractUtcDays(snapshotDate, days * 2),
};
}
@@ -695,6 +727,7 @@ export function assembleOverviewPageData(
engineScope: OverviewEngineScope = 'community',
referenceHardware: OverviewReferenceHardware = OVERVIEW_DEFAULT_REFERENCE_HARDWARE,
modelScope: OverviewModelScope = OVERVIEW_DEFAULT_MODEL_SCOPE,
+ visibleHardware: OverviewHardware[] = [...OVERVIEW_HARDWARE],
): OverviewPageData {
const perModel = overviewModelsForScope(modelScope).map((model) => ({
model,
@@ -711,6 +744,8 @@ export function assembleOverviewPageData(
comparisonMode: OVERVIEW_DEFAULT_COMPARISON_MODE,
referenceHardware,
modelScope,
+ historyDays: OVERVIEW_DEFAULT_HISTORY_DAYS,
+ visibleHardware,
historicalWindow: null,
};
}
@@ -730,6 +765,8 @@ export function assembleOverviewHistoricalPageData(
engineScope: OverviewEngineScope = 'community',
referenceHardware: OverviewReferenceHardware = OVERVIEW_DEFAULT_REFERENCE_HARDWARE,
modelScope: OverviewModelScope = OVERVIEW_DEFAULT_MODEL_SCOPE,
+ historyDays: OverviewHistoryDays = OVERVIEW_DEFAULT_HISTORY_DAYS,
+ visibleHardware: OverviewHardware[] = [...OVERVIEW_HARDWARE],
): OverviewPageData {
const current = assembleOverviewPageData(
currentRowsByModel,
@@ -737,6 +774,7 @@ export function assembleOverviewHistoricalPageData(
engineScope,
referenceHardware,
modelScope,
+ visibleHardware,
);
const baseline = assembleOverviewPageData(
baselineRowsByModel,
@@ -744,6 +782,7 @@ export function assembleOverviewHistoricalPageData(
engineScope,
referenceHardware,
modelScope,
+ visibleHardware,
);
const baselineByKey = new Map(
baseline.models.flatMap((model) =>
@@ -754,6 +793,7 @@ export function assembleOverviewHistoricalPageData(
return {
...current,
comparisonMode: 'history',
+ historyDays,
historicalWindow: window,
models: current.models.map((model) => ({
...model,
diff --git a/packages/app/src/lib/overview-links.test.ts b/packages/app/src/lib/overview-links.test.ts
index 63ef4a08..7234e5fb 100644
--- a/packages/app/src/lib/overview-links.test.ts
+++ b/packages/app/src/lib/overview-links.test.ts
@@ -248,6 +248,18 @@ describe('overviewHref', () => {
);
});
+ it('serializes every supported development window and visible hardware selection', () => {
+ expect(overviewHref('en', 50, 'community', 'history', 'b200', 'default', 7)).toBe(
+ '/overview?compare=7d',
+ );
+ expect(overviewHref('en', 50, 'community', 'history', 'b200', 'default', 60)).toBe(
+ '/overview?compare=60d',
+ );
+ expect(
+ overviewHref('zh', 50, 'community', 'hardware', 'b200', 'default', 30, ['b200', 'gb300']),
+ ).toBe('/zh/overview?hw=b200%2Cgb300');
+ });
+
it('omits the B200 default reference and preserves a non-default reference in every mode', () => {
expect(overviewHref('en', 50, 'community', 'hardware', 'b200')).toBe('/overview');
expect(overviewHref('en', 50, 'community', 'hardware', 'b300')).toBe('/overview?ref=b300');
@@ -362,4 +374,10 @@ describe('overview model scope links', () => {
'/overview?tier=75',
);
});
+
+ it('merges visible hardware without dropping the active history window', () => {
+ expect(
+ mergeOverviewControlHref('/overview?compare=60d', '/overview?hw=b200%2Cgb300', ['hw']),
+ ).toBe('/overview?compare=60d&hw=b200%2Cgb300');
+ });
});
diff --git a/packages/app/src/lib/overview-links.ts b/packages/app/src/lib/overview-links.ts
index 5412ef39..94d9b6e2 100644
--- a/packages/app/src/lib/overview-links.ts
+++ b/packages/app/src/lib/overview-links.ts
@@ -1,25 +1,30 @@
import { runIdFromRunUrl } from './known-issues';
import {
OVERVIEW_DEFAULT_COMPARISON_MODE,
+ OVERVIEW_DEFAULT_HISTORY_DAYS,
OVERVIEW_DEFAULT_MODEL_SCOPE,
OVERVIEW_DEFAULT_REFERENCE_HARDWARE,
OVERVIEW_PRIMARY_TIER,
+ OVERVIEW_HARDWARE,
type OverviewComparisonMode,
type OverviewConfigResult,
type OverviewEngineScope,
+ type OverviewHardware,
+ type OverviewHistoryDays,
type OverviewModelScope,
type OverviewModelSummary,
type OverviewReferenceHardware,
type OverviewTier,
} from './overview-data';
-export type OverviewSearchKey = 'tier' | 'engine' | 'ref' | 'compare' | 'models';
+export type OverviewSearchKey = 'tier' | 'engine' | 'ref' | 'compare' | 'hw' | 'models';
const OVERVIEW_SEARCH_ORDER: readonly OverviewSearchKey[] = [
'tier',
'engine',
'ref',
'compare',
+ 'hw',
'models',
];
@@ -128,7 +133,7 @@ function uniqueValues(values: readonly string[]): string {
}
/**
- * Dashboard view for one Overview 30-day cell. The two independently ranked
+ * Dashboard view for one Overview historical-comparison cell. The two independently ranked
* serving envelopes are carried explicitly so the chart can show exactly the
* curves behind the percentage even when engine, precision, or topology changed.
*/
@@ -188,6 +193,8 @@ export function overviewHref(
comparisonMode: OverviewComparisonMode = OVERVIEW_DEFAULT_COMPARISON_MODE,
referenceHardware: OverviewReferenceHardware = OVERVIEW_DEFAULT_REFERENCE_HARDWARE,
modelScope: OverviewModelScope = OVERVIEW_DEFAULT_MODEL_SCOPE,
+ historyDays: OverviewHistoryDays = OVERVIEW_DEFAULT_HISTORY_DAYS,
+ visibleHardware: readonly OverviewHardware[] = OVERVIEW_HARDWARE,
): string {
const base = locale === 'zh' ? '/zh/overview' : '/overview';
const query = new URLSearchParams();
@@ -196,7 +203,13 @@ export function overviewHref(
if (referenceHardware !== OVERVIEW_DEFAULT_REFERENCE_HARDWARE) {
query.set('ref', referenceHardware);
}
- if (comparisonMode === 'history') query.set('compare', '30d');
+ if (comparisonMode === 'history') query.set('compare', `${historyDays}d`);
+ if (
+ visibleHardware.length !== OVERVIEW_HARDWARE.length ||
+ visibleHardware.some((hardware, index) => hardware !== OVERVIEW_HARDWARE[index])
+ ) {
+ query.set('hw', visibleHardware.join(','));
+ }
if (modelScope !== OVERVIEW_DEFAULT_MODEL_SCOPE) query.set('models', modelScope);
const search = query.toString();
return search === '' ? base : `${base}?${search}`;
@@ -210,8 +223,19 @@ export function overviewTierHref(
comparisonMode: OverviewComparisonMode = OVERVIEW_DEFAULT_COMPARISON_MODE,
referenceHardware: OverviewReferenceHardware = OVERVIEW_DEFAULT_REFERENCE_HARDWARE,
modelScope: OverviewModelScope = OVERVIEW_DEFAULT_MODEL_SCOPE,
+ historyDays: OverviewHistoryDays = OVERVIEW_DEFAULT_HISTORY_DAYS,
+ visibleHardware: readonly OverviewHardware[] = OVERVIEW_HARDWARE,
): string {
- return overviewHref(locale, tier, engineScope, comparisonMode, referenceHardware, modelScope);
+ return overviewHref(
+ locale,
+ tier,
+ engineScope,
+ comparisonMode,
+ referenceHardware,
+ modelScope,
+ historyDays,
+ visibleHardware,
+ );
}
/** Engine-scope switch preserving the active service tier. */
@@ -222,6 +246,17 @@ export function overviewEngineScopeHref(
comparisonMode: OverviewComparisonMode = OVERVIEW_DEFAULT_COMPARISON_MODE,
referenceHardware: OverviewReferenceHardware = OVERVIEW_DEFAULT_REFERENCE_HARDWARE,
modelScope: OverviewModelScope = OVERVIEW_DEFAULT_MODEL_SCOPE,
+ historyDays: OverviewHistoryDays = OVERVIEW_DEFAULT_HISTORY_DAYS,
+ visibleHardware: readonly OverviewHardware[] = OVERVIEW_HARDWARE,
): string {
- return overviewHref(locale, tier, engineScope, comparisonMode, referenceHardware, modelScope);
+ return overviewHref(
+ locale,
+ tier,
+ engineScope,
+ comparisonMode,
+ referenceHardware,
+ modelScope,
+ historyDays,
+ visibleHardware,
+ );
}
diff --git a/packages/app/src/lib/url-state.ts b/packages/app/src/lib/url-state.ts
index c0216233..1575eb7b 100644
--- a/packages/app/src/lib/url-state.ts
+++ b/packages/app/src/lib/url-state.ts
@@ -53,7 +53,7 @@ const URL_STATE_KEYS = [
'i_fw',
'i_disagg',
'i_spec',
- // Exact serving-envelope pair behind an Overview 30-day comparison cell.
+ // Exact serving-envelope pair behind an Overview historical comparison cell.
'i_overview_current',
'i_overview_baseline',
// Evaluation