mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-27 07:43:34 +00:00
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 { 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue