feat(stats): unify comparison cards
This commit is contained in:
parent
7dbfd0b934
commit
f4faacbe87
4 changed files with 109 additions and 118 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
import { For, Show } from "solid-js"
|
import { For, Show } from "solid-js"
|
||||||
import { catalogSlug, formatCatalogLabName, type ModelCatalogEntry } from "./model-catalog"
|
import { catalogSlug, formatCatalogLabName, type ModelCatalogEntry } from "./model-catalog"
|
||||||
|
|
||||||
|
|
@ -13,6 +14,7 @@ export type ComparisonPair = {
|
||||||
first: ComparisonModelRef
|
first: ComparisonModelRef
|
||||||
second: ComparisonModelRef
|
second: ComparisonModelRef
|
||||||
detail: string
|
detail: string
|
||||||
|
description?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function modelRefFromCatalog(entry: ModelCatalogEntry): ComparisonModelRef {
|
export function modelRefFromCatalog(entry: ModelCatalogEntry): ComparisonModelRef {
|
||||||
|
|
@ -48,34 +50,25 @@ export function ComparisonCardsSection(props: {
|
||||||
title?: string
|
title?: string
|
||||||
description?: string
|
description?: string
|
||||||
compact?: boolean
|
compact?: boolean
|
||||||
|
variant?: "panel" | "featured"
|
||||||
}) {
|
}) {
|
||||||
|
const featured = () => props.variant === "featured"
|
||||||
|
const pairs = () => (featured() ? props.pairs.slice(0, 4) : props.pairs)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={props.pairs.length > 0}>
|
<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">
|
<p data-slot="section-title">
|
||||||
<strong>{props.title ?? "Model Comparisons"}.</strong>{" "}
|
<strong>{props.title ?? "Model Comparisons"}.</strong>{" "}
|
||||||
<span>{props.description ?? "Compare usage, cost, limits, and features."}</span>
|
<span>{props.description ?? "Compare usage, cost, limits, and features."}</span>
|
||||||
</p>
|
</p>
|
||||||
<div data-component="comparison-card-grid">
|
<div data-component={featured() ? "compare-home-card-grid" : "comparison-card-grid"}>
|
||||||
<For each={props.pairs}>
|
<For each={pairs()}>
|
||||||
{(pair) => (
|
{(pair) => (featured() ? <FeaturedComparisonCard pair={pair} /> : <ComparisonPanelCard pair={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>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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) {
|
function modelKey(model: ComparisonModelRef) {
|
||||||
return `${catalogSlug(model.lab)}/${catalogSlug(model.slug)}`
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { createAsync, query, useParams, useSearchParams } from "@solidjs/router"
|
||||||
import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js"
|
import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js"
|
||||||
import { getRequestEvent } from "solid-js/web"
|
import { getRequestEvent } from "solid-js/web"
|
||||||
import {
|
import {
|
||||||
|
ComparisonCardsSection,
|
||||||
comparisonHref,
|
comparisonHref,
|
||||||
modelRefFromCatalog,
|
modelRefFromCatalog,
|
||||||
uniqueComparisonPairs,
|
uniqueComparisonPairs,
|
||||||
|
|
@ -382,7 +383,12 @@ export default function ModelComparePair() {
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ComparisonRelatedSection pairs={relatedPairs()} />
|
<ComparisonCardsSection
|
||||||
|
pairs={relatedPairs()}
|
||||||
|
title="Related comparisons"
|
||||||
|
description="Other model pairs to check."
|
||||||
|
variant="featured"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Footer
|
<Footer
|
||||||
themePreference={themePreference()}
|
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" }) {
|
function ModelAvatar(props: { model: ModelCatalogEntry; size: "large" | "small" | "tiny" }) {
|
||||||
return <LabLogo lab={props.model.lab} label={props.model.name} size={props.size} />
|
return <LabLogo lab={props.model.lab} label={props.model.name} size={props.size} />
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,13 @@ import { LocaleLinks } from "../../component/locale-links"
|
||||||
import { useI18n } from "../../context/i18n"
|
import { useI18n } from "../../context/i18n"
|
||||||
import { useLanguage } from "../../context/language"
|
import { useLanguage } from "../../context/language"
|
||||||
import { localizedUrl } from "../../lib/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 { formatCatalogLabName, getModelCatalog, type ModelCatalogEntry } from "../model-catalog"
|
||||||
import { setStatsPageCacheHeaders } from "../stats-cache"
|
import { setStatsPageCacheHeaders } from "../stats-cache"
|
||||||
import {
|
import {
|
||||||
|
|
@ -56,13 +62,6 @@ const categoryTemplates = [
|
||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
type CompareCategory = {
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
first: ComparisonModelRef
|
|
||||||
second: ComparisonModelRef
|
|
||||||
avatars: ComparisonModelRef[]
|
|
||||||
}
|
|
||||||
type CompareSlot = "first" | "second"
|
type CompareSlot = "first" | "second"
|
||||||
|
|
||||||
export default function ModelCompareIndex() {
|
export default function ModelCompareIndex() {
|
||||||
|
|
@ -162,16 +161,12 @@ export default function ModelCompareIndex() {
|
||||||
<CompareHomeSelector models={featuredModels()} />
|
<CompareHomeSelector models={featuredModels()} />
|
||||||
</Show>
|
</Show>
|
||||||
</section>
|
</section>
|
||||||
<Show when={categories().length > 0}>
|
<ComparisonCardsSection
|
||||||
<section id="model-comparison" data-section="compare-home-related">
|
pairs={categories()}
|
||||||
<p data-slot="section-title">
|
title="Related comparisons"
|
||||||
<strong>Related comparisons.</strong> <span>Other model pairs to check.</span>
|
description="Other model pairs to check."
|
||||||
</p>
|
variant="featured"
|
||||||
<div data-component="compare-home-card-grid">
|
/>
|
||||||
<For each={categories()}>{(category) => <CompareHomeCard category={category} />}</For>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
<Footer
|
<Footer
|
||||||
themePreference={themePreference()}
|
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" }) {
|
function ModelAvatar(props: { model: ModelCatalogEntry; size: "large" | "small" | "tiny" }) {
|
||||||
return <LabLogo lab={props.model.lab} label={props.model.name} size={props.size} />
|
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[] {
|
function buildComparisonCategories(models: ModelCatalogEntry[]): ComparisonPair[] {
|
||||||
return categoryTemplates.reduce<{ keys: Set<string>; categories: CompareCategory[] }>(
|
return categoryTemplates.reduce<{ keys: Set<string>; categories: ComparisonPair[] }>(
|
||||||
(result, template, index) => {
|
(result, template, index) => {
|
||||||
const candidates = categoryCandidates(template.kind, models)
|
const candidates = categoryCandidates(template.kind, models)
|
||||||
const pair = categoryPair(candidates, models, index, result.keys)
|
const pair = categoryPair(candidates, models, index, result.keys)
|
||||||
|
|
@ -496,11 +462,10 @@ function buildComparisonCategories(models: ModelCatalogEntry[]): CompareCategory
|
||||||
const first = modelRefFromCatalog(pair.first)
|
const first = modelRefFromCatalog(pair.first)
|
||||||
const second = modelRefFromCatalog(pair.second)
|
const second = modelRefFromCatalog(pair.second)
|
||||||
result.categories.push({
|
result.categories.push({
|
||||||
title: template.title,
|
detail: template.title,
|
||||||
description: template.description,
|
description: template.description,
|
||||||
first,
|
first,
|
||||||
second,
|
second,
|
||||||
avatars: [first, second],
|
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,7 @@ export default function StatsHome() {
|
||||||
pairs={homeComparisonPairs(stats().leaderboard["All Users"]["2M"])}
|
pairs={homeComparisonPairs(stats().leaderboard["All Users"]["2M"])}
|
||||||
title="Model Comparisons"
|
title="Model Comparisons"
|
||||||
description="Popular model pairs from the leaderboard."
|
description="Popular model pairs from the leaderboard."
|
||||||
|
variant="featured"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue