Skip to content
Merged
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
120 changes: 78 additions & 42 deletions client/src/components/models/ModelComparison.jsx

Large diffs are not rendered by default.

40 changes: 35 additions & 5 deletions client/src/components/models/ModelComparison.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ vi.mock('recharts', () => ({
ResponsiveContainer: ({ children }) => <div>{children}</div>,
ScatterChart: ({ children }) => <div>{children}</div>,
Scatter: ({ name, data, line }) => (
<div data-testid={`scatter-${name}`} data-has-line={!!line} data-points={data?.length} />
<div data-testid={`scatter-${name}`} data-has-line={!!line} data-points={data?.length} data-values={JSON.stringify(data?.map(({ x, y }) => [x, y]))} />
),
CartesianGrid: () => null,
LabelList: () => null,
Expand Down Expand Up @@ -50,7 +50,7 @@ beforeEach(() => {
it('shows missing evidence, filters models and estimates token costs without inventing reasoning prices or local cost', async () => {
render(<MemoryRouter><ModelComparison /></MemoryRouter>);
await act(async () => {});
await screen.findByText('1 plotted · 1 missing quality or cost');
await screen.findByText('1 plotted · 1 missing selected metrics or outside log scale');
fireEvent.click(screen.getByText(/Evidence & sources/));
fireEvent.click(screen.getByText('Show or hide providers, models & effort'));
expect(screen.getAllByText(/E2E unknown/)).toHaveLength(2);
Expand Down Expand Up @@ -111,7 +111,7 @@ it('connects reasoning effort points with line and allows toggling line style an
expect(screen.getByTestId('scatter-gpt-5.6-sol')).toHaveAttribute('data-has-line', 'false');

// Test scale toggle
fireEvent.change(screen.getByLabelText('Cost scale'), { target: { value: 'log' } });
fireEvent.change(screen.getByLabelText('X-axis scale'), { target: { value: 'log' } });
expect(screen.getByTestId('xaxis')).toHaveAttribute('data-scale', 'log');

// Test quick filter reasoning curves
Expand Down Expand Up @@ -204,7 +204,7 @@ it('stretches chart width and adjusts height', async () => {
render(<MemoryRouter><ModelComparison /></MemoryRouter>);
await act(async () => {});

await screen.findByText('1 plotted · 1 missing quality or cost');
await screen.findByText('1 plotted · 1 missing selected metrics or outside log scale');

// Initially at 1x
expect(screen.getByRole('button', { name: '1×' })).toHaveAttribute('aria-pressed', 'true');
Expand Down Expand Up @@ -232,7 +232,7 @@ it('scales axes via zoom buttons and manual bounds inputs', async () => {
render(<MemoryRouter><ModelComparison /></MemoryRouter>);
await act(async () => {});

await screen.findByText('3 plotted · 0 missing quality or cost');
await screen.findByText('3 plotted · 0 missing selected metrics or outside log scale');
const xaxis = screen.getByTestId('xaxis');
const yaxis = screen.getByTestId('yaxis');

Expand Down Expand Up @@ -320,3 +320,33 @@ it('persists settings to localStorage and restores them on a fresh visit', async
expect(screen.getByRole('button', { name: '1.5×' })).toHaveAttribute('aria-pressed', 'true');
});


it('switches metric coordinates, clears stale zoom, and preserves evidence table units', async () => {
api.getModelComparison.mockResolvedValue({ observations: [
{ ...observation, tokensPerSecond: metric(80), responseSeconds: metric(12) },
{ ...observation, id: 'missing-speed', model: 'other', tokensPerSecond: null },
], inventory: [] });
render(<MemoryRouter initialEntries={['/?xMin=0.1&yMin=40']}><ModelComparison /></MemoryRouter>);
await screen.findByLabelText('X axis');
fireEvent.change(screen.getByLabelText('X axis'), { target: { value: 'tokensPerSecond' } });
fireEvent.change(screen.getByLabelText('Y axis'), { target: { value: 'responseSeconds' } });
expect(screen.getByTestId('scatter-example-model')).toHaveAttribute('data-values', '[[80,12]]');
expect(screen.queryByTestId('scatter-other')).toBeNull();
expect(screen.getByTestId('xaxis')).toHaveAttribute('data-scale', 'linear');
expect(screen.getByTestId('xaxis')).toHaveAttribute('data-allow-overflow', 'false');
expect(screen.getAllByText('$0.5000')).toHaveLength(2);
expect(screen.getAllByText('50')).toHaveLength(2);
});

it('keeps exact Zen IDs in available coverage and plots free prices without inventing quality', async () => {
const free = { ...observation, id: 'free', model: 'example-free', benchmark: 'Unbenchmarked (pricing only)', quality: null, costPerTask: null, inputPerMillion: metric(0), outputPerMillion: metric(0) };
api.getModelComparison.mockResolvedValue({ observations: [observation, free], availableModels: ['example'], inventory: [{ id: 'zen', models: [{ model: `opencode/${free.model}`, efforts: [] }] }] });
render(<MemoryRouter initialEntries={['/?xAxis=inputPerMillion&yAxis=outputPerMillion&scale=linear']}><ModelComparison /></MemoryRouter>);
expect(await screen.findByTestId('scatter-example-free')).toHaveAttribute('data-values', '[[0,0]]');
fireEvent.click(screen.getByRole('button', { name: 'Fit visible' }));
const domain = JSON.parse(screen.getByTestId('xaxis').getAttribute('data-domain'));
expect(domain[1]).toBeGreaterThan(domain[0]);
fireEvent.change(screen.getByLabelText('Y axis'), { target: { value: 'quality' } });
expect(screen.queryByTestId('scatter-example-free')).toBeNull();
expect(screen.getAllByText('Unknown').length).toBeGreaterThan(0);
});
256 changes: 256 additions & 0 deletions data.reference/model-comparison.json
Original file line number Diff line number Diff line change
Expand Up @@ -5918,6 +5918,262 @@
},
"quota": null,
"notes": "Sourced from Artificial Analysis (Grok 3 mini Reasoning (high))."
},
{
"id": "zen-free-2026-09-big-pickle",
"provider": "OpenCode Zen",
"model": "big-pickle",
"effort": "unspecified",
"configuration": "OpenCode Zen free endpoint; provider default configuration",
"billing": "api",
"benchmark": "Unbenchmarked (pricing only)",
"quality": null,
"costPerTask": null,
"inputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry big-pickle: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"outputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry big-pickle: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"reasoningPerMillion": null,
"responseSeconds": null,
"tokensPerSecond": null,
"quota": null,
"notes": "Big Pickle. Free endpoint listed on 2026-09-06. Pricing is temporary; usage limits and data-use terms apply (https://opencode.ai/docs/zen/). No verified quality or speed measurement for this exact endpoint/effort; do not transfer a similarly named model's scores."
},
{
"id": "zen-free-2026-09-deepseek-v4-flash-free",
"provider": "OpenCode Zen",
"model": "deepseek-v4-flash-free",
"effort": "unspecified",
"configuration": "OpenCode Zen free endpoint; provider default configuration",
"billing": "api",
"benchmark": "Unbenchmarked (pricing only)",
"quality": null,
"costPerTask": null,
"inputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry deepseek-v4-flash-free: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"outputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry deepseek-v4-flash-free: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"reasoningPerMillion": null,
"responseSeconds": null,
"tokensPerSecond": null,
"quota": null,
"notes": "DeepSeek V4 Flash Free. Free endpoint listed on 2026-09-06. Pricing is temporary; usage limits and data-use terms apply (https://opencode.ai/docs/zen/). No verified quality or speed measurement for this exact endpoint/effort; do not transfer a similarly named model's scores."
},
{
"id": "zen-free-2026-09-muse-spark-1.3-contributor-free",
"provider": "OpenCode Zen",
"model": "muse-spark-1.3-contributor-free",
"effort": "unspecified",
"configuration": "OpenCode Zen free endpoint; provider default configuration",
"billing": "api",
"benchmark": "Unbenchmarked (pricing only)",
"quality": null,
"costPerTask": null,
"inputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry muse-spark-1.3-contributor-free: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"outputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry muse-spark-1.3-contributor-free: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"reasoningPerMillion": null,
"responseSeconds": null,
"tokensPerSecond": null,
"quota": null,
"notes": "Muse Spark 1.3 Free. Free endpoint listed on 2026-09-06. Pricing is temporary; usage limits and data-use terms apply (https://opencode.ai/docs/zen/). No verified quality or speed measurement for this exact endpoint/effort; do not transfer a similarly named model's scores."
},
{
"id": "zen-free-2026-09-muse-spark-1.2-contributor-free",
"provider": "OpenCode Zen",
"model": "muse-spark-1.2-contributor-free",
"effort": "unspecified",
"configuration": "OpenCode Zen free endpoint; provider default configuration",
"billing": "api",
"benchmark": "Unbenchmarked (pricing only)",
"quality": null,
"costPerTask": null,
"inputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry muse-spark-1.2-contributor-free: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"outputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry muse-spark-1.2-contributor-free: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"reasoningPerMillion": null,
"responseSeconds": null,
"tokensPerSecond": null,
"quota": null,
"notes": "Muse Spark 1.2 Free. Free endpoint listed on 2026-09-06. Pricing is temporary; usage limits and data-use terms apply (https://opencode.ai/docs/zen/). No verified quality or speed measurement for this exact endpoint/effort; do not transfer a similarly named model's scores."
},
{
"id": "zen-free-2026-09-mimo-v2.5-free",
"provider": "OpenCode Zen",
"model": "mimo-v2.5-free",
"effort": "unspecified",
"configuration": "OpenCode Zen free endpoint; provider default configuration",
"billing": "api",
"benchmark": "Unbenchmarked (pricing only)",
"quality": null,
"costPerTask": null,
"inputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry mimo-v2.5-free: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"outputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry mimo-v2.5-free: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"reasoningPerMillion": null,
"responseSeconds": null,
"tokensPerSecond": null,
"quota": null,
"notes": "MiMo V2.5 Free. Free endpoint listed on 2026-09-06. Pricing is temporary; usage limits and data-use terms apply (https://opencode.ai/docs/zen/). No verified quality or speed measurement for this exact endpoint/effort; do not transfer a similarly named model's scores."
},
{
"id": "zen-free-2026-09-ling-3.0-flash-fin-free",
"provider": "OpenCode Zen",
"model": "ling-3.0-flash-fin-free",
"effort": "unspecified",
"configuration": "OpenCode Zen free endpoint; provider default configuration",
"billing": "api",
"benchmark": "Unbenchmarked (pricing only)",
"quality": null,
"costPerTask": null,
"inputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry ling-3.0-flash-fin-free: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"outputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry ling-3.0-flash-fin-free: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"reasoningPerMillion": null,
"responseSeconds": null,
"tokensPerSecond": null,
"quota": null,
"notes": "Ling 3.0 Flash Fin Free. Free endpoint listed on 2026-09-06. Pricing is temporary; usage limits and data-use terms apply (https://opencode.ai/docs/zen/). No verified quality or speed measurement for this exact endpoint/effort; do not transfer a similarly named model's scores."
},
{
"id": "zen-free-2026-09-nemotron-3-ultra-free",
"provider": "OpenCode Zen",
"model": "nemotron-3-ultra-free",
"effort": "unspecified",
"configuration": "OpenCode Zen free endpoint; provider default configuration",
"billing": "api",
"benchmark": "Unbenchmarked (pricing only)",
"quality": null,
"costPerTask": null,
"inputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry nemotron-3-ultra-free: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"outputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry nemotron-3-ultra-free: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"reasoningPerMillion": null,
"responseSeconds": null,
"tokensPerSecond": null,
"quota": null,
"notes": "Nemotron 3 Ultra Free. Free endpoint listed on 2026-09-06. Pricing is temporary; usage limits and data-use terms apply (https://opencode.ai/docs/zen/). No verified quality or speed measurement for this exact endpoint/effort; do not transfer a similarly named model's scores."
},
{
"id": "zen-free-2026-09-nemotron-3.5-lightning-free",
"provider": "OpenCode Zen",
"model": "nemotron-3.5-lightning-free",
"effort": "unspecified",
"configuration": "OpenCode Zen free endpoint; provider default configuration",
"billing": "api",
"benchmark": "Unbenchmarked (pricing only)",
"quality": null,
"costPerTask": null,
"inputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry nemotron-3.5-lightning-free: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"outputPerMillion": {
"value": 0,
"source": {
"url": "https://models.dev/api.json",
"retrievedAt": "2026-09-06T00:00:00Z",
"methodology": "OpenCode provider catalog entry nemotron-3.5-lightning-free: published USD per million input/output tokens. Availability cross-checked at https://opencode.ai/zen/v1/models. Promotional endpoint pricing, not a measured benchmark task or an unlimited quota guarantee."
}
},
"reasoningPerMillion": null,
"responseSeconds": null,
"tokensPerSecond": null,
"quota": null,
"notes": "Nemotron 3.5 Lightning Free. Free endpoint listed on 2026-09-06. Pricing is temporary; usage limits and data-use terms apply (https://opencode.ai/docs/zen/). No verified quality or speed measurement for this exact endpoint/effort; do not transfer a similarly named model's scores."
}
]
}
Loading