Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f234013
create embed URL with iframe-safe layout and CSP
Apr 30, 2026
0d6dc27
iframe-safe embed scatter route with canonical URL support
May 1, 2026
4194160
Merge branch 'master' into embeddable-charts-269
adibarra May 2, 2026
a138f6f
skip e2e
May 5, 2026
4ec17c4
chore(charts): remove experimental model support disclaimer (#283)
functionstackx May 2, 2026
2ac9903
feat(inference): bus/car overlay + searchable Y-axis metric selector …
functionstackx May 2, 2026
bf9a8bb
feat(inference): line labels for unofficial run rooflines + AGENTS ru…
functionstackx May 2, 2026
6308145
feat(inference): Donkey / Elytra Minecraft overlay alongside Bus / Ra…
functionstackx May 2, 2026
a27c93c
feat(gpu-graph): date line labels on GPU comparison rooflines (#288)
functionstackx May 3, 2026
aab016b
add SGLang 0.5.6 B200 DeepSeek R1 FP4 article (#289)
adibarra May 3, 2026
785f8c5
fix(eval-samples): match artifacts after framework-alias canonicaliza…
adibarra May 3, 2026
f731e39
fix: close precision multiselect when focus moves to other filters (#…
lalithsagar10 May 3, 2026
ed34dc5
chore: dep bumps (#291)
adibarra May 3, 2026
f391046
chore: update dependabot label (#292)
adibarra May 3, 2026
a276b89
simplify hwToGpuKey: split on first '-' instead of explicit suffix li…
adibarra May 3, 2026
e3e2f09
feat(theme): minecraft assets scattered around + Ender Dragon fly-acr…
functionstackx May 4, 2026
451f072
fix(db): drop tp/ep/num_gpu >= 1 checks to allow aggregated multinode…
adibarra May 4, 2026
bcaaf91
chore: bump the all-minor-patch group across 1 directory with 4 updat…
dependabot[bot] May 4, 2026
b7a5520
fix(inference): y_costn y-axis label should be ($) not (%) (#300)
adibarra May 4, 2026
cbbff0c
chore(ci): consolidate claude workflows into one file with implement+…
adibarra May 4, 2026
81f854c
fix(ci): claude workflow if-block evaluates false for issues events (…
adibarra May 4, 2026
35797ee
fix(ci): claude workflow dev server failing due to missing .env + db …
adibarra May 5, 2026
e52be00
test(e2e): improve run speed, consolidate spec files (#306)
rafaykhan-source May 5, 2026
b5ce5c2
chore(ci): unify @claude trigger phrase with second-word routing (#308)
adibarra May 5, 2026
52d41ff
feat(theme): play Ender Dragon growl during fly-across (#307)
functionstackx May 5, 2026
e13e0cd
fix build failure
May 5, 2026
2a1035d
Merge branch 'master' into embeddable-charts-269
lalithsagar10 May 5, 2026
9c42052
Merge branch 'master' into embeddable-charts-269
lalithsagar10 May 7, 2026
f0bb9c6
fix skipped tests
May 7, 2026
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
56 changes: 0 additions & 56 deletions .env.example

This file was deleted.

12 changes: 12 additions & 0 deletions .github/workflows/tests-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ jobs:
run: pnpm install --frozen-lockfile
env:
CYPRESS_INSTALL_BINARY: '0'
- name: Create CI env file
env:
DATABASE_READONLY_URL: ${{ secrets.DATABASE_READONLY_URL }}
GITHUB_TOKEN: ${{ secrets.INFX_MAIN_PAT }}
run: |
DB_URL="${DATABASE_READONLY_URL:-postgresql://postgres:postgres@localhost:5432/postgres}"
cat > .env <<EOF
DATABASE_READONLY_URL=$DB_URL
DATABASE_DRIVER=neon
DATABASE_SSL=true
GITHUB_TOKEN=${GITHUB_TOKEN:-}
EOF
- name: Cache Cypress binary
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,37 @@ pnpm dev

Open [http://localhost:3000](http://localhost:3000) with your browser.

## Embedding Scatter Chart

Use `/embed/scatter` for iframe-safe inference chart embeds.

### Canonical URL contract (recommended)

Use the same canonical query keys as dashboard state:

- `g_model` (e.g. `DeepSeek-R1-0528`)
- `i_seq` (e.g. `1024/1024`)
- `i_prec` (e.g. `fp8`, comma-separated for multi-select)
- `i_gpus` (e.g. `h200-sxm,b200-sxm,mi325x`)
- `i_metric` (e.g. `y_tpPerGpu`)
- `chart` (`interactivity` default, or `e2e`)

Example:

```text
/embed/scatter?g_model=DeepSeek-R1-0528&i_seq=1024/1024&i_prec=fp8&i_gpus=h200-sxm,b200-sxm,mi325x&i_metric=y_tpPerGpu&chart=e2e
```

### Quick check links (local)

- [Embed scatter - Interactivity](http://localhost:3000/embed/scatter?model=dsr1&gpus=h200%2Cb200%2Cmi325x&isl=1024&osl=1024&y=tpPerGpu)
- [Embed scatter - End-to-end Latency](http://localhost:3000/embed/scatter?model=dsr1&gpus=h200%2Cb200%2Cmi325x&isl=1024&osl=1024&y=tpPerGpu&chart=e2e)
- [Embed scatter - Canonical params](http://localhost:3000/embed/scatter?g_model=DeepSeek-R1-0528&i_seq=1024/1024&i_prec=fp8&i_gpus=h200-sxm,b200-sxm,mi325x&i_metric=y_tpPerGpu&chart=interactivity)

### Alias compatibility

Alias keys are still accepted for backward compatibility (`model`, `isl`/`osl`, `precision`/`prec`, `gpus`, `y`), but if both canonical and alias forms are present, canonical values take precedence.

## Development Scripts

These are the main scripts you'll use during development. Admin scripts for database and cache management are listed separately below.
Expand Down
13 changes: 13 additions & 0 deletions packages/app/src/app/embed/scatter/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Metadata } from 'next';

import { EmbedScatterClientPage } from '@/components/embed/embed-scatter-client-page';

export const metadata: Metadata = {
title: 'InferenceX Embed Scatter',
description: 'Embeddable InferenceX scatter chart.',
robots: { index: false, follow: false },
};

export default function EmbedScatterPage() {
return <EmbedScatterClientPage />;
}
7 changes: 5 additions & 2 deletions packages/app/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import './globals.css';
import { Analytics } from '@vercel/analytics/next';
import { SpeedInsights } from '@vercel/speed-insights/next';
import type { Metadata } from 'next';
import { headers } from 'next/headers';
import { DM_Sans } from 'next/font/google';
import localFont from 'next/font/local';

Expand Down Expand Up @@ -168,6 +169,8 @@ export default async function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
const requestHeaders = await headers();
const isEmbedRoute = requestHeaders.get('x-inferencex-embed') === '1';
const starCount = await fetchStarCount();
return (
<html lang="en" className={monocraft.variable} suppressHydrationWarning>
Expand All @@ -191,9 +194,9 @@ export default async function RootLayout({
disableTransitionOnChange
>
<PostHogPageView />
<Header starCount={starCount} />
{!isEmbedRoute && <Header starCount={starCount} />}
<div className="grow flex flex-col">{children}</div>
<Footer starCount={starCount} />
{!isEmbedRoute && <Footer starCount={starCount} />}
</ThemeProvider>
</QueryProvider>
{process.env.VERCEL && <Analytics />}
Expand Down
16 changes: 16 additions & 0 deletions packages/app/src/components/embed/embed-scatter-client-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client';

import { useEffect, useState } from 'react';

import { EmbedScatterView } from '@/components/embed/embed-scatter-view';

export function EmbedScatterClientPage() {
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
}, []);

if (!mounted) return null;
return <EmbedScatterView />;
}
44 changes: 44 additions & 0 deletions packages/app/src/components/embed/embed-scatter-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client';

import Link from 'next/link';
import { useEffect, useMemo } from 'react';

import { GlobalFilterProvider } from '@/components/GlobalFilterContext';
import { InferenceProvider } from '@/components/inference/InferenceContext';
import InferenceChartDisplay from '@/components/inference/ui/ChartDisplay';
import { UnofficialRunProvider } from '@/components/unofficial-run-provider';
import { track } from '@/lib/analytics';

export function EmbedScatterView() {
useEffect(() => {
track('embed_view', {
embed_type: 'scatter',
referrer: typeof document !== 'undefined' && document.referrer ? document.referrer : 'direct',
});
}, []);

const canonicalHref = useMemo(() => {
if (typeof window === 'undefined') return '/inference';
const url = new URL(window.location.href);
url.pathname = '/inference';
return `${url.pathname}${url.search}`;
}, []);

return (
<main className="mx-auto w-full max-w-[1280px] p-2 sm:p-4">
<UnofficialRunProvider>
<GlobalFilterProvider>
<InferenceProvider activeTab="inference">
<InferenceChartDisplay />
</InferenceProvider>
</GlobalFilterProvider>
</UnofficialRunProvider>
<p className="mt-2 text-center text-xs text-muted-foreground">
Data by{' '}
<Link className="underline hover:text-foreground" href={canonicalHref} target="_blank">
SemiAnalysis InferenceX
</Link>
</p>
</main>
);
}
125 changes: 79 additions & 46 deletions packages/app/src/components/inference/ui/ChartDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { track } from '@/lib/analytics';
import dynamic from 'next/dynamic';
import { useMemo, useState } from 'react';
import { BarChart3, ChevronDown, Table2, X } from 'lucide-react';
import { usePathname, useSearchParams } from 'next/navigation';

import chartDefinitions from '@/components/inference/inference-chart-config.json';
import { useInference } from '@/components/inference/InferenceContext';
Expand Down Expand Up @@ -124,6 +125,11 @@ const VIEW_MODE_OPTIONS: SegmentedToggleOption<InferenceViewMode>[] = [
* the current filtered benchmark data.
*/
export default function ChartDisplay() {
const pathname = usePathname();
const searchParams = useSearchParams();
const isEmbedRoute = pathname.startsWith('/embed/');
const embedChartType = searchParams.get('chart') === 'e2e' ? 'e2e' : 'interactivity';

const {
graphs,
loading,
Expand Down Expand Up @@ -309,6 +315,10 @@ export default function ChartDisplay() {
}));
}, [graphs, overlayDataByChartType, selectedModel, selectedSequence]);

const graphsForRender = isEmbedRoute
? effectiveGraphs.filter((graph) => graph.chartDefinition.chartType === embedChartType)
: effectiveGraphs;

const displayGraphs = isFirstLoad
? Array.from({ length: 2 }).map((_, index) => (
<Card key={`skeleton-${index}`}>
Expand All @@ -317,58 +327,73 @@ export default function ChartDisplay() {
<Skeleton className="h-[600px] w-full" />
</Card>
))
: effectiveGraphs.length === 0
: graphsForRender.length === 0
? []
: effectiveGraphs.map((graph, graphIndex) => (
: graphsForRender.map((graph, graphIndex) => (
<section key={graphIndex} className="pt-8 md:pt-0">
<figure data-testid="chart-figure" className="relative rounded-lg">
<ChartButtons
chartId={`chart-${graphIndex}`}
analyticsPrefix={
selectedDateRange.startDate &&
selectedDateRange.endDate &&
selectedGPUs.length > 0
? 'gpu_timeseries'
: graph.chartDefinition.chartType === 'e2e'
? 'latency'
: 'interactivity'
}
leadingControls={
<SegmentedToggle
value={getViewMode(graphIndex)}
options={VIEW_MODE_OPTIONS}
onValueChange={(v) => handleViewModeChange(graphIndex, v)}
ariaLabel="View mode"
testId={`inference-view-toggle-${graphIndex}`}
/>
}
hideImageExport={getViewMode(graphIndex) === 'table'}
setIsLegendExpanded={setIsLegendExpanded}
exportFileName={`InferenceX_${selectedModel}_${graph.chartDefinition.chartType}`}
onExportCsv={() => {
const isTimeline =
{!isEmbedRoute && (
<ChartButtons
chartId={`chart-${graphIndex}`}
analyticsPrefix={
selectedDateRange.startDate &&
selectedDateRange.endDate &&
selectedGPUs.length > 0;
const visibleData = graph.data.filter((d) =>
isTimeline
? activeDates.has(`${d.date}_${d.hwKey}`)
: activeHwTypes.has(d.hwKey as string) &&
selectedPrecisions.includes(d.precision),
);
const { headers, rows } = inferenceChartToCsv(
visibleData,
graph.model,
graph.sequence,
);
exportToCsv(
`InferenceX_${selectedModel}_${graph.chartDefinition.chartType}`,
headers,
rows,
);
}}
/>
selectedGPUs.length > 0
? 'gpu_timeseries'
: graph.chartDefinition.chartType === 'e2e'
? 'latency'
: 'interactivity'
}
leadingControls={
<SegmentedToggle
value={getViewMode(graphIndex)}
options={VIEW_MODE_OPTIONS}
onValueChange={(v) => handleViewModeChange(graphIndex, v)}
ariaLabel="View mode"
testId={`inference-view-toggle-${graphIndex}`}
/>
}
hideImageExport={getViewMode(graphIndex) === 'table'}
setIsLegendExpanded={setIsLegendExpanded}
exportFileName={`InferenceX_${selectedModel}_${graph.chartDefinition.chartType}`}
onExportCsv={() => {
const isTimeline =
selectedDateRange.startDate &&
selectedDateRange.endDate &&
selectedGPUs.length > 0;
const visibleData = graph.data.filter((d) =>
isTimeline
? activeDates.has(`${d.date}_${d.hwKey}`)
: activeHwTypes.has(d.hwKey as string) &&
selectedPrecisions.includes(d.precision),
);
const { headers, rows } = inferenceChartToCsv(
visibleData,
graph.model,
graph.sequence,
);
exportToCsv(
`InferenceX_${selectedModel}_${graph.chartDefinition.chartType}`,
headers,
rows,
);
}}
/>
)}
<Card>
{isEmbedRoute && (
<div className="pointer-events-none absolute top-2 right-2 z-20">
<div className="pointer-events-auto">
<SegmentedToggle
value={getViewMode(graphIndex)}
options={VIEW_MODE_OPTIONS}
onValueChange={(v) => handleViewModeChange(graphIndex, v)}
ariaLabel="Embed view mode"
testId={`embed-inference-view-toggle-${graphIndex}`}
/>
</div>
</div>
)}
{(() => {
const chartCaption = (
<>
Expand Down Expand Up @@ -543,6 +568,14 @@ export default function ChartDisplay() {
</section>
));

if (isEmbedRoute) {
return (
<div data-testid="inference-chart-display-embed" className="flex flex-col gap-4">
{displayGraphs}
</div>
);
}

return (
<div data-testid="inference-chart-display" className="flex flex-col gap-4">
<section className="relative z-20">
Expand Down
Loading
Loading