Compare commits

...

1 Commits

Author SHA1 Message Date
Adam 003e179e3a feat(stats): add model comparison pages 2026-06-24 10:43:56 -05:00
10 changed files with 1385 additions and 18 deletions
@@ -23,10 +23,17 @@ import {
findModelCatalogEntry,
formatCatalogLabName,
getModelCatalog,
type ModelCatalog,
type ModelCatalogCost,
type ModelCatalogEntry,
} from "../model-catalog"
import { runStatsEffect } from "../../stats-runtime"
import {
ComparisonCardsSection,
modelRefFromCatalog,
uniqueComparisonPairs,
type ComparisonModelRef,
} from "../compare-cards"
import {
applyThemePreference,
Footer,
@@ -180,6 +187,11 @@ export default function StatsModel() {
<ModelEfficiencySection data={stats() ?? null} catalog={catalogEntry() ?? null} />
<ModelGeoBreakdownSection data={stats()?.country ?? emptyCountryRecord()} />
<ModelPeersSection data={stats() ?? null} />
<ComparisonCardsSection
pairs={modelComparisonPairs(catalog(), catalogEntry() ?? null, stats() ?? null)}
title="Compare This Model"
description="Other models to compare with this one."
/>
</>
</Show>
</Show>
@@ -188,6 +200,7 @@ export default function StatsModel() {
themePreference={themePreference()}
onThemePreferenceChange={updateThemePreference}
links={modelFooterLinks}
bridge={{ href: "#model-comparison", label: "MODEL COMPARISONS" }}
/>
</div>
</main>
@@ -204,7 +217,7 @@ function ModelLoading() {
Data
</a>
<h1>Model Data</h1>
<p>Reading model aggregates from model_stat.</p>
<p>Loading model data.</p>
</div>
</div>
</section>
@@ -225,7 +238,7 @@ function ModelNotFound(props: { lab: string; model: string }) {
Data
</a>
<h1>{props.model || "Model"}</h1>
<p>No model facts or model_stat rows matched {props.lab ? `${props.lab}/${props.model}` : props.model}.</p>
<p>No model data found for {props.lab ? `${props.lab}/${props.model}` : props.model}.</p>
</div>
</div>
</section>
@@ -575,9 +588,7 @@ function ModelGeoBreakdownSection(props: { data: Record<UsageRange, CountryEntry
<SectionTitle title="Geo Breakdown" description="OpenCode Go model tokens used by country." />
<Show
when={data().length > 0}
fallback={
<ModelEmptyState title="No geo data" description="No OpenCode Go geo_stat rows matched this model." />
}
fallback={<ModelEmptyState title="No geo data" description="No country data for this model." />}
>
<div data-component="geo-breakdown">
<div data-slot="geo-map-panel">
@@ -796,6 +807,53 @@ function ModelEmptyState(props: { title: string; description: string; compact?:
)
}
function modelComparisonPairs(
catalog: ModelCatalog | undefined,
catalogEntry: ModelCatalogEntry | null,
data: StatsModelData | null,
) {
const current = modelComparisonRef(catalogEntry, data)
if (!current) return []
const peerPairs = (data?.peers ?? [])
.filter((peer) => peer.model !== data?.model)
.slice(0, 3)
.map((peer) => ({
first: current,
second: {
name: peer.model,
lab: peer.provider,
slug: peer.slug,
labName: peer.author,
metric: `#${peer.rank} / ${formatTokens(peer.tokens)}`,
},
detail: "Usage peer",
}))
const catalogPairs = (catalogEntry && catalog ? catalog.labs.find((lab) => lab.id === catalogEntry.lab)?.models ?? [] : [])
.filter((model) => model.id !== catalogEntry?.id)
.slice(0, 3)
.map((model) => ({
first: current,
second: modelRefFromCatalog(model),
detail: "Same lab pair",
}))
return uniqueComparisonPairs([...peerPairs, ...catalogPairs])
}
function modelComparisonRef(
catalogEntry: ModelCatalogEntry | null,
data: StatsModelData | null,
): ComparisonModelRef | undefined {
if (catalogEntry) return modelRefFromCatalog(catalogEntry)
if (!data) return undefined
return {
name: data.model,
lab: data.provider,
slug: data.slug,
labName: data.author,
metric: `#${data.rank}`,
}
}
function getProviderIconId(author: string) {
if (author === "MiniMax") return "minimax"
if (author === "Moonshot") return "moonshotai"
@@ -17,6 +17,11 @@ import {
type ModelCatalogLab,
} from "../model-catalog"
import { runStatsEffect } from "../../stats-runtime"
import {
ComparisonCardsSection,
modelRefFromCatalog,
uniqueComparisonPairs,
} from "../compare-cards"
import {
applyThemePreference,
Footer,
@@ -120,6 +125,11 @@ export default function StatsLab() {
<LabHero lab={data()} stats={stats() ?? null} />
<LabUsageSection lab={data()} data={stats() ?? null} />
<LabModelsSection lab={data()} usage={stats()?.models ?? []} />
<ComparisonCardsSection
pairs={labComparisonPairs(data(), stats()?.models ?? [])}
title={`${data().name} Model Comparisons`}
description="Model pairs from this lab."
/>
</>
)}
</Show>
@@ -129,6 +139,7 @@ export default function StatsLab() {
themePreference={themePreference()}
onThemePreferenceChange={updateThemePreference}
links={labFooterLinks}
bridge={{ href: "#model-comparison", label: "MODEL COMPARISONS" }}
/>
</div>
</main>
@@ -374,6 +385,31 @@ function LabEmptyState(props: { title: string; description: string }) {
)
}
function labComparisonPairs(lab: ModelCatalogLab, usage: LabUsageModelEntry[]) {
const usageRefs = usage.slice(0, 4).map((model) => ({
name: model.model,
lab: model.provider,
slug: model.slug,
labName: model.author,
metric: formatTokens(model.tokens),
}))
const refs = usageRefs.length > 1 ? usageRefs : lab.models.slice(0, 4).map(modelRefFromCatalog)
return uniqueComparisonPairs(
(
[
[0, 1, "Most-used lab pair"],
[0, 2, "Lab alternative"],
[1, 2, "Adjacent lab pair"],
[2, 3, "Same lab pair"],
] as const
).flatMap(([firstIndex, secondIndex, detail]) => {
const first = refs[firstIndex]
const second = refs[secondIndex]
return first && second ? [{ first, second, detail }] : []
}),
)
}
function formatCatalogLimit(value: number | undefined) {
return value === undefined ? "Unknown" : formatTokens(value)
}
@@ -0,0 +1,88 @@
import { For, Show } from "solid-js"
import { catalogSlug, formatCatalogLabName, type ModelCatalogEntry } from "./model-catalog"
export type ComparisonModelRef = {
name: string
lab: string
slug: string
labName?: string
metric?: string
}
export type ComparisonPair = {
first: ComparisonModelRef
second: ComparisonModelRef
detail: string
}
export function modelRefFromCatalog(entry: ModelCatalogEntry): ComparisonModelRef {
return {
name: entry.name,
lab: entry.lab,
slug: entry.slug,
labName: formatCatalogLabName(entry.lab),
}
}
export function comparisonHref(first: ComparisonModelRef, second: ComparisonModelRef) {
return `${import.meta.env.BASE_URL}compare/${catalogSlug(first.lab)}/${catalogSlug(first.slug)}/${catalogSlug(
second.lab,
)}/${catalogSlug(second.slug)}`
}
export function uniqueComparisonPairs(pairs: ComparisonPair[]) {
return pairs.reduce<{ keys: Set<string>; pairs: ComparisonPair[] }>(
(result, pair) => {
const key = [modelKey(pair.first), modelKey(pair.second)].toSorted().join("|")
if (result.keys.has(key) || modelKey(pair.first) === modelKey(pair.second)) return result
result.keys.add(key)
result.pairs.push(pair)
return result
},
{ keys: new Set(), pairs: [] },
).pairs
}
export function ComparisonCardsSection(props: {
pairs: ComparisonPair[]
title?: string
description?: string
compact?: boolean
}) {
return (
<Show when={props.pairs.length > 0}>
<section id="model-comparison" data-section="model-panel" data-variant={props.compact ? "compact" : undefined}>
<p data-slot="section-title">
<strong>{props.title ?? "Model Comparisons"}.</strong>{" "}
<span>{props.description ?? "Compare usage, cost, limits, and features."}</span>
</p>
<div data-component="comparison-card-grid">
<For each={props.pairs}>
{(pair) => (
<a data-component="comparison-card" href={comparisonHref(pair.first, pair.second)}>
<span>{pair.detail}</span>
<strong>
{pair.first.name} <em>vs</em> {pair.second.name}
</strong>
<p>
<b>{pair.first.labName ?? formatCatalogLabName(pair.first.lab)}</b>
<i />
<b>{pair.second.labName ?? formatCatalogLabName(pair.second.lab)}</b>
</p>
<Show when={pair.first.metric || pair.second.metric}>
<small>
{pair.first.metric ?? "Listed"} / {pair.second.metric ?? "Listed"}
</small>
</Show>
</a>
)}
</For>
</div>
</section>
</Show>
)
}
function modelKey(model: ComparisonModelRef) {
return `${catalogSlug(model.lab)}/${catalogSlug(model.slug)}`
}
@@ -0,0 +1,83 @@
import { createEffect, createMemo, createSignal, For } from "solid-js"
import { comparisonHref, modelRefFromCatalog } from "./compare-cards"
import type { ModelCatalogEntry } from "./model-catalog"
export function ComparisonSelector(props: {
models: ModelCatalogEntry[]
firstId?: string
secondId?: string
label?: string
}) {
const [firstId, setFirstId] = createSignal(props.firstId ?? props.models[0]?.id ?? "")
const [secondId, setSecondId] = createSignal(differentModelId(props.models, firstId(), props.secondId))
const modelById = createMemo(() => new Map(props.models.map((model) => [model.id, model])))
const href = createMemo(() => {
const first = modelById().get(firstId())
const second = modelById().get(secondId())
if (!first || !second || first.id === second.id) return undefined
return comparisonHref(modelRefFromCatalog(first), modelRefFromCatalog(second))
})
createEffect(() => {
if (firstId() && modelById().has(firstId())) return
const next = props.firstId && modelById().has(props.firstId) ? props.firstId : props.models[0]?.id
if (next) setFirstId(next)
})
createEffect(() => {
if (secondId() && secondId() !== firstId() && modelById().has(secondId())) return
setSecondId(differentModelId(props.models, firstId(), props.secondId))
})
return (
<form
data-component="comparison-selector"
aria-label={props.label ?? "Model comparison selector"}
onSubmit={(event) => {
event.preventDefault()
const url = href()
if (!url || typeof window === "undefined") return
window.location.href = url
}}
>
<label>
<span>First model</span>
<select value={firstId()} onInput={(event) => setFirstId(event.currentTarget.value)} required>
<option value="" disabled>
Select model
</option>
<For each={props.models}>
{(model) => (
<option value={model.id}>
{model.name} ({model.lab})
</option>
)}
</For>
</select>
</label>
<label>
<span>Second model</span>
<select value={secondId()} onInput={(event) => setSecondId(event.currentTarget.value)} required>
<option value="" disabled>
Select model
</option>
<For each={props.models}>
{(model) => (
<option value={model.id} disabled={model.id === firstId()}>
{model.name} ({model.lab})
</option>
)}
</For>
</select>
</label>
<button type="submit" disabled={!href()}>
Compare models
</button>
</form>
)
}
function differentModelId(models: ModelCatalogEntry[], firstId: string, preferredId: string | undefined) {
if (preferredId && preferredId !== firstId && models.some((model) => model.id === preferredId)) return preferredId
return models.find((model) => model.id !== firstId)?.id ?? ""
}
@@ -0,0 +1,608 @@
import "../../../../index.css"
import { Link, Meta, Title } from "@solidjs/meta"
import {
getStatsModelComparisonData,
type StatsModelComparisonEntry,
} from "@opencode-ai/stats-core/domain/home"
import { runtime } from "@opencode-ai/stats-core/runtime"
import { createAsync, query, useParams } from "@solidjs/router"
import { createMemo, createSignal, For, onMount, Show } from "solid-js"
import { getRequestEvent } from "solid-js/web"
import {
ComparisonCardsSection,
modelRefFromCatalog,
uniqueComparisonPairs,
type ComparisonModelRef,
type ComparisonPair,
} from "../../../../compare-cards"
import { ComparisonSelector } from "../../../../compare-selector"
import {
catalogSlug,
findModelCatalogEntry,
formatCatalogLabName,
getModelCatalog,
type ModelCatalog,
type ModelCatalogEntry,
} from "../../../../model-catalog"
import {
applyThemePreference,
Footer,
getGitHubStars,
Header,
isThemePreference,
themeStorageKey,
type HeaderLink,
type ThemePreference,
} from "../../../../stats-shell"
const compareFallbackUrl = "https://stats.opencode.ai"
const compareHeaderLinks: readonly HeaderLink[] = [
{ href: "#overview", label: "Overview" },
{ href: "#comparison", label: "Comparison" },
{ href: "#compare-tool", label: "Compare" },
{ href: "#model-comparison", label: "Related" },
]
const compareFooterLinks: readonly HeaderLink[] = [
{ href: import.meta.env.BASE_URL, label: "Data Home" },
{ href: `${import.meta.env.BASE_URL}compare`, label: "Model Compare" },
{ href: `${import.meta.env.BASE_URL}#top-models`, label: "Top Models" },
{ href: `${import.meta.env.BASE_URL}#token-cost`, label: "Token Cost" },
]
type ComparisonModel = {
name: string
lab: string
labName: string
slug: string
catalog: ModelCatalogEntry | null
stats: StatsModelComparisonEntry | null
}
type ComparisonDirection = "higher" | "lower"
type ComparisonCell = { value: string; detail?: string; score?: number }
type ComparisonRow = {
label: string
description: string
direction: ComparisonDirection
cells: [ComparisonCell, ComparisonCell]
}
const getComparisonData = query(
async (firstLab: string, firstModel: string, secondLab: string, secondModel: string) => {
"use server"
return runtime.runPromise(getStatsModelComparisonData(firstLab, firstModel, secondLab, secondModel))
},
"getStatsModelComparisonData",
)
export default function ModelComparePair() {
const event = getRequestEvent()
event?.response.headers.set("Cache-Control", "public, max-age=60, s-maxage=300, stale-while-revalidate=86400")
const params = useParams()
const firstLabParam = createMemo(() => params.firstLab ?? "")
const firstModelParam = createMemo(() => params.firstModel ?? "")
const secondLabParam = createMemo(() => params.secondLab ?? "")
const secondModelParam = createMemo(() => params.secondModel ?? "")
const catalog = createAsync(() => getModelCatalog())
const firstCatalog = createMemo(() => resolvedCatalogEntry(catalog(), firstLabParam(), firstModelParam()))
const secondCatalog = createMemo(() => resolvedCatalogEntry(catalog(), secondLabParam(), secondModelParam()))
const stats = createAsync(() => {
if (catalog() === undefined || firstCatalog() === undefined || secondCatalog() === undefined)
return Promise.resolve(undefined)
return getComparisonData(
firstCatalog()?.lab ?? firstLabParam(),
firstCatalog()?.slug ?? firstModelParam(),
secondCatalog()?.lab ?? secondLabParam(),
secondCatalog()?.slug ?? secondModelParam(),
)
})
const githubStars = createAsync(() => getGitHubStars())
const [themePreference, setThemePreference] = createSignal<ThemePreference>("system")
const models = createMemo(
() =>
[
buildComparisonModel(firstLabParam(), firstModelParam(), firstCatalog() ?? null, stats()?.models[0] ?? null),
buildComparisonModel(secondLabParam(), secondModelParam(), secondCatalog() ?? null, stats()?.models[1] ?? null),
] as const,
)
const title = createMemo(() => `${models()[0].name} vs ${models()[1].name} - Model Comparison`)
const description = createMemo(
() =>
`Compare ${models()[0].name} and ${models()[1].name} by usage, rank, context window, output limit, cache ratio, and cost across OpenCode data.`,
)
const canonicalPath = createMemo(
() =>
`${import.meta.env.BASE_URL}compare/${catalogSlug(models()[0].lab)}/${catalogSlug(models()[0].slug)}/${catalogSlug(
models()[1].lab,
)}/${catalogSlug(models()[1].slug)}`,
)
const canonicalUrl = createMemo(() =>
new URL(
canonicalPath(),
event?.request.url ?? (typeof window === "undefined" ? compareFallbackUrl : window.location.href),
).toString(),
)
const rows = createMemo(() => buildComparisonRows(models()[0], models()[1]))
const relatedPairs = createMemo(() => buildRelatedPairs(catalog(), models()[0], models()[1]))
const selectorModels = createMemo(() =>
uniqueCatalogModels([comparisonCatalogEntry(models()[0]), comparisonCatalogEntry(models()[1]), ...(catalog()?.models ?? [])]),
)
const structuredData = createMemo(() =>
JSON.stringify({
"@context": "https://schema.org",
"@type": "WebPage",
name: title(),
description: description(),
url: canonicalUrl(),
about: models().map((model) => ({
"@type": "SoftwareApplication",
name: model.name,
applicationCategory: "AI model",
provider: model.labName,
})),
}),
)
const updateThemePreference = (preference: ThemePreference) => {
applyThemePreference(preference)
setThemePreference(preference)
if (typeof window === "undefined") return
window.localStorage.setItem(themeStorageKey, preference)
}
onMount(() => {
if (typeof window === "undefined") return
const preference = window.localStorage.getItem(themeStorageKey)
const nextPreference = isThemePreference(preference) ? preference : "system"
applyThemePreference(nextPreference)
setThemePreference(nextPreference)
})
return (
<main data-page="stats" data-theme={themePreference()}>
<Title>{title()}</Title>
<Meta name="description" content={description()} />
<Link rel="canonical" href={canonicalUrl()} />
<Meta property="og:type" content="website" />
<Meta property="og:site_name" content="OpenCode" />
<Meta property="og:title" content={title()} />
<Meta property="og:description" content={description()} />
<Meta property="og:url" content={canonicalUrl()} />
<Meta name="twitter:card" content="summary" />
<Meta name="twitter:title" content={title()} />
<Meta name="twitter:description" content={description()} />
<script type="application/ld+json">{structuredData()}</script>
<Header githubStars={githubStars() ?? "150K"} links={compareHeaderLinks} brandHref={import.meta.env.BASE_URL} />
<div data-component="container">
<div data-component="content">
<ComparisonHero models={models()} />
<section id="comparison" data-section="model-panel">
<p data-slot="section-title">
<strong>Comparison Table.</strong>{" "}
<span>Compare usage, cost, limits, and features.</span>
</p>
<Show
when={stats() !== undefined}
fallback={
<div data-component="empty-state" data-compact="true">
<strong>Loading comparison</strong>
<p>Loading stats for both models.</p>
</div>
}
>
<ComparisonTable models={models()} rows={rows()} />
</Show>
</section>
<section id="compare-tool" data-section="model-panel" data-variant="compact">
<p data-slot="section-title">
<strong>Compare Another Pair.</strong> <span>Choose two models to compare.</span>
</p>
<Show
when={selectorModels().length > 1}
fallback={
<div data-component="empty-state" data-compact="true">
<strong>No models found</strong>
<p>The model list could not be loaded.</p>
</div>
}
>
<ComparisonSelector
models={selectorModels()}
firstId={comparisonCatalogEntry(models()[0]).id}
secondId={comparisonCatalogEntry(models()[1]).id}
/>
</Show>
</section>
<ComparisonCardsSection
pairs={relatedPairs()}
title="Related Model Comparisons"
description="Other model pairs to check."
/>
</div>
<Footer
themePreference={themePreference()}
onThemePreferenceChange={updateThemePreference}
links={compareFooterLinks}
bridge={{ href: "#comparison", label: "COMPARE TABLE" }}
/>
</div>
</main>
)
}
function ComparisonHero(props: { models: readonly [ComparisonModel, ComparisonModel] }) {
return (
<section id="overview" data-section="model-hero">
<a data-slot="model-back-link" href={`${import.meta.env.BASE_URL}compare`}>
Compare
</a>
<div data-slot="model-hero-copy">
<h1>
{props.models[0].name} vs {props.models[1].name}
</h1>
<p>Compare usage, cost, limits, and features for these two models.</p>
</div>
<div data-slot="model-hero-pattern" aria-hidden="true" />
</section>
)
}
function ComparisonTable(props: { models: readonly [ComparisonModel, ComparisonModel]; rows: ComparisonRow[] }) {
return (
<div data-component="comparison-table-wrap">
<table data-component="comparison-table">
<caption>
{props.models[0].name} compared with {props.models[1].name}
</caption>
<thead>
<tr>
<th scope="col">Metric</th>
<For each={props.models}>{(model) => <th scope="col">{model.name}</th>}</For>
</tr>
</thead>
<tbody>
<For each={props.rows}>
{(row) => {
const best = () => bestCellIndex(row)
return (
<tr>
<th scope="row">
<strong>{row.label}</strong>
<span>{row.description}</span>
</th>
<For each={row.cells}>
{(cell, index) => (
<td data-best={best() === index() ? "true" : undefined}>
<strong>{cell.value}</strong>
<Show when={cell.detail}>{(detail) => <span>{detail()}</span>}</Show>
</td>
)}
</For>
</tr>
)
}}
</For>
</tbody>
</table>
</div>
)
}
function resolvedCatalogEntry(catalog: ModelCatalog | undefined, lab: string, model: string) {
if (!catalog) return undefined
return findModelCatalogEntry(catalog, model, lab) ?? null
}
function buildComparisonModel(
labParam: string,
modelParam: string,
catalog: ModelCatalogEntry | null,
stats: StatsModelComparisonEntry | null,
): ComparisonModel {
return {
name: catalog?.name ?? stats?.model ?? formatParamName(modelParam),
lab: catalog?.lab ?? stats?.provider ?? catalogSlug(labParam),
labName: formatCatalogLabName(catalog?.lab ?? stats?.provider ?? labParam),
slug: catalog?.slug ?? stats?.slug ?? catalogSlug(modelParam),
catalog,
stats,
}
}
function comparisonCatalogEntry(model: ComparisonModel): ModelCatalogEntry {
if (model.catalog) return model.catalog
return {
id: `${catalogSlug(model.lab)}/${catalogSlug(model.slug)}`,
lab: catalogSlug(model.lab),
slug: catalogSlug(model.slug),
name: model.name,
modalities: { input: [], output: [] },
openWeights: false,
reasoning: false,
toolCall: false,
attachment: false,
temperature: false,
weights: [],
benchmarks: [],
}
}
function uniqueCatalogModels(models: ModelCatalogEntry[]) {
return Object.values(
models.reduce<Record<string, ModelCatalogEntry>>((result, model) => {
result[model.id] = result[model.id] ?? model
return result
}, {}),
)
}
function buildComparisonRows(first: ComparisonModel, second: ComparisonModel): ComparisonRow[] {
return [
comparisonRow(
"Recent Rank",
"Lower is better.",
{
value: first.stats?.rank == null ? "No usage" : `#${first.stats.rank}`,
score: first.stats?.rank ?? undefined,
},
{
value: second.stats?.rank == null ? "No usage" : `#${second.stats.rank}`,
score: second.stats?.rank ?? undefined,
},
"lower",
),
comparisonRow(
"Token Share",
"Share of recent OpenCode usage.",
{ value: first.stats ? formatPercent(first.stats.tokenShare) : "No usage", score: first.stats?.tokenShare },
{ value: second.stats ? formatPercent(second.stats.tokenShare) : "No usage", score: second.stats?.tokenShare },
"higher",
),
comparisonRow(
"Tokens",
"Recent token volume.",
{ value: first.stats ? formatTokens(first.stats.totals.tokens) : "No usage", score: first.stats?.totals.tokens },
{
value: second.stats ? formatTokens(second.stats.totals.tokens) : "No usage",
score: second.stats?.totals.tokens,
},
"higher",
),
comparisonRow(
"Sessions",
"Recent session count.",
{
value: first.stats ? formatInteger(first.stats.totals.sessions) : "No usage",
score: first.stats?.totals.sessions,
},
{
value: second.stats ? formatInteger(second.stats.totals.sessions) : "No usage",
score: second.stats?.totals.sessions,
},
"higher",
),
comparisonRow(
"Cost / 1M Tokens",
"Lower is better.",
{
value: first.stats ? formatMoney(first.stats.totals.costPerMillion) : "No usage",
score: positiveScore(first.stats?.totals.costPerMillion),
},
{
value: second.stats ? formatMoney(second.stats.totals.costPerMillion) : "No usage",
score: positiveScore(second.stats?.totals.costPerMillion),
},
"lower",
),
comparisonRow(
"Cost / Session",
"Lower is better.",
{
value: first.stats ? formatSessionCost(first.stats.totals.costPerSession) : "No usage",
score: positiveScore(first.stats?.totals.costPerSession),
},
{
value: second.stats ? formatSessionCost(second.stats.totals.costPerSession) : "No usage",
score: positiveScore(second.stats?.totals.costPerSession),
},
"lower",
),
comparisonRow(
"Cache Ratio",
"Higher is better.",
{ value: first.stats ? formatPercent(first.stats.totals.cacheRatio) : "No usage", score: first.stats?.totals.cacheRatio },
{
value: second.stats ? formatPercent(second.stats.totals.cacheRatio) : "No usage",
score: second.stats?.totals.cacheRatio,
},
"higher",
),
comparisonRow(
"Context Window",
"Higher limit is better.",
{
value: formatCatalogLimit(first.catalog?.limit?.context),
score: first.catalog?.limit?.context,
},
{
value: formatCatalogLimit(second.catalog?.limit?.context),
score: second.catalog?.limit?.context,
},
"higher",
),
comparisonRow(
"Output Limit",
"Higher limit is better.",
{
value: formatCatalogLimit(first.catalog?.limit?.output),
score: first.catalog?.limit?.output,
},
{
value: formatCatalogLimit(second.catalog?.limit?.output),
score: second.catalog?.limit?.output,
},
"higher",
),
comparisonRow(
"Release Date",
"Newer release is highlighted.",
{
value: formatCatalogDate(first.catalog?.releaseDate),
score: catalogDateScore(first.catalog?.releaseDate),
},
{
value: formatCatalogDate(second.catalog?.releaseDate),
score: catalogDateScore(second.catalog?.releaseDate),
},
"higher",
),
comparisonRow(
"Reasoning",
"Supports reasoning.",
booleanCell(first.catalog?.reasoning),
booleanCell(second.catalog?.reasoning),
"higher",
),
comparisonRow(
"Tool Calling",
"Supports tool calls.",
booleanCell(first.catalog?.toolCall),
booleanCell(second.catalog?.toolCall),
"higher",
),
comparisonRow(
"Attachments",
"Supports attachments.",
booleanCell(first.catalog?.attachment),
booleanCell(second.catalog?.attachment),
"higher",
),
comparisonRow(
"Open Weights",
"Open weights available.",
booleanCell(first.catalog?.openWeights),
booleanCell(second.catalog?.openWeights),
"higher",
),
]
}
function comparisonRow(
label: string,
description: string,
first: ComparisonCell,
second: ComparisonCell,
direction: ComparisonDirection,
): ComparisonRow {
return { label, description, direction, cells: [first, second] }
}
function bestCellIndex(row: ComparisonRow) {
const [first, second] = row.cells.map((cell) => cell.score)
if (first === undefined || second === undefined || first === second) return undefined
if (row.direction === "higher") return first > second ? 0 : 1
return first < second ? 0 : 1
}
function buildRelatedPairs(
catalog: ModelCatalog | undefined,
first: ComparisonModel,
second: ComparisonModel,
): ComparisonPair[] {
const current = [comparisonRef(first), comparisonRef(second)] as const
const alternatives = (catalog?.models ?? [])
.filter((model) => model.id !== first.catalog?.id && model.id !== second.catalog?.id)
.slice(0, 4)
.map(modelRefFromCatalog)
return uniqueComparisonPairs([
...alternatives.slice(0, 3).flatMap((model, index) => [
{ first: current[0], second: model, detail: index === 0 ? "Nearby alternative" : "Related comparison" },
{ first: current[1], second: model, detail: index === 0 ? "Nearby alternative" : "Related comparison" },
]),
]).slice(0, 6)
}
function comparisonRef(model: ComparisonModel): ComparisonModelRef {
return {
name: model.name,
lab: model.lab,
slug: model.slug,
labName: model.labName,
metric: model.stats ? `#${model.stats.rank}` : "Catalog",
}
}
function positiveScore(value: number | undefined) {
return value && value > 0 ? value : undefined
}
function booleanCell(value: boolean | undefined): ComparisonCell {
if (value === undefined) return { value: "Unknown" }
return { value: value ? "Yes" : "No", score: value ? 1 : 0 }
}
function catalogDateScore(value: string | undefined) {
if (!value) return undefined
const match = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/.exec(value)
if (!match) return undefined
return Date.UTC(Number(match[1]), match[2] ? Number(match[2]) - 1 : 0, match[3] ? Number(match[3]) : 1)
}
function formatParamName(value: string) {
return value
.replace(/[-_]/g, " ")
.replace(/\b\w/g, (letter) => letter.toUpperCase())
.trim()
}
function formatCatalogLimit(value: number | undefined) {
return value === undefined ? "Unknown" : formatTokens(value)
}
function formatCatalogDate(value: string | undefined) {
if (!value) return "Unknown"
const match = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/.exec(value)
if (!match) return value
const year = Number(match[1])
const month = match[2] ? Number(match[2]) - 1 : 0
const day = match[3] ? Number(match[3]) : 1
return new Intl.DateTimeFormat("en", {
month: match[2] ? "short" : undefined,
day: match[3] ? "numeric" : undefined,
year: "numeric",
timeZone: "UTC",
}).format(new Date(Date.UTC(year, month, day)))
}
function formatTokens(value: number) {
if (value >= 1_000_000_000_000)
return `${trimNumber(value / 1_000_000_000_000, value >= 10_000_000_000_000 ? 0 : 1)}T`
if (value >= 1_000_000_000) return `${trimNumber(value / 1_000_000_000, value >= 10_000_000_000 ? 0 : 1)}B`
if (value >= 1_000_000) return `${trimNumber(value / 1_000_000, value >= 10_000_000 ? 0 : 1)}M`
if (value >= 1_000) return `${trimNumber(value / 1_000, value >= 10_000 ? 0 : 1)}K`
return String(Math.round(value))
}
function formatInteger(value: number) {
return new Intl.NumberFormat("en").format(value)
}
function formatPercent(value: number) {
return `${trimNumber(value, value >= 10 ? 1 : 2)}%`
}
function formatMoney(value: number) {
if (value >= 1) return `$${trimNumber(value, 2)}`
if (value > 0) return `$${value.toFixed(4)}`
return "$0"
}
function formatSessionCost(value: number) {
if (value >= 1) return `$${trimNumber(value, 2)}`
if (value >= 0.01) return `$${value.toFixed(2)}`
if (value > 0) return `$${value.toFixed(4)}`
return "$0"
}
function trimNumber(value: number, digits: number) {
return Number(value.toFixed(digits)).toLocaleString("en")
}
@@ -0,0 +1,148 @@
import "../index.css"
import { Link, Meta, Title } from "@solidjs/meta"
import { createAsync } from "@solidjs/router"
import { createMemo, createSignal, onMount, Show } from "solid-js"
import { getRequestEvent } from "solid-js/web"
import { ComparisonCardsSection, modelRefFromCatalog, uniqueComparisonPairs } from "../compare-cards"
import { ComparisonSelector } from "../compare-selector"
import { getModelCatalog, type ModelCatalogEntry } from "../model-catalog"
import {
applyThemePreference,
Footer,
getGitHubStars,
Header,
isThemePreference,
themeStorageKey,
type HeaderLink,
type ThemePreference,
} from "../stats-shell"
const compareFallbackUrl = "https://stats.opencode.ai"
const compareTitle = "AI Model Comparison"
const compareDescription =
"Compare AI models used in OpenCode by context, output, release date, usage, rank, token share, and cost."
const compareHeaderLinks: readonly HeaderLink[] = [
{ href: "#compare-tool", label: "Compare" },
{ href: "#model-comparison", label: "Popular" },
]
const compareFooterLinks: readonly HeaderLink[] = [
{ href: import.meta.env.BASE_URL, label: "Data Home" },
{ href: `${import.meta.env.BASE_URL}#top-models`, label: "Top Models" },
{ href: `${import.meta.env.BASE_URL}#token-cost`, label: "Token Cost" },
{ href: `${import.meta.env.BASE_URL}compare`, label: "Model Compare" },
]
export default function ModelCompareIndex() {
const event = getRequestEvent()
event?.response.headers.set("Cache-Control", "public, max-age=60, s-maxage=300, stale-while-revalidate=86400")
const catalog = createAsync(() => getModelCatalog())
const githubStars = createAsync(() => getGitHubStars())
const [themePreference, setThemePreference] = createSignal<ThemePreference>("system")
const compareUrl = createMemo(() =>
new URL(
`${import.meta.env.BASE_URL}compare`,
event?.request.url ?? (typeof window === "undefined" ? compareFallbackUrl : window.location.href),
).toString(),
)
const featuredModels = createMemo(() => (catalog()?.models ?? []).slice(0, 120))
const pairs = createMemo(() => buildPopularPairs(featuredModels()))
const updateThemePreference = (preference: ThemePreference) => {
applyThemePreference(preference)
setThemePreference(preference)
if (typeof window === "undefined") return
window.localStorage.setItem(themeStorageKey, preference)
}
onMount(() => {
if (typeof window === "undefined") return
const preference = window.localStorage.getItem(themeStorageKey)
const nextPreference = isThemePreference(preference) ? preference : "system"
applyThemePreference(nextPreference)
setThemePreference(nextPreference)
})
return (
<main data-page="stats" data-theme={themePreference()}>
<Title>{compareTitle}</Title>
<Meta name="description" content={compareDescription} />
<Link rel="canonical" href={compareUrl()} />
<Meta property="og:type" content="website" />
<Meta property="og:site_name" content="OpenCode" />
<Meta property="og:title" content={compareTitle} />
<Meta property="og:description" content={compareDescription} />
<Meta property="og:url" content={compareUrl()} />
<Meta name="twitter:card" content="summary" />
<Meta name="twitter:title" content={compareTitle} />
<Meta name="twitter:description" content={compareDescription} />
<Header githubStars={githubStars() ?? "150K"} links={compareHeaderLinks} brandHref={import.meta.env.BASE_URL} />
<div data-component="container">
<div data-component="content">
<section id="compare-tool" data-section="model-hero">
<a data-slot="model-back-link" href={import.meta.env.BASE_URL}>
Data
</a>
<div data-slot="model-hero-grid">
<div data-slot="model-hero-copy">
<span data-slot="model-id-tag">/compare</span>
<h1>Model Comparison</h1>
<p>Choose two models and compare usage, cost, limits, and features.</p>
</div>
<div data-component="model-rank-panel">
<span>Model Pairs</span>
<strong>{formatPairCount(featuredModels().length)}</strong>
<p>Available pairings from the model list.</p>
</div>
</div>
<div data-slot="model-hero-pattern" aria-hidden="true" />
<Show
when={featuredModels().length > 1}
fallback={
<div data-component="empty-state" data-compact="true">
<strong>No models found</strong>
<p>The model list could not be loaded.</p>
</div>
}
>
<ComparisonSelector models={featuredModels()} />
</Show>
</section>
<ComparisonCardsSection
pairs={pairs()}
title="Popular Model Comparisons"
description="Common model pairs to start with."
/>
</div>
<Footer
themePreference={themePreference()}
onThemePreferenceChange={updateThemePreference}
links={compareFooterLinks}
bridge={{ href: "#compare-tool", label: "MODEL COMPARE" }}
/>
</div>
</main>
)
}
function buildPopularPairs(models: ModelCatalogEntry[]) {
return uniqueComparisonPairs(
(
[
[0, 1, "Popular pair"],
[0, 2, "Top alternative"],
[1, 2, "Nearby pair"],
[2, 3, "Recent model pair"],
[3, 4, "Model pair"],
[4, 5, "Model pair"],
] as const
).flatMap(([firstIndex, secondIndex, detail]) => {
const first = models[firstIndex]
const second = models[secondIndex]
return first && second ? [{ first: modelRefFromCatalog(first), second: modelRefFromCatalog(second), detail }] : []
}),
)
}
function formatPairCount(count: number) {
if (count < 2) return "0"
return new Intl.NumberFormat("en", { notation: "compact", maximumFractionDigits: 1 }).format((count * (count - 1)) / 2)
}
+253 -2
View File
@@ -3261,6 +3261,243 @@
white-space: nowrap;
}
[data-page="stats"] [data-section="model-panel"][data-variant="compact"] {
padding-top: 56px;
padding-bottom: 56px;
}
[data-page="stats"] [data-component="comparison-card-grid"] {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
}
[data-page="stats"] [data-component="comparison-card"] {
display: grid;
align-content: space-between;
gap: 18px;
min-height: 168px;
min-width: 0;
padding: 16px;
border: 1px solid var(--stats-line);
background: var(--stats-layer);
color: var(--stats-text);
text-decoration: none;
}
[data-page="stats"] [data-component="comparison-card"]:hover,
[data-page="stats"] [data-component="comparison-card"]:focus-visible {
border-color: var(--stats-text);
outline: none;
background: var(--stats-bg);
text-decoration: none;
}
[data-page="stats"] [data-component="comparison-card"] span,
[data-page="stats"] [data-component="comparison-card"] p,
[data-page="stats"] [data-component="comparison-card"] small {
color: var(--stats-muted);
font-size: 11px;
font-weight: 500;
line-height: 1.35;
}
[data-page="stats"] [data-component="comparison-card"] strong {
min-width: 0;
color: var(--stats-text);
font-size: 20px;
font-weight: 500;
line-height: 1.08;
overflow-wrap: anywhere;
}
[data-page="stats"] [data-component="comparison-card"] em {
color: var(--stats-faint);
font: inherit;
}
[data-page="stats"] [data-component="comparison-card"] p {
display: flex;
align-items: center;
gap: 8px;
margin: 0;
}
[data-page="stats"] [data-component="comparison-card"] b {
min-width: 0;
overflow: hidden;
font: inherit;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-page="stats"] [data-component="comparison-card"] i {
flex: 0 0 auto;
width: 16px;
height: 1px;
background: var(--stats-line-strong);
}
[data-page="stats"] [data-component="comparison-selector"] {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
gap: 8px;
align-items: end;
max-width: 920px;
}
[data-page="stats"] [data-component="comparison-selector"] label {
display: grid;
gap: 8px;
min-width: 0;
}
[data-page="stats"] [data-component="comparison-selector"] label span {
color: var(--stats-muted);
font-size: 11px;
font-weight: 500;
line-height: 1.2;
}
[data-page="stats"] [data-component="comparison-selector"] select,
[data-page="stats"] [data-component="comparison-selector"] button {
box-sizing: border-box;
height: 40px;
min-width: 0;
border: 1px solid var(--stats-line);
border-radius: 0;
appearance: none;
font: inherit;
font-size: 13px;
font-weight: 500;
line-height: 1;
}
[data-page="stats"] [data-component="comparison-selector"] select {
width: 100%;
padding: 0 36px 0 12px;
background:
linear-gradient(45deg, transparent 50%, var(--stats-muted) 50%) calc(100% - 18px) 17px / 5px 5px no-repeat,
linear-gradient(135deg, var(--stats-muted) 50%, transparent 50%) calc(100% - 13px) 17px / 5px 5px no-repeat,
var(--stats-bg);
color: var(--stats-text);
}
[data-page="stats"] [data-component="comparison-selector"] button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 14px;
background: var(--stats-text);
color: var(--stats-bg);
cursor: pointer;
text-decoration: none;
white-space: nowrap;
}
[data-page="stats"] [data-component="comparison-selector"] button:disabled {
cursor: not-allowed;
opacity: 0.45;
}
[data-page="stats"] [data-component="comparison-selector"] select:focus-visible,
[data-page="stats"] [data-component="comparison-selector"] button:focus-visible {
outline: 2px solid var(--stats-accent);
outline-offset: 2px;
}
[data-page="stats"] [data-component="comparison-table-wrap"] {
overflow-x: auto;
border: 1px solid var(--stats-line);
background: var(--stats-layer);
}
[data-page="stats"] [data-component="comparison-table"] {
width: 100%;
min-width: 720px;
border-collapse: collapse;
color: var(--stats-text);
font-size: 13px;
line-height: 1.35;
}
[data-page="stats"] [data-component="comparison-table"] caption {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
[data-page="stats"] [data-component="comparison-table"] th,
[data-page="stats"] [data-component="comparison-table"] td {
min-width: 0;
padding: 14px 16px;
border-bottom: 1px solid var(--stats-line);
border-left: 1px solid var(--stats-line);
text-align: left;
vertical-align: top;
}
[data-page="stats"] [data-component="comparison-table"] th:first-child {
border-left: 0;
}
[data-page="stats"] [data-component="comparison-table"] thead th {
color: var(--stats-muted);
font-size: 11px;
font-weight: 600;
line-height: 1.2;
text-transform: uppercase;
}
[data-page="stats"] [data-component="comparison-table"] tbody tr:last-child th,
[data-page="stats"] [data-component="comparison-table"] tbody tr:last-child td {
border-bottom: 0;
}
[data-page="stats"] [data-component="comparison-table"] tbody th {
width: 34%;
background: var(--stats-bg);
}
[data-page="stats"] [data-component="comparison-table"] tbody th strong,
[data-page="stats"] [data-component="comparison-table"] td strong {
display: block;
color: var(--stats-text);
font-size: 13px;
font-weight: 600;
line-height: 1.25;
overflow-wrap: anywhere;
}
[data-page="stats"] [data-component="comparison-table"] tbody th span,
[data-page="stats"] [data-component="comparison-table"] td span {
display: block;
margin-top: 5px;
color: var(--stats-muted);
font-size: 11px;
font-weight: 500;
line-height: 1.35;
}
[data-page="stats"] [data-component="comparison-table"] td[data-best="true"] {
position: relative;
background: var(--stats-bg);
box-shadow: inset 0 0 0 1px var(--stats-accent);
}
[data-page="stats"] [data-component="comparison-table"] td[data-best="true"]::before {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 3px;
content: "";
background: var(--stats-accent);
}
[data-page="stats"][data-theme="dark"],
:root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) {
color-scheme: dark;
@@ -3629,10 +3866,19 @@
[data-page="stats"] [data-component="model-metric-grid"],
[data-page="stats"] [data-component="model-metric-grid"][data-variant="dense"],
[data-page="stats"] [data-component="lab-model-grid"] {
[data-page="stats"] [data-component="lab-model-grid"],
[data-page="stats"] [data-component="comparison-card-grid"] {
grid-template-columns: 1fr 1fr;
}
[data-page="stats"] [data-component="comparison-selector"] {
grid-template-columns: 1fr;
}
[data-page="stats"] [data-component="comparison-selector"] button {
width: 100%;
}
[data-page="stats"] [data-component="model-peer-list"] a {
grid-template-columns: 32px 22px minmax(0, 1fr) minmax(72px, auto);
}
@@ -3813,10 +4059,15 @@
[data-page="stats"] [data-component="model-metric-grid"],
[data-page="stats"] [data-component="model-metric-grid"][data-variant="dense"],
[data-page="stats"] [data-component="lab-model-grid"] {
[data-page="stats"] [data-component="lab-model-grid"],
[data-page="stats"] [data-component="comparison-card-grid"] {
grid-template-columns: 1fr;
}
[data-page="stats"] [data-component="comparison-table"] {
min-width: 640px;
}
[data-page="stats"] [data-component="model-metric"] {
min-height: 112px;
}
+45 -9
View File
@@ -28,6 +28,7 @@ import type { FeatureCollection, GeometryObject, GeoJsonProperties } from "geojs
import type { GeometryCollection, Topology } from "topojson-specification"
import { runStatsEffect } from "../stats-runtime"
import { findModelCatalogEntry, getModelCatalog, type ModelCatalog } from "./model-catalog"
import { ComparisonCardsSection, uniqueComparisonPairs, type ComparisonModelRef } from "./compare-cards"
import {
applyThemePreference,
Footer,
@@ -49,6 +50,12 @@ const rangeLabels: Record<UsageRange, string> = {
"1M": "1 Month",
"2M": "2 Months",
}
const comparisonPairIndexes = [
[0, 1, "Top two by recent usage"],
[0, 2, "Leader vs challenger"],
[1, 2, "Adjacent leaderboard pair"],
[2, 3, "Top model alternative"],
] as const
const statsHomeTitle = "AI Model Usage Rankings | OpenCode Data"
const statsHomeDescription =
"Explore OpenCode Go usage across AI models, including token volume, rankings, market share, token pricing, session cost, cache ratio, and geo breakdowns."
@@ -177,11 +184,20 @@ export default function StatsHome() {
<CacheRatioSection data={stats().cacheRatio} />
<MarketShareSection data={stats().market} />
<GeoBreakdownSection data={stats().country} />
<ComparisonCardsSection
pairs={homeComparisonPairs(stats().leaderboard["All Users"]["2M"])}
title="Model Comparisons"
description="Popular model pairs from the leaderboard."
/>
</>
)}
</Show>
</div>
<Footer themePreference={themePreference()} onThemePreferenceChange={updateThemePreference} />
<Footer
themePreference={themePreference()}
onThemePreferenceChange={updateThemePreference}
bridge={{ href: "#model-comparison", label: "MODEL COMPARISONS" }}
/>
</div>
</main>
)
@@ -296,7 +312,7 @@ function StatsLoading() {
<>
<Hero updatedAt={null} />
<ChartSection title="Usage">
<EmptyState title="Loading data" description="Reading model aggregates from model_stat." />
<EmptyState title="Loading data" description="Loading model data." />
</ChartSection>
</>
)
@@ -407,7 +423,7 @@ function TopModelsSection(props: { data: StatsHomeData["usage"]; leaderboard: St
</h2>
<Show
when={data().some((item) => usageTotal(item) > 0)}
fallback={<EmptyState title="No usage data" description="No model_stat rows matched this product and range." />}
fallback={<EmptyState title="No usage data" description="No usage data for this product and range." />}
>
<TopModelsChart
data={data()}
@@ -419,7 +435,7 @@ function TopModelsSection(props: { data: StatsHomeData["usage"]; leaderboard: St
<Show
when={leaderboard().length > 0}
fallback={
<EmptyState title="No leaderboard data" description="No model_stat rows matched this product and range." />
<EmptyState title="No leaderboard data" description="No leaderboard data for this product and range." />
}
>
<Leaderboard data={leaderboard()} activeModel={activeModel()} onActiveModelChange={setActiveModel} />
@@ -1060,7 +1076,7 @@ function MarketShareSection(props: { data: StatsHomeData["market"] }) {
<SectionTitle title="Market Share" description="Compare token share by model author." />
<Show
when={activeDay()}
fallback={<EmptyState title="No market data" description="No model_stat rows matched this range." />}
fallback={<EmptyState title="No market data" description="No market data for this range." />}
>
{(day) => (
<>
@@ -1277,7 +1293,7 @@ function GeoBreakdownSection(props: { data: StatsHomeData["country"] }) {
<SectionTitle title="Geo Breakdown" description="Tokens used by country." />
<Show
when={data().length > 0}
fallback={<EmptyState title="No geo data" description="No geo_stat rows matched this range." />}
fallback={<EmptyState title="No geo data" description="No country data for this range." />}
>
<div data-component="geo-breakdown">
<div data-slot="geo-map-panel">
@@ -1557,7 +1573,7 @@ function TokenCostSection(props: { data: StatsHomeData["tokenCost"]; catalog: Mo
<Show
when={visible().length > 0}
fallback={
<EmptyState title="No token cost data" description="No cost-bearing model_stat rows matched this product." />
<EmptyState title="No token cost data" description="No token cost data for this product." />
}
>
<TokenCostChart data={visible()} activeIndex={selectedIndex()} onActiveIndexChange={setActiveIndex} />
@@ -1637,7 +1653,7 @@ function CacheRatioSection(props: { data: StatsHomeData["cacheRatio"] }) {
<Show
when={visible().length > 0}
fallback={
<EmptyState title="No cache ratio data" description="No input-token model_stat rows matched this product." />
<EmptyState title="No cache ratio data" description="No cache ratio data for this product." />
}
>
<CacheRatioChart data={visible()} activeIndex={selectedIndex()} onActiveIndexChange={setActiveIndex} />
@@ -1764,7 +1780,7 @@ function SessionCostSection(props: { data: StatsHomeData["sessionCost"] }) {
fallback={
<EmptyState
title="No session cost data"
description="No session-bearing model_stat rows matched this product."
description="No session cost data for this product."
/>
}
>
@@ -1876,6 +1892,26 @@ function formatSessionCost(value: number) {
return `$${value.toFixed(4)}`
}
function homeComparisonPairs(leaderboard: LeaderboardEntry[]) {
return uniqueComparisonPairs(
comparisonPairIndexes.flatMap(([firstIndex, secondIndex, detail]) => {
const first = leaderboard[firstIndex]
const second = leaderboard[secondIndex]
return first && second ? [{ first: leaderboardRef(first), second: leaderboardRef(second), detail }] : []
}),
)
}
function leaderboardRef(entry: LeaderboardEntry): ComparisonModelRef {
return {
name: entry.model,
lab: entry.provider,
slug: modelSlug(entry.model),
labName: entry.author,
metric: `#${entry.rank} / ${formatBillions(entry.tokens)}`,
}
}
function modelSlug(value: string) {
return value
.trim()
@@ -206,6 +206,7 @@ export function Footer(props: {
themePreference: ThemePreference
onThemePreferenceChange: (preference: ThemePreference) => void
links?: readonly HeaderLink[]
bridge?: HeaderLink | null
}) {
const [subscribeOpen, setSubscribeOpen] = createSignal(false)
const modelStats = props.links ?? [
@@ -228,10 +229,12 @@ export function Footer(props: {
githubLink,
{ href: "https://www.youtube.com/@anomaly-co", label: "YouTube" },
]
const bridge = () =>
props.bridge === undefined ? { href: "#geo-breakdown", label: "GEO BREAKDOWN" } : props.bridge
return (
<footer data-component="footer">
<SectionBridge label="GEO BREAKDOWN" href="#geo-breakdown" />
<Show when={bridge()}>{(link) => <SectionBridge label={link().label} href={link().href} />}</Show>
<div data-slot="footer-grid">
<a data-slot="footer-mark" href="https://opencode.ai" aria-label="OpenCode home">
<OpenCodeMark />
+57 -1
View File
@@ -1,8 +1,9 @@
import { Client } from "@planetscale/database"
import { Effect } from "effect"
import { Resource } from "sst/resource"
import { DatabaseError } from "../database"
import type { GeoStatMetric } from "./geo"
import type { ModelStatMetric } from "./model"
import { ModelStatRepo, type ModelStatMetric } from "./model"
import type { ProviderStatMetric } from "./provider"
export type UsageProduct = "All Users" | "Zen" | "Go" | "Enterprise"
@@ -81,6 +82,23 @@ export type StatsLabData = {
usage: ModelUsagePoint[]
models: LabUsageModelEntry[]
}
export type StatsModelComparisonEntry = {
updatedAt: string | null
model: string
slug: string
provider: string
author: string
rank: number | null
previousRank: number | null
totalModels: number
tokenShare: number
tokenChange: number
totals: StatsModelData["totals"]
}
export type StatsModelComparisonData = {
updatedAt: string | null
models: [StatsModelComparisonEntry | null, StatsModelComparisonEntry | null]
}
export type StatsHomeData = {
updatedAt: string | null
usage: Record<UsageProduct, Record<UsageRange, UsagePoint[]>>
@@ -271,6 +289,27 @@ function dateValue(value: unknown) {
return value instanceof Date ? value : new Date(stringValue(value))
}
export const getStatsModelComparisonData: (
firstProvider: string,
firstModel: string,
secondProvider: string,
secondModel: string,
) => Effect.Effect<StatsModelComparisonData, DatabaseError, ModelStatRepo> = Effect.fn("StatsModelComparison.getData")(
function* (firstProvider, firstModel, secondProvider, secondModel) {
const modelStats = yield* ModelStatRepo
const rows = yield* modelStats.listDaily()
const first = toComparisonEntry(buildStatsModelData(firstModel, rows, [], firstProvider))
const second = toComparisonEntry(buildStatsModelData(secondModel, rows, [], secondProvider))
const latest = [first?.updatedAt, second?.updatedAt]
.flatMap((value) => (value ? [dateTime(value)] : []))
.toSorted((a, b) => b - a)[0]
return {
updatedAt: latest === undefined ? null : new Date(latest).toISOString(),
models: [first, second],
}
},
)
function buildStatsHomeData(
modelRows: ModelStatMetric[],
providerRows: ProviderStatMetric[],
@@ -448,6 +487,23 @@ function buildStatsLabData(providerParam: string, modelRows: ModelStatMetric[]):
}
}
function toComparisonEntry(data: StatsModelData | null): StatsModelComparisonEntry | null {
if (!data) return null
return {
updatedAt: data.updatedAt,
model: data.model,
slug: data.slug,
provider: data.provider,
author: data.author,
rank: data.rank,
previousRank: data.previousRank,
totalModels: data.totalModels,
tokenShare: data.tokenShare,
tokenChange: data.tokenChange,
totals: data.totals,
}
}
function emptyStatsHomeData(): StatsHomeData {
return {
updatedAt: null,