feat(stats): unify comparison cards

This commit is contained in:
Adam 2026-07-10 06:49:50 -05:00
parent 7dbfd0b934
commit f4faacbe87
No known key found for this signature in database
GPG key ID: 9CB48779AF150E75
4 changed files with 109 additions and 118 deletions

View file

@ -1,3 +1,4 @@
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { For, Show } from "solid-js"
import { catalogSlug, formatCatalogLabName, type ModelCatalogEntry } from "./model-catalog"
@ -13,6 +14,7 @@ export type ComparisonPair = {
first: ComparisonModelRef
second: ComparisonModelRef
detail: string
description?: string
}
export function modelRefFromCatalog(entry: ModelCatalogEntry): ComparisonModelRef {
@ -48,34 +50,25 @@ export function ComparisonCardsSection(props: {
title?: string
description?: string
compact?: boolean
variant?: "panel" | "featured"
}) {
const featured = () => props.variant === "featured"
const pairs = () => (featured() ? props.pairs.slice(0, 4) : props.pairs)
return (
<Show when={props.pairs.length > 0}>
<section id="model-comparison" data-section="model-panel" data-variant={props.compact ? "compact" : undefined}>
<section
id="model-comparison"
data-section={featured() ? "compare-home-related" : "model-panel"}
data-variant={!featured() && 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>
)}
<div data-component={featured() ? "compare-home-card-grid" : "comparison-card-grid"}>
<For each={pairs()}>
{(pair) => (featured() ? <FeaturedComparisonCard pair={pair} /> : <ComparisonPanelCard pair={pair} />)}
</For>
</div>
</section>
@ -83,6 +76,77 @@ export function ComparisonCardsSection(props: {
)
}
function FeaturedComparisonCard(props: { pair: ComparisonPair }) {
return (
<a
data-component="compare-home-card"
href={comparisonHref(props.pair.first, props.pair.second)}
aria-label={`${props.pair.detail}: ${props.pair.first.name} vs ${props.pair.second.name}`}
>
<span data-slot="compare-home-card-head">
<span>
<strong>{props.pair.detail}</strong>
<em>{props.pair.description ?? `${props.pair.first.name} vs ${props.pair.second.name}`}</em>
</span>
<b aria-hidden="true" />
</span>
<span data-slot="compare-home-card-divider" aria-hidden="true" />
<span data-slot="compare-home-card-models">
<span>{props.pair.first.name}</span>
<i aria-hidden="true">·</i>
<span>{props.pair.second.name}</span>
</span>
<span data-slot="compare-home-card-avatars" aria-hidden="true">
<ComparisonLabLogo model={props.pair.first} />
<ComparisonLabLogo model={props.pair.second} />
</span>
</a>
)
}
function ComparisonPanelCard(props: { pair: ComparisonPair }) {
return (
<a data-component="comparison-card" href={comparisonHref(props.pair.first, props.pair.second)}>
<span>{props.pair.detail}</span>
<strong>
{props.pair.first.name} <em>vs</em> {props.pair.second.name}
</strong>
<p>
<b>{props.pair.first.labName ?? formatCatalogLabName(props.pair.first.lab)}</b>
<i />
<b>{props.pair.second.labName ?? formatCatalogLabName(props.pair.second.lab)}</b>
</p>
<Show when={props.pair.first.metric || props.pair.second.metric}>
<small>
{props.pair.first.metric ?? "Listed"} / {props.pair.second.metric ?? "Listed"}
</small>
</Show>
</a>
)
}
function ComparisonLabLogo(props: { model: ComparisonModelRef }) {
const iconId = () => providerIconId(props.model.lab)
return (
<span
data-slot="compare-home-avatar"
data-lab={iconId()}
data-size="small"
aria-label={props.model.labName ?? formatCatalogLabName(props.model.lab)}
>
<ProviderIcon aria-hidden="true" id={iconId()} />
</span>
)
}
function modelKey(model: ComparisonModelRef) {
return `${catalogSlug(model.lab)}/${catalogSlug(model.slug)}`
}
function providerIconId(provider: string) {
const id = provider.toLowerCase().replace(/[^a-z0-9]+/g, "")
if (id === "moonshot") return "moonshotai"
if (id === "zhipu") return "zhipuai"
return id
}

View file

@ -12,6 +12,7 @@ import { createAsync, query, useParams, useSearchParams } from "@solidjs/router"
import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js"
import { getRequestEvent } from "solid-js/web"
import {
ComparisonCardsSection,
comparisonHref,
modelRefFromCatalog,
uniqueComparisonPairs,
@ -382,7 +383,12 @@ export default function ModelComparePair() {
</Show>
</div>
</div>
<ComparisonRelatedSection pairs={relatedPairs()} />
<ComparisonCardsSection
pairs={relatedPairs()}
title="Related comparisons"
description="Other model pairs to check."
variant="featured"
/>
</div>
<Footer
themePreference={themePreference()}
@ -865,51 +871,6 @@ function ComparisonUsageBars(props: { data: ModelUsagePoint[]; column: number; l
)
}
function ComparisonRelatedSection(props: { pairs: ComparisonPair[] }) {
return (
<Show when={props.pairs.length > 0}>
<section id="model-comparison" data-section="compare-home-related">
<p data-slot="section-title">
<strong>Related comparisons.</strong> <span>Other model pairs to check.</span>
</p>
<div data-component="compare-home-card-grid">
<For each={props.pairs.slice(0, 4)}>{(pair) => <ComparisonRelatedCard pair={pair} />}</For>
</div>
</section>
</Show>
)
}
function ComparisonRelatedCard(props: { pair: ComparisonPair }) {
return (
<a
data-component="compare-home-card"
href={comparisonHref(props.pair.first, props.pair.second)}
aria-label={`${props.pair.first.name} vs ${props.pair.second.name}`}
>
<span data-slot="compare-home-card-head">
<span>
<strong>{props.pair.detail}</strong>
<em>
{props.pair.first.name} vs {props.pair.second.name}
</em>
</span>
<b aria-hidden="true" />
</span>
<span data-slot="compare-home-card-divider" aria-hidden="true" />
<span data-slot="compare-home-card-models">
<span>{props.pair.first.name}</span>
<i aria-hidden="true">·</i>
<span>{props.pair.second.name}</span>
</span>
<span data-slot="compare-home-card-avatars" aria-hidden="true">
<LabLogo lab={props.pair.first.lab} label={props.pair.first.labName ?? props.pair.first.lab} size="small" />
<LabLogo lab={props.pair.second.lab} label={props.pair.second.labName ?? props.pair.second.lab} size="small" />
</span>
</a>
)
}
function ModelAvatar(props: { model: ModelCatalogEntry; size: "large" | "small" | "tiny" }) {
return <LabLogo lab={props.model.lab} label={props.model.name} size={props.size} />
}

View file

@ -8,7 +8,13 @@ import { LocaleLinks } from "../../component/locale-links"
import { useI18n } from "../../context/i18n"
import { useLanguage } from "../../context/language"
import { localizedUrl } from "../../lib/language"
import { comparisonHref, modelRefFromCatalog, type ComparisonModelRef } from "../compare-cards"
import {
ComparisonCardsSection,
comparisonHref,
modelRefFromCatalog,
type ComparisonModelRef,
type ComparisonPair,
} from "../compare-cards"
import { formatCatalogLabName, getModelCatalog, type ModelCatalogEntry } from "../model-catalog"
import { setStatsPageCacheHeaders } from "../stats-cache"
import {
@ -56,13 +62,6 @@ const categoryTemplates = [
},
] as const
type CompareCategory = {
title: string
description: string
first: ComparisonModelRef
second: ComparisonModelRef
avatars: ComparisonModelRef[]
}
type CompareSlot = "first" | "second"
export default function ModelCompareIndex() {
@ -162,16 +161,12 @@ export default function ModelCompareIndex() {
<CompareHomeSelector models={featuredModels()} />
</Show>
</section>
<Show when={categories().length > 0}>
<section id="model-comparison" data-section="compare-home-related">
<p data-slot="section-title">
<strong>Related comparisons.</strong> <span>Other model pairs to check.</span>
</p>
<div data-component="compare-home-card-grid">
<For each={categories()}>{(category) => <CompareHomeCard category={category} />}</For>
</div>
</section>
</Show>
<ComparisonCardsSection
pairs={categories()}
title="Related comparisons"
description="Other model pairs to check."
variant="featured"
/>
</div>
<Footer
themePreference={themePreference()}
@ -443,35 +438,6 @@ function HeroModelStack() {
)
}
function CompareHomeCard(props: { category: CompareCategory }) {
return (
<a
data-component="compare-home-card"
href={comparisonHref(props.category.first, props.category.second)}
aria-label={`${props.category.title}: ${props.category.first.name} vs ${props.category.second.name}`}
>
<span data-slot="compare-home-card-head">
<span>
<strong>{props.category.title}</strong>
<em>{props.category.description}</em>
</span>
<b aria-hidden="true" />
</span>
<span data-slot="compare-home-card-divider" aria-hidden="true" />
<span data-slot="compare-home-card-models">
<span>{props.category.first.name}</span>
<i aria-hidden="true">·</i>
<span>{props.category.second.name}</span>
</span>
<span data-slot="compare-home-card-avatars" aria-hidden="true">
<For each={props.category.avatars}>
{(model) => <LabLogo lab={model.lab} label={model.name} size="small" />}
</For>
</span>
</a>
)
}
function ModelAvatar(props: { model: ModelCatalogEntry; size: "large" | "small" | "tiny" }) {
return <LabLogo lab={props.model.lab} label={props.model.name} size={props.size} />
}
@ -486,8 +452,8 @@ function LabLogo(props: { lab: string; label: string; size: "large" | "small" |
)
}
function buildComparisonCategories(models: ModelCatalogEntry[]): CompareCategory[] {
return categoryTemplates.reduce<{ keys: Set<string>; categories: CompareCategory[] }>(
function buildComparisonCategories(models: ModelCatalogEntry[]): ComparisonPair[] {
return categoryTemplates.reduce<{ keys: Set<string>; categories: ComparisonPair[] }>(
(result, template, index) => {
const candidates = categoryCandidates(template.kind, models)
const pair = categoryPair(candidates, models, index, result.keys)
@ -496,11 +462,10 @@ function buildComparisonCategories(models: ModelCatalogEntry[]): CompareCategory
const first = modelRefFromCatalog(pair.first)
const second = modelRefFromCatalog(pair.second)
result.categories.push({
title: template.title,
detail: template.title,
description: template.description,
first,
second,
avatars: [first, second],
})
return result
},

View file

@ -194,6 +194,7 @@ export default function StatsHome() {
pairs={homeComparisonPairs(stats().leaderboard["All Users"]["2M"])}
title="Model Comparisons"
description="Popular model pairs from the leaderboard."
variant="featured"
/>
</>
)}